Re: [AD] log window

[ Thread Index | Date Index | More lists.liballeg.org/allegro-developers Archives ]


On Mon, 2010-07-05 at 12:05 +1000, Peter Wang wrote:
> 
> I would just have the printf parameters:
>     al_append_log(window, const char *format, ...)
> 

True. First I thought you always want a new line so the \n would not be
needed but then I realized sometimes you do want to continue a line and
so added the other function... but yes, overkill.

> I think we should also let the user (try to) close the log window, and
> generate an event when that happens.  Then examples which output usage
> information can give the user a chance to read the message before
> calling al_close_native_log_window().

Ok, added a close button (and a flag to disable it) as well as the
event. New patch is attached.

> For non-example uses, we should limit the size of the log.  Probably a
> fixed limit of 1000 lines or so is good enough.
> 

True. Maybe with an unlimited flag since some things can generate a lot
of log lines. My terminal's scrollback buffer is set to 50 000 lines and
I think I still scroll all the way up occasionally and am annoyed when
the initial lines are lost :P

I wonder if we also should provide functions to clear the buffer or read
back its contents. The latter could be useful especially if we keep the
log window editable. Probably going overboard with this now though.

-- 
Elias Pschernig <elias.pschernig@xxxxxxxxxx>
diff --git a/addons/native_dialog/allegro5/allegro_native_dialog.h b/addons/native_dialog/allegro5/allegro_native_dialog.h
index 900591f..722d7d2 100644
--- a/addons/native_dialog/allegro5/allegro_native_dialog.h
+++ b/addons/native_dialog/allegro5/allegro_native_dialog.h
@@ -53,6 +53,15 @@ ALLEGRO_DIALOG_FUNC(
 ALLEGRO_DIALOG_FUNC(void, al_destroy_native_dialog, (ALLEGRO_NATIVE_DIALOG *fc));
 ALLEGRO_DIALOG_FUNC(uint32_t, al_get_allegro_native_dialog_version, (void));
 
+ALLEGRO_DIALOG_FUNC(ALLEGRO_NATIVE_DIALOG *, al_open_native_log_window,
+   (char const *title, int flags));
+ALLEGRO_DIALOG_FUNC(void, al_close_native_log_window,
+   (ALLEGRO_NATIVE_DIALOG *textlog));
+ALLEGRO_DIALOG_FUNC(void, al_append_log,
+   (ALLEGRO_NATIVE_DIALOG *textlog, char const *format, ...));
+ALLEGRO_DIALOG_FUNC(ALLEGRO_EVENT_SOURCE *,
+   al_get_native_dialog_event_source, (ALLEGRO_NATIVE_DIALOG *dialog));
+
 #define ALLEGRO_FILECHOOSER_FILE_MUST_EXIST 1
 #define ALLEGRO_FILECHOOSER_SAVE 2
 #define ALLEGRO_FILECHOOSER_FOLDER 4
@@ -66,6 +75,11 @@ ALLEGRO_DIALOG_FUNC(uint32_t, al_get_allegro_native_dialog_version, (void));
 #define ALLEGRO_MESSAGEBOX_YES_NO (1<<3)
 #define ALLEGRO_MESSAGEBOX_QUESTION (1<<4)
 
+#define ALLEGRO_MESSAGELOG_NO_CLOSE (1<<0)
+
+// FIXME: How do addons allocate those?
+#define ALLEGRO_EVENT_NATIVE_DIALOG_CLOSE 600
+
 #ifdef __cplusplus
    }
 #endif
diff --git a/addons/native_dialog/allegro5/internal/aintern_native_dialog.h b/addons/native_dialog/allegro5/internal/aintern_native_dialog.h
index 8151fff..9f282da 100644
--- a/addons/native_dialog/allegro5/internal/aintern_native_dialog.h
+++ b/addons/native_dialog/allegro5/internal/aintern_native_dialog.h
@@ -1,26 +1,45 @@
 #ifndef __al_included_allegro_aintern_native_dialog_h
 #define __al_included_allegro_aintern_native_dialog_h
 
+/* We could use different structs for the different dialogs. But why
+ * bother.
+ */
 struct ALLEGRO_NATIVE_DIALOG
 {
-   ALLEGRO_PATH *initial_path;
    ALLEGRO_USTR *title;
-   ALLEGRO_USTR *heading;
    ALLEGRO_USTR *text;
-   ALLEGRO_USTR *patterns;
-   ALLEGRO_USTR *buttons;
-   ALLEGRO_PATH **paths;
-
    int mode;
+
+   /* Only used by file chooser. */
+   ALLEGRO_PATH *initial_path;
    size_t count;
+   ALLEGRO_PATH **paths;
+   ALLEGRO_USTR *patterns;
+   
+   /* Only used by message box. */
+   ALLEGRO_USTR *heading;
+   ALLEGRO_USTR *buttons;
    int pressed_button;
+   
+   /* Only used by text log. */
+   ALLEGRO_THREAD *thread;
+   ALLEGRO_COND *text_cond;
+   ALLEGRO_MUTEX *text_mutex;
+   bool done;
+   int new_line;
+   ALLEGRO_EVENT_SOURCE events;
 
    /* Only used by platform implementations. */
    bool is_active;
    ALLEGRO_COND *cond;
+   void *window;
+   void *textview;
 };
 
 extern int _al_show_native_message_box(ALLEGRO_DISPLAY *display,
    ALLEGRO_NATIVE_DIALOG *fd);
+extern void _al_open_native_log_window(ALLEGRO_NATIVE_DIALOG *textlog);
+extern void _al_close_native_log_window(ALLEGRO_NATIVE_DIALOG *textlog);
+void _al_append_to_textlog(ALLEGRO_NATIVE_DIALOG *textlog);
 
 #endif
diff --git a/addons/native_dialog/dialog.c b/addons/native_dialog/dialog.c
index e841ad2..ab38384 100644
--- a/addons/native_dialog/dialog.c
+++ b/addons/native_dialog/dialog.c
@@ -2,6 +2,7 @@
 #include "allegro5/allegro_native_dialog.h"
 #include "allegro5/internal/aintern_native_dialog.h"
 
+#include <stdio.h>
 
 /* Function: al_create_native_file_dialog
  */
@@ -109,3 +110,102 @@ void al_show_native_file_dialog(ALLEGRO_NATIVE_DIALOG *fd)
 }
 
 #endif
+
+/* This will only return when the text window is closed. */
+static void *textlog_proc(ALLEGRO_THREAD *thread, void *arg)
+{
+   ALLEGRO_NATIVE_DIALOG *textlog = arg;
+   _al_open_native_log_window(textlog);
+   return thread;
+}
+
+/* Function: al_show_native_textlog
+ */
+ALLEGRO_NATIVE_DIALOG *al_open_native_log_window(
+   char const *title, int flags)
+{
+   ALLEGRO_NATIVE_DIALOG *textlog = al_calloc(1, sizeof *textlog);
+   textlog->title = al_ustr_new(title);
+   textlog->mode = flags;
+   textlog->thread = al_create_thread(textlog_proc, textlog);
+   textlog->text_cond = al_create_cond();
+   textlog->text_mutex = al_create_mutex();
+   al_init_user_event_source(&textlog->events);
+
+   /* Unlike the other dialogs, this one never blocks as the intended
+    * use case is a log window running in the background for debugging
+    * purposes when no console can be used. Therefore we have it run
+    * in a separate thread.
+    */
+   al_start_thread(textlog->thread);
+   al_lock_mutex(textlog->text_mutex);
+   textlog->done = false;
+   while (!textlog->done) {
+      al_wait_cond(textlog->text_cond, textlog->text_mutex);
+   }
+   al_unlock_mutex(textlog->text_mutex);
+   
+   return textlog;
+}
+
+void al_close_native_log_window(ALLEGRO_NATIVE_DIALOG *textlog)
+{
+   al_lock_mutex(textlog->text_mutex);
+   textlog->done = false;
+
+   _al_close_native_log_window(textlog);
+   
+   while (!textlog->done) {
+      al_wait_cond(textlog->text_cond, textlog->text_mutex);
+   }
+   
+   al_ustr_free(textlog->title);
+   
+   al_destroy_user_event_source(&textlog->events);
+
+   al_unlock_mutex(textlog->text_mutex);
+
+   al_destroy_thread(textlog->thread);
+   al_destroy_cond(textlog->text_cond);
+   al_destroy_mutex(textlog->text_mutex);
+   al_free(textlog);
+}
+
+void al_append_log(ALLEGRO_NATIVE_DIALOG *textlog,
+   char const *format, ...)
+{
+   al_lock_mutex(textlog->text_mutex);
+   /* TODO: If we had an al_ustr_newv which can take the format
+    * parameter and a va_list, it would be easy doing this without a
+    * fixed buffer size...
+    */
+   char text[1024];
+   va_list args;
+   va_start(args, format);
+   vsnprintf(text, sizeof text, format, args);
+   va_end(args);
+   textlog->text = al_ustr_new(text);
+
+   _al_append_to_textlog(textlog);
+   
+   /* We wait until it is appended - that way we are sure it will
+    * work correctly even if 10 threads are calling us 60 times
+    * a second.  (Of course calling us that often would be slow.)
+    */
+   textlog->done = false;
+   while (!textlog->done) {
+      al_wait_cond(textlog->text_cond, textlog->text_mutex);
+   }
+   
+   al_ustr_free(textlog->text);
+
+   al_unlock_mutex(textlog->text_mutex);
+}
+
+/* Function: al_get_audio_stream_event_source
+ */
+ALLEGRO_EVENT_SOURCE *al_get_native_dialog_event_source(
+   ALLEGRO_NATIVE_DIALOG *dialog)
+{
+   return &dialog->events;
+}
diff --git a/addons/native_dialog/gtk_dialog.c b/addons/native_dialog/gtk_dialog.c
index 6631b21..10de022 100644
--- a/addons/native_dialog/gtk_dialog.c
+++ b/addons/native_dialog/gtk_dialog.c
@@ -296,3 +296,105 @@ int _al_show_native_message_box(ALLEGRO_DISPLAY *display,
 
    return fd->pressed_button;
 }
+
+static gboolean textlog_delete(GtkWidget *w, GdkEvent *gevent,
+   gpointer userdata)
+{
+   (void)w;
+   (void)gevent;
+   ALLEGRO_NATIVE_DIALOG *textlog = userdata;
+   ALLEGRO_EVENT event;
+   event.user.type = ALLEGRO_EVENT_NATIVE_DIALOG_CLOSE;
+   event.user.timestamp = al_current_time();
+   event.user.data1 = (intptr_t)textlog;
+   al_emit_user_event(&textlog->events, &event, NULL);
+   return TRUE;
+}
+
+void _al_open_native_log_window(ALLEGRO_NATIVE_DIALOG *textlog)
+{
+   al_lock_mutex(textlog->text_mutex);
+   
+   gtk_start_and_lock();
+
+   /* Create a new text log window. */
+   GtkWidget *top = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+   gtk_window_set_default_size(GTK_WINDOW(top), 640, 480);
+   gtk_window_set_title(GTK_WINDOW(top), al_cstr(textlog->title));
+   
+   if (textlog->mode & ALLEGRO_MESSAGELOG_NO_CLOSE) {
+      gtk_window_set_deletable(GTK_WINDOW(top), false);
+   }
+   g_signal_connect(G_OBJECT(top), "delete-event", G_CALLBACK(textlog_delete), textlog);
+   g_signal_connect(G_OBJECT(top), "destroy", G_CALLBACK(destroy), textlog);
+   GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
+   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+   gtk_container_add(GTK_CONTAINER(top), scroll);
+   GtkWidget *view = gtk_text_view_new();
+   gtk_container_add(GTK_CONTAINER(scroll), view);
+   gtk_widget_show(view);
+   gtk_widget_show(scroll);
+   gtk_widget_show(top);
+   textlog->window = top;
+   textlog->textview = view;
+
+   /* Now notify al_show_native_textlog that the text log is ready. */
+   textlog->done = true;
+   al_signal_cond(textlog->text_cond);
+   al_unlock_mutex(textlog->text_mutex);
+
+   /* Keep running until the textlog is closed. */
+   gtk_unlock_and_wait(textlog);
+   
+   /* Notify everyone that we're gone. */
+   al_lock_mutex(textlog->text_mutex);
+   textlog->done = true;
+   al_signal_cond(textlog->text_cond);
+   al_unlock_mutex(textlog->text_mutex);
+   
+}
+
+static gboolean _al_append_to_textlog_called_in_gtk_thread(gpointer data)
+{
+   ALLEGRO_NATIVE_DIALOG *textlog = data;
+   al_lock_mutex(textlog->text_mutex);
+
+   GtkTextView *tv = GTK_TEXT_VIEW(textlog->textview);
+   GtkTextBuffer *buffer = gtk_text_view_get_buffer(tv);
+   GtkTextIter iter;
+   
+   gtk_text_buffer_get_end_iter(buffer, &iter);
+   gtk_text_buffer_insert(buffer, &iter, al_cstr(textlog->text), -1);
+
+   gtk_text_buffer_get_end_iter(buffer, &iter);
+   gtk_text_view_scroll_to_iter(tv, &iter, 0, false, 0, 0);
+   
+   /* Notify the original caller that we are all done. */
+   textlog->done = true;
+   al_signal_cond(textlog->text_cond);
+   al_unlock_mutex(textlog->text_mutex);
+   return false;
+}
+
+void _al_append_to_textlog(ALLEGRO_NATIVE_DIALOG *textlog)
+{
+   gdk_threads_add_timeout(0, _al_append_to_textlog_called_in_gtk_thread, textlog);
+}
+
+static gboolean _al_close_native_log_window_called_in_gtk_thread(gpointer data)
+{
+   ALLEGRO_NATIVE_DIALOG *textlog = data;
+   /* This causes the GTK window as well as all of its children to
+    * be freed. Further it will call the destroy function which we
+    * connected to the destroy signal which in turn causes our
+    * gtk thread to quit.
+    */
+   gtk_widget_destroy(textlog->window);
+   return false;
+}
+
+void _al_close_native_log_window(ALLEGRO_NATIVE_DIALOG *textlog)
+{
+   gdk_threads_add_timeout(0, _al_close_native_log_window_called_in_gtk_thread, textlog);
+}
diff --git a/docs/src/refman/native_dialog.txt b/docs/src/refman/native_dialog.txt
index b76afdf..8545a01 100644
--- a/docs/src/refman/native_dialog.txt
+++ b/docs/src/refman/native_dialog.txt
@@ -121,3 +121,34 @@ Example:
 
 Returns the (compiled) version of the addon, in the same format as
 [al_get_allegro_version].
+
+## API: al_open_native_log_window
+
+Shows a text window to which you can append log messages with
+[al_append_log]. This can be useful for debugging if you don't want
+to depend on a console being available.
+
+Use [al_close_native_log_window] to close the window again.
+
+The only flag so far is ALLEGRO_MESSAGELOG_NO_CLOSE - if will prevent
+the window from having a close button. Otherwise if the close button
+is pressed an event is generated, see
+
+## API: al_close_native_log_window
+
+Closes a message log window opened with [al_open_native_log_window]
+earlier.
+
+## API: al_append_log
+
+Appends a line of text to the message log window and scrolls to the
+bottom (if the line would not be visible otherwise). This works like
+printf. A line is continued until you add a newline character.
+
+## API: al_get_native_dialog_event_source
+
+Get an event source for a native window. The possible events are:
+
+ALLEGRO_EVENT_NATIVE_DIALOG_CLOSE
+  ~ The close button of the window has been pressed. Only supported
+    for windows created with [al_open_native_log_window].
diff --git a/examples/ex_native_filechooser.c b/examples/ex_native_filechooser.c
index 494170c..7e81da2 100644
--- a/examples/ex_native_filechooser.c
+++ b/examples/ex_native_filechooser.c
@@ -28,6 +28,31 @@ typedef struct
    ALLEGRO_THREAD *thread;
 } AsyncDialog;
 
+ALLEGRO_NATIVE_DIALOG *textlog;
+
+static void message(char const *format, ...)
+{
+   if (!textlog) return;
+   char str[1024];
+   va_list args;
+   va_start(args, format);
+   vsnprintf(str, sizeof str, format, args);
+   va_end(args);
+   
+   al_append_log(textlog, "%s\n", str);
+}
+
+static void message_cont(char const *format, ...)
+{
+   if (!textlog) return;
+   char str[1024];
+   va_list args;
+   va_start(args, format);
+   vsnprintf(str, sizeof str, format, args);
+   va_end(args);
+   
+   al_append_log(textlog, "%s", str);
+}
 
 /* Our thread to show the native file dialog. */
 static void *async_file_dialog_thread_func(ALLEGRO_THREAD *thread, void *arg)
@@ -155,9 +180,15 @@ int main(void)
    AsyncDialog *cur_dialog = NULL;
    AsyncDialog *message_box = NULL;
    bool redraw = false;
+   bool close_log = false;
    int button;
+   bool message_log = true;
 
    al_init();
+   
+   textlog = al_open_native_log_window("Log", 0);
+   message("Starting up log window.");
+   
    al_init_image_addon();
    al_init_font_addon();
 
@@ -169,28 +200,39 @@ int main(void)
    al_install_mouse();
    al_install_keyboard();
 
+   message_cont("Creating 640x480 window...");
+
    display = al_create_display(640, 480);
    if (!display) {
+      message("failure.");
       abort_example("Error creating display\n");
       return 1;
    }
+   message("success.");
 
+   message_cont("Loading font '%s'...", "data/fixed_font.tga");
    font = al_load_font("data/fixed_font.tga", 0, 0);
    if (!font) {
+      message("failure.");
       abort_example("Error loading data/fixed_font.tga\n");
       return 1;
    }
+   message("success.");
 
    timer = al_install_timer(1.0 / 30);
 restart:
+   message("Starting main loop.");
    queue = al_create_event_queue();
    al_register_event_source(queue, al_get_keyboard_event_source());
    al_register_event_source(queue, al_get_mouse_event_source());
    al_register_event_source(queue, al_get_display_event_source(display));
    al_register_event_source(queue, al_get_timer_event_source(timer));
+   al_register_event_source(queue, al_get_native_dialog_event_source(
+      textlog));
    al_start_timer(timer);
 
    while (1) {
+      float h = al_get_display_height(display);
       ALLEGRO_EVENT event;
       al_wait_for_event(queue, &event);
 
@@ -206,8 +248,20 @@ restart:
        * shown already, we show a new one.
        */
       if (event.type == ALLEGRO_EVENT_MOUSE_BUTTON_DOWN) {
+          message("Mouse clicked at %d,%d.", event.mouse.x, event.mouse.y);
           if (event.mouse.y > 30) {
-             if (!message_box) {
+             if (event.mouse.y > h - 30) {
+                message_log = !message_log;
+                if (message_log) {
+                   textlog = al_open_native_log_window("Log", 0);
+                   al_register_event_source(queue,
+                     al_get_native_dialog_event_source(textlog));
+                }
+                else {
+                   close_log = true;
+                }
+             }
+             else if (!message_box) {
                 message_box = spawn_async_message_dialog(display);
                 al_register_event_source(queue, &message_box->event_source);
              }
@@ -249,6 +303,10 @@ restart:
          stop_async_dialog(message_box);
          message_box = NULL;
       }
+      
+      if (event.type == ALLEGRO_EVENT_NATIVE_DIALOG_CLOSE) {
+         close_log = true;
+      }
 
       if (event.type == ALLEGRO_EVENT_TIMER) {
          redraw = true;
@@ -261,11 +319,24 @@ restart:
          al_clear_to_color(background);
          al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);
          al_draw_textf(font, cur_dialog ? inactive : active, x, y, ALLEGRO_ALIGN_CENTRE, "Open");
+         al_draw_textf(font, cur_dialog ? inactive : active, x, h - 30,
+            ALLEGRO_ALIGN_CENTRE, message_log ? "Close Message Log" : "Open Message Log");
          if (old_dialog)
             show_files_list(old_dialog->file_dialog, font, info);
          al_flip_display();
       }
+      
+      if (close_log && textlog) {
+         close_log = false;
+         message_log = false;
+         al_unregister_event_source(queue,
+            al_get_native_dialog_event_source(textlog));
+         al_close_native_log_window(textlog);
+         textlog = NULL;
+      }
    }
+   
+   message("Exiting.");
 
    al_destroy_event_queue(queue);
 


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/