Added first support for CSS files.
[projects/chimara/chimara.git] / libchimara / chimara-glk.c
1 /* licensing and copyright information here */
2
3 #include <math.h>
4 #include <gtk/gtk.h>
5 #include <config.h>
6 #include <glib/gi18n-lib.h>
7 #include <gmodule.h>
8 #include <pango/pango.h>
9 #include "chimara-glk.h"
10 #include "chimara-glk-private.h"
11 #include "glk.h"
12 #include "abort.h"
13 #include "window.h"
14 #include "glkstart.h"
15 #include "glkunix.h"
16 #include "init.h"
17
18 #define CHIMARA_GLK_MIN_WIDTH 0
19 #define CHIMARA_GLK_MIN_HEIGHT 0
20
21 /**
22  * SECTION:chimara-glk
23  * @short_description: Widget which executes a Glk program
24  * @stability: Unstable
25  * @include: chimara/chimara-glk.h
26  * 
27  * The ChimaraGlk widget opens and runs a Glk program. The program must be
28  * compiled as a plugin module, with a function <function>glk_main()</function>
29  * that the Glk library can hook into.
30  *
31  * On Linux systems, this is a file with a name like 
32  * <filename>plugin.so</filename>. For portability, you can use libtool and 
33  * automake:
34  * |[
35  * pkglib_LTLIBRARIES = plugin.la
36  * plugin_la_SOURCES = plugin.c foo.c bar.c
37  * plugin_la_LDFLAGS = -module -shared -avoid-version -export-symbols-regex "^glk_main$$"
38  * ]|
39  * This will produce <filename>plugin.la</filename> which is a text file 
40  * containing the correct plugin file to open (see the relevant section of the
41  * <ulink 
42  * url="http://www.gnu.org/software/libtool/manual/html_node/Finding-the-dlname.html">
43  * Libtool manual</ulink>).
44  */
45
46 typedef void (* glk_main_t) (void);
47 typedef int (* glkunix_startup_code_t) (glkunix_startup_t*);
48
49 enum {
50     PROP_0,
51     PROP_INTERACTIVE,
52     PROP_PROTECT,
53         PROP_DEFAULT_FONT_DESCRIPTION,
54         PROP_MONOSPACE_FONT_DESCRIPTION,
55         PROP_SPACING
56 };
57
58 enum {
59         STOPPED,
60         STARTED,
61
62         LAST_SIGNAL
63 };
64
65 static guint chimara_glk_signals[LAST_SIGNAL] = { 0 };
66
67 G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER);
68
69 static void
70 chimara_glk_init(ChimaraGlk *self)
71 {
72     GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW);
73
74     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
75     
76     priv->self = self;
77     priv->interactive = TRUE;
78     priv->protect = FALSE;
79         priv->default_font_desc = pango_font_description_from_string("Sans");
80         priv->monospace_font_desc = pango_font_description_from_string("Monospace");
81         priv->css_file = "style.css";
82         priv->default_styles = g_hash_table_new(g_str_hash, g_str_equal);
83     priv->program = NULL;
84     priv->thread = NULL;
85     priv->event_queue = NULL;
86     priv->event_lock = NULL;
87     priv->event_queue_not_empty = NULL;
88     priv->event_queue_not_full = NULL;
89     priv->abort_lock = NULL;
90     priv->abort_signalled = FALSE;
91         priv->arrange_lock = NULL;
92         priv->rearranged = NULL;
93         priv->needs_rearrange = FALSE;
94         priv->ignore_next_arrange_event = FALSE;
95     priv->interrupt_handler = NULL;
96     priv->root_window = NULL;
97     priv->fileref_list = NULL;
98     priv->current_stream = NULL;
99     priv->stream_list = NULL;
100         priv->timer_id = 0;
101         priv->in_startup = FALSE;
102         priv->current_dir = NULL;
103 }
104
105 static void
106 chimara_glk_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
107 {
108     ChimaraGlk *glk = CHIMARA_GLK(object);
109     
110     switch(prop_id) 
111     {
112         case PROP_INTERACTIVE:
113             chimara_glk_set_interactive( glk, g_value_get_boolean(value) );
114             break;
115         case PROP_PROTECT:
116             chimara_glk_set_protect( glk, g_value_get_boolean(value) );
117             break;
118                 case PROP_DEFAULT_FONT_DESCRIPTION:
119                         chimara_glk_set_default_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) );
120                         break;
121                 case PROP_MONOSPACE_FONT_DESCRIPTION:
122                         chimara_glk_set_monospace_font_description( glk, (PangoFontDescription *)g_value_get_pointer(value) );
123                         break;
124                 case PROP_SPACING:
125                         chimara_glk_set_spacing( glk, g_value_get_uint(value) );
126                         break;
127         default:
128             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
129     }
130 }
131
132 static void
133 chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
134 {
135     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(object);
136     
137     switch(prop_id)
138     {
139         case PROP_INTERACTIVE:
140             g_value_set_boolean(value, priv->interactive);
141             break;
142         case PROP_PROTECT:
143             g_value_set_boolean(value, priv->protect);
144             break;
145                 case PROP_DEFAULT_FONT_DESCRIPTION:
146                         g_value_set_pointer(value, priv->default_font_desc);
147                         break;
148                 case PROP_MONOSPACE_FONT_DESCRIPTION:
149                         g_value_set_pointer(value, priv->monospace_font_desc);
150                         break;
151                 case PROP_SPACING:
152                         g_value_set_uint(value, priv->spacing);
153                         break;
154         default:
155             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
156     }
157 }
158
159 static void
160 chimara_glk_finalize(GObject *object)
161 {
162     ChimaraGlk *self = CHIMARA_GLK(object);
163     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
164     
165     /* Free the event queue */
166     g_mutex_lock(priv->event_lock);
167         g_queue_foreach(priv->event_queue, (GFunc)g_free, NULL);
168         g_queue_free(priv->event_queue);
169         g_cond_free(priv->event_queue_not_empty);
170         g_cond_free(priv->event_queue_not_full);
171         priv->event_queue = NULL;
172         g_mutex_unlock(priv->event_lock);
173         g_mutex_free(priv->event_lock);
174         
175         /* Free the abort signalling mechanism */
176         g_mutex_lock(priv->abort_lock);
177         /* Make sure no other thread is busy with this */
178         g_mutex_unlock(priv->abort_lock);
179         g_mutex_free(priv->abort_lock);
180         priv->abort_lock = NULL;
181
182         /* Free the window arrangement signalling */
183         g_mutex_lock(priv->arrange_lock);
184         g_cond_free(priv->rearranged);
185         g_mutex_unlock(priv->arrange_lock);
186         g_mutex_free(priv->arrange_lock);
187         priv->arrange_lock = NULL;
188         
189         /* Free private data */
190         pango_font_description_free(priv->default_font_desc);
191         pango_font_description_free(priv->monospace_font_desc);
192         g_free(priv->current_dir);
193         g_hash_table_destroy(priv->default_styles);
194         
195     G_OBJECT_CLASS(chimara_glk_parent_class)->finalize(object);
196 }
197
198 /* Internal function: Recursively get the Glk window tree's size request */
199 static void
200 request_recurse(winid_t win, GtkRequisition *requisition, guint spacing)
201 {
202         if(win->type == wintype_Pair)
203         {
204                 /* Get children's size requests */
205                 GtkRequisition child1, child2;
206                 request_recurse(win->window_node->children->data, &child1, spacing);
207                 request_recurse(win->window_node->children->next->data, &child2, spacing);
208
209                 glui32 division = win->split_method & winmethod_DivisionMask;
210                 glui32 direction = win->split_method & winmethod_DirMask;
211                 
212                 /* If the split is fixed, get the size of the fixed child */
213                 if(division == winmethod_Fixed)
214                 {
215                         switch(direction)
216                         {
217                                 case winmethod_Left:
218                                         child1.width = win->key_window?
219                                                 win->constraint_size * win->key_window->unit_width
220                                                 : 0;
221                                         break;
222                                 case winmethod_Right:
223                                         child2.width = win->key_window?
224                                                 win->constraint_size * win->key_window->unit_width
225                                                 : 0;
226                                         break;
227                                 case winmethod_Above:
228                                         child1.height = win->key_window?
229                                                 win->constraint_size * win->key_window->unit_height
230                                                 : 0;
231                                         break;
232                                 case winmethod_Below:
233                                         child2.height = win->key_window?
234                                                 win->constraint_size * win->key_window->unit_height
235                                                 : 0;
236                                         break;
237                         }
238                 }
239                 
240                 /* Add the children's requests */
241                 switch(direction)
242                 {
243                         case winmethod_Left:
244                         case winmethod_Right:
245                                 requisition->width = child1.width + child2.width + spacing;
246                                 requisition->height = MAX(child1.height, child2.height);
247                                 break;
248                         case winmethod_Above:
249                         case winmethod_Below:
250                                 requisition->width = MAX(child1.width, child2.width);
251                                 requisition->height = child1.height + child2.height + spacing;
252                                 break;
253                 }
254         }
255         
256         /* For non-pair windows, just use the size that GTK requests */
257         else
258                 gtk_widget_size_request(win->frame, requisition);
259 }
260
261 /* Overrides gtk_widget_size_request */
262 static void
263 chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition)
264 {
265     g_return_if_fail(widget);
266     g_return_if_fail(requisition);
267     g_return_if_fail(CHIMARA_IS_GLK(widget));
268     
269     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
270     
271     /* For now, just pass the size request on to the root Glk window */
272     if(priv->root_window) 
273         {
274                 request_recurse(priv->root_window->data, requisition, priv->spacing);
275                 requisition->width += 2 * GTK_CONTAINER(widget)->border_width;
276                 requisition->height += 2 * GTK_CONTAINER(widget)->border_width;
277         } 
278         else 
279         {
280         requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * GTK_CONTAINER(widget)->border_width;
281         requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * GTK_CONTAINER(widget)->border_width;
282     }
283 }
284
285 /* Recursively give the Glk windows their allocated space. Returns a window
286  containing all children of this window that must be redrawn, or NULL if there 
287  are no children that require redrawing. */
288 static winid_t
289 allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing)
290 {
291         if(win->type == wintype_Pair)
292         {
293                 glui32 division = win->split_method & winmethod_DivisionMask;
294                 glui32 direction = win->split_method & winmethod_DirMask;
295
296                 /* If the space gets too small to honor the spacing property, then just 
297                  ignore spacing in this window and below. */
298                 if( (spacing > allocation->width && (direction == winmethod_Left || direction == winmethod_Right))
299                    || (spacing > allocation->height && (direction == winmethod_Above || direction == winmethod_Below)) )
300                         spacing = 0;
301                 
302                 GtkAllocation child1, child2;
303                 child1.x = allocation->x;
304                 child1.y = allocation->y;
305                 
306                 if(division == winmethod_Fixed)
307                 {
308                         /* If the key window has been closed, then default to 0; otherwise
309                          use the key window to determine the size */
310                         switch(direction)
311                         {
312                                 case winmethod_Left:
313                                         child1.width = win->key_window? 
314                                                 CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing) 
315                                                 : 0;
316                                         break;
317                                 case winmethod_Right:
318                                         child2.width = win->key_window? 
319                                                 CLAMP(win->constraint_size * win->key_window->unit_width, 0, allocation->width - spacing)
320                                                 : 0;
321                                         break;
322                                 case winmethod_Above:
323                                         child1.height = win->key_window? 
324                                                 CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing)
325                                                 : 0;
326                                         break;
327                                 case winmethod_Below:
328                                         child2.height = win->key_window?
329                                                 CLAMP(win->constraint_size * win->key_window->unit_height, 0, allocation->height - spacing)
330                                                 : 0;
331                                         break;
332                         }
333                 }
334                 else /* proportional */
335                 {
336                         gdouble fraction = win->constraint_size / 100.0;
337                         switch(direction)
338                         {
339                                 case winmethod_Left:
340                                         child1.width = MAX(0, (gint)ceil(fraction * (allocation->width - spacing)) );
341                                         break;
342                                 case winmethod_Right:
343                                         child2.width = MAX(0, (gint)ceil(fraction * (allocation->width - spacing)) );
344                                         break;
345                                 case winmethod_Above:
346                                         child1.height = MAX(0, (gint)ceil(fraction * (allocation->height - spacing)) );
347                                         break;
348                                 case winmethod_Below:
349                                         child2.height = MAX(0, (gint)ceil(fraction * (allocation->height - spacing)) );
350                                         break;
351                         }
352                 }
353                 
354                 /* Fill in the rest of the size requisitions according to the child specified above */
355                 switch(direction)
356                 {
357                         case winmethod_Left:
358                                 child2.width = MAX(0, allocation->width - spacing - child1.width);
359                                 child2.x = child1.x + child1.width + spacing;
360                                 child2.y = child1.y;
361                                 child1.height = child2.height = allocation->height;
362                                 break;
363                         case winmethod_Right:
364                                 child1.width = MAX(0, allocation->width - spacing - child2.width);
365                                 child2.x = child1.x + child1.width + spacing;
366                                 child2.y = child1.y;
367                                 child1.height = child2.height = allocation->height;
368                                 break;
369                         case winmethod_Above:
370                                 child2.height = MAX(0, allocation->height - spacing - child1.height);
371                                 child2.x = child1.x;
372                                 child2.y = child1.y + child1.height + spacing;
373                                 child1.width = child2.width = allocation->width;
374                                 break;
375                         case winmethod_Below:
376                                 child1.height = MAX(0, allocation->height - spacing - child2.height);
377                                 child2.x = child1.x;
378                                 child2.y = child1.y + child1.height + spacing;
379                                 child1.width = child2.width = allocation->width;
380                                 break;
381                 }
382                 
383                 /* Recurse */
384                 winid_t arrange1 = allocate_recurse(win->window_node->children->data, &child1, spacing);
385                 winid_t arrange2 = allocate_recurse(win->window_node->children->next->data, &child2, spacing);
386                 if(arrange1 == NULL)
387                         return arrange2;
388                 if(arrange2 == NULL)
389                         return arrange1;
390                 return win;
391         }
392         
393         else if(win->type == wintype_TextGrid)
394         {
395                 /* Pass the size allocation on to the framing widget */
396                 gtk_widget_size_allocate(win->frame, allocation);
397                 /* It says in the spec that when a text grid window is resized smaller,
398                  the bottom or right area is thrown away; when it is resized larger, the
399                  bottom or right area is filled with blanks. */
400                 glui32 newwidth = (glui32)(win->widget->allocation.width / win->unit_width);
401                 glui32 newheight = (glui32)(win->widget->allocation.height / win->unit_height);
402                 gint line;
403                 GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) );
404                 GtkTextIter start, end;
405         
406                 for(line = 0; line < win->height; line++)
407                 {
408                         gtk_text_buffer_get_iter_at_line(textbuffer, &start, line);
409                         /* If this line is going to fall off the bottom, delete it */
410                         if(line >= newheight)
411                         {
412                                 end = start;
413                                 gtk_text_iter_forward_to_line_end(&end);
414                                 gtk_text_iter_forward_char(&end);
415                                 gtk_text_buffer_delete(textbuffer, &start, &end);
416                                 break;
417                         }
418                         /* If this line is not long enough, add spaces on the end */
419                         if(newwidth > win->width)
420                         {
421                                 gchar *spaces = g_strnfill(newwidth - win->width, ' ');
422                                 gtk_text_iter_forward_to_line_end(&start);
423                                 gtk_text_buffer_insert(textbuffer, &start, spaces, -1);
424                                 g_free(spaces);
425                         }
426                         /* But if it's too long, delete characters from the end */
427                         else if(newwidth < win->width)
428                         {
429                                 end = start;
430                                 gtk_text_iter_forward_chars(&start, newwidth);
431                                 gtk_text_iter_forward_to_line_end(&end);
432                                 gtk_text_buffer_delete(textbuffer, &start, &end);
433                         }
434                         /* Note: if the widths are equal, do nothing */
435                 }
436                 /* Add blank lines if there aren't enough lines to fit the new size */
437                 if(newheight > win->height)
438                 {
439                         gchar *blanks = g_strnfill(win->width, ' ');
440                     gchar **blanklines = g_new0(gchar *, (newheight - win->height) + 1);
441                     int count;
442                     for(count = 0; count < newheight - win->height; count++)
443                         blanklines[count] = blanks;
444                     blanklines[newheight - win->height] = NULL;
445                     gchar *text = g_strjoinv("\n", blanklines);
446                     g_free(blanklines); /* not g_strfreev() */
447                     g_free(blanks);
448                     
449                         gtk_text_buffer_get_end_iter(textbuffer, &start);
450                         gtk_text_buffer_insert(textbuffer, &start, "\n", -1);
451                     gtk_text_buffer_insert(textbuffer, &start, text, -1);
452                     g_free(text);
453                 }
454         
455                 gboolean arrange = !(win->width == newwidth && win->height == newheight);
456                 win->width = newwidth;
457                 win->height = newheight;
458                 return arrange? win : NULL;
459         }
460         
461         /* For non-pair, non-text-grid windows, just give them the size */
462         gtk_widget_size_allocate(win->frame, allocation);
463         return NULL;
464 }
465
466 /* Overrides gtk_widget_size_allocate */
467 static void
468 chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
469 {
470     g_return_if_fail(widget);
471     g_return_if_fail(allocation);
472     g_return_if_fail(CHIMARA_IS_GLK(widget));
473     
474     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget);
475     
476     widget->allocation = *allocation;
477             
478     if(priv->root_window) {
479                 GtkAllocation child;
480                 child.x = allocation->x + GTK_CONTAINER(widget)->border_width;
481                 child.y = allocation->y + GTK_CONTAINER(widget)->border_width;
482                 child.width = CLAMP(allocation->width - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->width);
483                 child.height = CLAMP(allocation->height - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->height);
484                 winid_t arrange = allocate_recurse(priv->root_window->data, &child, priv->spacing);
485                 
486                 /* arrange points to a window that contains all text grid and graphics
487                  windows which have been resized */
488                 g_mutex_lock(priv->arrange_lock);
489                 if(!priv->ignore_next_arrange_event)
490                 {
491                         if(arrange)
492                                 event_throw(CHIMARA_GLK(widget), evtype_Arrange, arrange == priv->root_window->data? NULL : arrange, 0, 0);
493                 }
494                 else
495                         priv->ignore_next_arrange_event = FALSE;
496                 priv->needs_rearrange = FALSE;
497                 g_cond_signal(priv->rearranged);
498                 g_mutex_unlock(priv->arrange_lock);
499         }
500 }
501
502 /* Recursively invoke callback() on the GtkWidget of each non-pair window in the tree */
503 static void
504 forall_recurse(winid_t win, GtkCallback callback, gpointer callback_data)
505 {
506         if(win->type == wintype_Pair)
507         {
508                 forall_recurse(win->window_node->children->data, callback, callback_data);
509                 forall_recurse(win->window_node->children->next->data, callback, callback_data);
510         }
511         else
512                 (*callback)(win->frame, callback_data);
513 }
514
515 /* Overrides gtk_container_forall */
516 static void
517 chimara_glk_forall(GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data)
518 {
519     g_return_if_fail(container);
520     g_return_if_fail(CHIMARA_IS_GLK(container));
521     
522     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(container);
523     
524         /* All the children are "internal" */
525         if(!include_internals)
526                 return;
527         
528     if(priv->root_window)
529                 forall_recurse(priv->root_window->data, callback, callback_data);
530 }
531
532 static void
533 chimara_glk_stopped(ChimaraGlk *self)
534 {
535     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
536
537     /* Free the plugin */
538         if( priv->program && !g_module_close(priv->program) )
539             g_warning( "Error closing module: %s", g_module_error() );
540 }
541
542 static void
543 chimara_glk_started(ChimaraGlk *self)
544 {
545         /* TODO: Add default signal handler implementation here */
546 }
547
548 /* G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */
549 #ifndef G_PARAM_STATIC_STRINGS
550 #define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB)
551 #endif
552
553 static void
554 chimara_glk_class_init(ChimaraGlkClass *klass)
555 {
556     /* Override methods of parent classes */
557     GObjectClass *object_class = G_OBJECT_CLASS(klass);
558     object_class->set_property = chimara_glk_set_property;
559     object_class->get_property = chimara_glk_get_property;
560     object_class->finalize = chimara_glk_finalize;
561     
562     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
563     widget_class->size_request = chimara_glk_size_request;
564     widget_class->size_allocate = chimara_glk_size_allocate;
565
566     GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
567     container_class->forall = chimara_glk_forall;
568
569     /* Signals */
570     klass->stopped = chimara_glk_stopped;
571     klass->started = chimara_glk_started;
572     /**
573      * ChimaraGlk::stopped:
574      * @glk: The widget that received the signal
575      *
576      * The ::stopped signal is emitted when the a Glk program finishes
577      * executing in the widget, whether it ended normally, or was interrupted.
578      */ 
579     chimara_glk_signals[STOPPED] = g_signal_new("stopped", 
580         G_OBJECT_CLASS_TYPE(klass), 0, 
581         G_STRUCT_OFFSET(ChimaraGlkClass, stopped), NULL, NULL,
582                 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
583         /**
584          * ChimaraGlk::started:
585          * @glk: The widget that received the signal
586          *
587          * The ::started signal is emitted when a Glk program starts executing in
588          * the widget.
589          */
590         chimara_glk_signals[STARTED] = g_signal_new ("started",
591                 G_OBJECT_CLASS_TYPE (klass), 0,
592                 G_STRUCT_OFFSET(ChimaraGlkClass, started), NULL, NULL,
593                 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
594
595     /* Properties */
596     /**
597      * ChimaraGlk:interactive:
598      *
599      * Sets whether the widget is interactive. A Glk widget is normally 
600      * interactive, but in non-interactive mode, keyboard and mouse input are 
601      * ignored and the Glk program is controlled by chimara_glk_feed_text(). 
602      * <quote>More</quote> prompts when a lot of text is printed to a text 
603          * buffer are also disabled. This is typically used when you wish to control
604          * an interpreter program by feeding it a predefined list of commands.
605      */
606     g_object_class_install_property( object_class, PROP_INTERACTIVE, 
607                 g_param_spec_boolean("interactive", _("Interactive"),
608         _("Whether user input is expected in the Glk program"),
609         TRUE,
610         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
611
612         /**
613      * ChimaraGlk:protect:
614      *
615      * Sets whether the Glk program is allowed to do file operations. In protect
616      * mode, all file operations will fail.
617      */
618     g_object_class_install_property(object_class, PROP_PROTECT, 
619                 g_param_spec_boolean("protect", _("Protected"),
620         _("Whether the Glk program is barred from doing file operations"),
621         FALSE,
622         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
623
624         /* We can't use G_PARAM_CONSTRUCT on these because then the constructor will
625          initialize them with NULL */
626         /**
627          * ChimaraGlk:default-font-description:
628          * 
629          * Pointer to a #PangoFontDescription describing the default proportional 
630          * font, to be used in text buffer windows for example.
631          *
632          * Default value: font description created from the string 
633          * <quote>Sans</quote>
634          */
635         g_object_class_install_property(object_class, PROP_DEFAULT_FONT_DESCRIPTION, 
636                 g_param_spec_pointer("default-font-description", _("Default Font"),
637                 _("Font description of the default proportional font"),
638                 G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
639
640         /**
641          * ChimaraGlk:monospace-font-description:
642          *
643          * Pointer to a #PangoFontDescription describing the default monospace font,
644          * to be used in text grid windows and %style_Preformatted, for example.
645          *
646          * Default value: font description created from the string 
647          * <quote>Monospace</quote>
648          */
649         g_object_class_install_property(object_class, PROP_MONOSPACE_FONT_DESCRIPTION, 
650                 g_param_spec_pointer("monospace-font-description", _("Monospace Font"),
651                 _("Font description of the default monospace font"),
652                 G_PARAM_READWRITE | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
653
654         /**
655          * ChimaraGlk:spacing:
656          *
657          * The amount of space between the Glk windows.
658          */
659         g_object_class_install_property(object_class, PROP_SPACING,
660                 g_param_spec_uint("spacing", _("Spacing"),
661                 _("The amount of space between Glk windows"),
662                 0, G_MAXUINT, 0,
663                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS) );
664         
665     /* Private data */
666     g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate));
667 }
668
669 /* PUBLIC FUNCTIONS */
670
671 /**
672  * chimara_glk_new:
673  *
674  * Creates and initializes a new #ChimaraGlk widget.
675  *
676  * Return value: a #ChimaraGlk widget, with a floating reference.
677  */
678 GtkWidget *
679 chimara_glk_new(void)
680 {
681         /* This is a library entry point; initialize the library */
682         chimara_init();
683
684     ChimaraGlk *self = CHIMARA_GLK(g_object_new(CHIMARA_TYPE_GLK, NULL));
685     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self);
686     
687     priv->event_queue = g_queue_new();
688     priv->event_lock = g_mutex_new();
689     priv->event_queue_not_empty = g_cond_new();
690     priv->event_queue_not_full = g_cond_new();
691     priv->abort_lock = g_mutex_new();
692         priv->arrange_lock = g_mutex_new();
693         priv->rearranged = g_cond_new();
694
695     return GTK_WIDGET(self);
696 }
697
698 /**
699  * chimara_glk_set_interactive:
700  * @glk: a #ChimaraGlk widget
701  * @interactive: whether the widget should expect user input
702  *
703  * Sets the #ChimaraGlk:interactive property of @glk. 
704  */
705 void 
706 chimara_glk_set_interactive(ChimaraGlk *glk, gboolean interactive)
707 {
708     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
709     
710     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
711     priv->interactive = interactive;
712 }
713
714 /**
715  * chimara_glk_get_interactive:
716  * @glk: a #ChimaraGlk widget
717  *
718  * Returns whether @glk is interactive (expecting user input). See 
719  * #ChimaraGlk:interactive.
720  *
721  * Return value: %TRUE if @glk is interactive.
722  */
723 gboolean 
724 chimara_glk_get_interactive(ChimaraGlk *glk)
725 {
726     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
727     
728     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
729     return priv->interactive;
730 }
731
732 /**
733  * chimara_glk_set_protect:
734  * @glk: a #ChimaraGlk widget
735  * @protect: whether the widget should allow the Glk program to do file 
736  * operations
737  *
738  * Sets the #ChimaraGlk:protect property of @glk. In protect mode, the Glk 
739  * program is not allowed to do file operations.
740  */
741 void 
742 chimara_glk_set_protect(ChimaraGlk *glk, gboolean protect)
743 {
744     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
745     
746     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
747     priv->protect = protect;
748 }
749
750 /**
751  * chimara_glk_get_protect:
752  * @glk: a #ChimaraGlk widget
753  *
754  * Returns whether @glk is in protect mode (banned from doing file operations).
755  * See #ChimaraGlk:protect.
756  *
757  * Return value: %TRUE if @glk is in protect mode.
758  */
759 gboolean 
760 chimara_glk_get_protect(ChimaraGlk *glk)
761 {
762     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
763     
764     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
765     return priv->protect;
766 }
767
768 /**
769  * chimara_glk_set_default_font_description:
770  * @glk: a #ChimaraGlk widget
771  * @font: a #PangoFontDescription
772  *
773  * Sets @glk's default proportional font. See 
774  * #ChimaraGlk:default-font-description.
775  */
776 void 
777 chimara_glk_set_default_font_description(ChimaraGlk *glk, PangoFontDescription *font)
778 {
779         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
780         g_return_if_fail(font);
781         
782         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
783         pango_font_description_free(priv->default_font_desc);
784         priv->default_font_desc = pango_font_description_copy(font);
785         
786         /* TODO: Apply the font description to all the windows and recalculate the sizes */
787 }
788
789 /**
790  * chimara_glk_set_default_font_string:
791  * @glk: a #ChimaraGlk widget
792  * @font: string representation of a font description
793  *
794  * Sets @glk's default proportional font according to the string @font, which
795  * must be a string in the form <quote><replaceable>FAMILY-LIST</replaceable> 
796  * [<replaceable>STYLE-OPTIONS</replaceable>] 
797  * [<replaceable>SIZE</replaceable>]</quote>, such as <quote>Charter,Utopia 
798  * Italic 12</quote> or <quote>Sans</quote>. See 
799  * #ChimaraGlk:default-font-description.
800  */
801 void 
802 chimara_glk_set_default_font_string(ChimaraGlk *glk, const gchar *font)
803 {
804         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
805         g_return_if_fail(font || *font);
806         
807         PangoFontDescription *fontdesc = pango_font_description_from_string(font);
808         g_return_if_fail(fontdesc);
809         
810         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
811         pango_font_description_free(priv->default_font_desc);
812         priv->default_font_desc = fontdesc;
813         
814         /* TODO: Apply the font description to all the windows and recalculate the sizes */
815 }
816         
817 /**
818  * chimara_glk_get_default_font_description:
819  * @glk: a #ChimaraGlk widget
820  * 
821  * Returns @glk's default proportional font.
822  *
823  * Return value: a newly-allocated #PangoFontDescription which must be freed
824  * using pango_font_description_free(), or %NULL on error.
825  */
826 PangoFontDescription *
827 chimara_glk_get_default_font_description(ChimaraGlk *glk)
828 {
829         g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL);
830         
831         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
832         return pango_font_description_copy(priv->default_font_desc);
833 }
834
835 /**
836  * chimara_glk_set_monospace_font_description:
837  * @glk: a #ChimaraGlk widget
838  * @font: a #PangoFontDescription
839  *
840  * Sets @glk's default monospace font. See 
841  * #ChimaraGlk:monospace-font-description.
842  */
843 void 
844 chimara_glk_set_monospace_font_description(ChimaraGlk *glk, PangoFontDescription *font)
845 {
846         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
847         g_return_if_fail(font);
848         
849         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
850         pango_font_description_free(priv->monospace_font_desc);
851         priv->monospace_font_desc = pango_font_description_copy(font);
852         
853         /* TODO: Apply the font description to all the windows and recalculate the sizes */
854 }
855
856 /**
857  * chimara_glk_set_monospace_font_string:
858  * @glk: a #ChimaraGlk widget
859  * @font: string representation of a font description
860  *
861  * Sets @glk's default monospace font according to the string @font, which must
862  * be a string in the form <quote><replaceable>FAMILY-LIST</replaceable> 
863  * [<replaceable>STYLE-OPTIONS</replaceable>] 
864  * [<replaceable>SIZE</replaceable>]</quote>, such as <quote>Courier 
865  * Bold 12</quote> or <quote>Monospace</quote>. See 
866  * #ChimaraGlk:monospace-font-description.
867  */
868 void 
869 chimara_glk_set_monospace_font_string(ChimaraGlk *glk, const gchar *font)
870 {
871         g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
872         g_return_if_fail(font || *font);
873         
874         PangoFontDescription *fontdesc = pango_font_description_from_string(font);
875         g_return_if_fail(fontdesc);
876         
877         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
878         pango_font_description_free(priv->monospace_font_desc);
879         priv->monospace_font_desc = fontdesc;
880         
881         /* TODO: Apply the font description to all the windows and recalculate the sizes */
882 }
883         
884 /**
885  * chimara_glk_get_monospace_font_description:
886  * @glk: a #ChimaraGlk widget
887  * 
888  * Returns @glk's default monospace font.
889  *
890  * Return value: a newly-allocated #PangoFontDescription which must be freed
891  * using pango_font_description_free(), or %NULL on error.
892  */
893 PangoFontDescription *
894 chimara_glk_get_monospace_font_description(ChimaraGlk *glk)
895 {
896         g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), NULL);
897         
898         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
899         return pango_font_description_copy(priv->monospace_font_desc);
900 }
901
902 /**
903  * chimara_glk_set_spacing:
904  * @glk: a #ChimaraGlk widget
905  * @spacing: the number of pixels to put between Glk windows
906  *
907  * Sets the #ChimaraGlk:spacing property of @glk, which is the border width in
908  * pixels between Glk windows.
909  */
910 void 
911 chimara_glk_set_spacing(ChimaraGlk *glk, guint spacing)
912 {
913         g_return_if_fail( glk || CHIMARA_IS_GLK(glk) );
914         
915         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
916         priv->spacing = spacing;
917 }
918
919 /**
920  * chimara_glk_get_spacing:
921  * @glk: a #ChimaraGlk widget
922  *
923  * Gets the value set by chimara_glk_set_spacing().
924  *
925  * Return value: pixels of spacing between Glk windows
926  */
927 guint 
928 chimara_glk_get_spacing(ChimaraGlk *glk)
929 {
930         g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), 0);
931         
932         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
933         return priv->spacing;
934 }
935
936 struct StartupData {
937         glk_main_t glk_main;
938         glkunix_startup_code_t glkunix_startup_code;
939         glkunix_startup_t args;
940         ChimaraGlkPrivate *glk_data;
941 };
942
943 /* glk_enter() is the actual function called in the new thread in which glk_main() runs.  */
944 static gpointer
945 glk_enter(struct StartupData *startup)
946 {
947         extern GPrivate *glk_data_key;
948         g_private_set(glk_data_key, startup->glk_data);
949         
950         /* Run startup function */
951         if(startup->glkunix_startup_code) {
952                 startup->glk_data->in_startup = TRUE;
953                 int result = startup->glkunix_startup_code(&startup->args);
954                 startup->glk_data->in_startup = FALSE;
955                 
956                 int i = 0;
957                 while(i < startup->args.argc)
958                         g_free(startup->args.argv[i++]);
959                 g_free(startup->args.argv);
960                 
961                 if(!result)
962                         return NULL;
963         }
964         
965         /* Run main function */
966     g_signal_emit_by_name(startup->glk_data->self, "started");
967         (startup->glk_main)();
968         g_signal_emit_by_name(startup->glk_data->self, "stopped");
969         g_slice_free(struct StartupData, startup);
970         return NULL;
971 }
972
973 /**
974  * chimara_glk_run:
975  * @glk: a #ChimaraGlk widget
976  * @plugin: path to a plugin module compiled with <filename 
977  * class="header">glk.h</filename>
978  * @argc: Number of command line arguments in @argv
979  * @argv: Array of command line arguments to pass to the plugin
980  * @error: location to store a <link linkend="glib-GError">GError</link>, or 
981  * %NULL
982  *
983  * Opens a Glk program compiled as a plugin. Sorts out its command line
984  * arguments from #glkunix_arguments, calls its startup function
985  * glkunix_startup_code(), and then calls its main function glk_main() in
986  * a separate thread. On failure, returns %FALSE and sets @error.
987  *
988  * The plugin must at least export a glk_main() function; #glkunix_arguments and
989  * glkunix_startup_code() are optional.
990  *
991  * Return value: %TRUE if the Glk program was started successfully.
992  */
993 gboolean
994 chimara_glk_run(ChimaraGlk *glk, gchar *plugin, int argc, char *argv[], GError **error)
995 {
996     g_return_val_if_fail(glk || CHIMARA_IS_GLK(glk), FALSE);
997     g_return_val_if_fail(plugin, FALSE);
998     
999     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
1000         struct StartupData *startup = g_slice_new0(struct StartupData);
1001     
1002     /* Open the module to run */
1003     g_assert( g_module_supported() );
1004     priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY);
1005     
1006     if(!priv->program)
1007     {
1008         g_warning( "Error opening module: %s", g_module_error() );
1009         return FALSE;
1010     }
1011     if( !g_module_symbol(priv->program, "glk_main", (gpointer *) &startup->glk_main) )
1012     {
1013         g_warning( "Error finding glk_main(): %s", g_module_error() );
1014         return FALSE;
1015     }
1016
1017     if( g_module_symbol(priv->program, "glkunix_startup_code", (gpointer *) &startup->glkunix_startup_code) )
1018     {
1019                 glkunix_argumentlist_t *glkunix_arguments;
1020
1021                 if( !(g_module_symbol(priv->program, "glkunix_arguments", (gpointer *) &glkunix_arguments) 
1022                           && parse_command_line(glkunix_arguments, argc, argv, &startup->args)) )
1023                 {
1024                         /* arguments could not be parsed, so create data ourselves */
1025                         startup->args.argc = 1;
1026                         startup->args.argv = g_new0(gchar *, 1);
1027                 }
1028
1029                 /* Set the program name */
1030                 startup->args.argv[0] = g_strdup(plugin);
1031     }
1032         startup->glk_data = priv;
1033         
1034     /* Run in a separate thread */
1035         priv->thread = g_thread_create((GThreadFunc)glk_enter, startup, TRUE, error);
1036         
1037         return !(priv->thread == NULL);
1038 }
1039
1040 /**
1041  * chimara_glk_stop:
1042  * @glk: a #ChimaraGlk widget
1043  *
1044  * Signals the Glk program running in @glk to abort. Note that if the program is
1045  * caught in an infinite loop in which glk_tick() is not called, this may not
1046  * work.
1047  */
1048 void
1049 chimara_glk_stop(ChimaraGlk *glk)
1050 {
1051     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
1052     /* TODO: check if glk is actually running a program */
1053         ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
1054         if(priv->abort_lock) {
1055                 g_mutex_lock(priv->abort_lock);
1056                 priv->abort_signalled = TRUE;
1057                 g_mutex_unlock(priv->abort_lock);
1058                 /* Stop blocking on the event queue condition */
1059                 event_throw(glk, evtype_Abort, NULL, 0, 0);
1060         }
1061 }
1062
1063 /**
1064  * chimara_glk_wait:
1065  * @glk: a #ChimaraGlk widget
1066  *
1067  * Holds up the main thread and waits for the Glk program running in @glk to 
1068  * finish.
1069  */
1070 void
1071 chimara_glk_wait(ChimaraGlk *glk)
1072 {
1073     g_return_if_fail(glk || CHIMARA_IS_GLK(glk));
1074     
1075     ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(glk);
1076     g_thread_join(priv->thread);
1077 }