X-Git-Url: https://git.stderr.nl/gitweb?a=blobdiff_plain;f=libchimara%2Fchimara-glk.c;h=8fd1a1e81b752a430537961253813191dd685ba4;hb=59f3bc1e4f21377c362c1f497fb225711127d7a0;hp=a200b6ba46ad8f8189a71efb8f8ac1032b5ccb28;hpb=3ce5b9f501ea360ab84ee35f4ef0b1f1066dee9d;p=projects%2Fchimara%2Fchimara.git diff --git a/libchimara/chimara-glk.c b/libchimara/chimara-glk.c index a200b6b..8fd1a1e 100644 --- a/libchimara/chimara-glk.c +++ b/libchimara/chimara-glk.c @@ -127,7 +127,8 @@ enum { PROP_SPACING, PROP_PROGRAM_NAME, PROP_PROGRAM_INFO, - PROP_STORY_NAME + PROP_STORY_NAME, + PROP_RUNNING }; enum { @@ -149,7 +150,9 @@ G_DEFINE_TYPE(ChimaraGlk, chimara_glk, GTK_TYPE_CONTAINER); static void chimara_glk_init(ChimaraGlk *self) { - GTK_WIDGET_SET_FLAGS(GTK_WIDGET(self), GTK_NO_WINDOW); + chimara_init(); /* This is a library entry point */ + + gtk_widget_set_has_window(GTK_WIDGET(self), FALSE); ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(self); @@ -246,6 +249,9 @@ chimara_glk_get_property(GObject *object, guint prop_id, GValue *value, GParamSp case PROP_STORY_NAME: g_value_set_string(value, priv->story_name); break; + case PROP_RUNNING: + g_value_set_boolean(value, priv->running); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); } @@ -302,6 +308,9 @@ chimara_glk_finalize(GObject *object) /* Unref input queues (this should destroy them since any Glk thread has stopped by now */ g_async_queue_unref(priv->char_input_queue); g_async_queue_unref(priv->line_input_queue); + /* Destroy callback data if ownership retained */ + if(priv->resource_load_callback_destroy_data) + priv->resource_load_callback_destroy_data(priv->resource_load_callback_data); /* Free other stuff */ g_free(priv->current_dir); @@ -379,7 +388,9 @@ request_recurse(winid_t win, GtkRequisition *requisition, guint spacing) gtk_widget_size_request(win->frame, requisition); } -/* Overrides gtk_widget_size_request */ +/* Old GTK 2 functionality overriding gtk_widget_size_request(); +get_preferred_width() and get_preferred_height() are implemented in terms of +this function. */ static void chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition) { @@ -389,20 +400,51 @@ chimara_glk_size_request(GtkWidget *widget, GtkRequisition *requisition) ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); + guint border_width = gtk_container_get_border_width(GTK_CONTAINER(widget)); /* For now, just pass the size request on to the root Glk window */ if(priv->root_window) { request_recurse(priv->root_window->data, requisition, priv->spacing); - requisition->width += 2 * GTK_CONTAINER(widget)->border_width; - requisition->height += 2 * GTK_CONTAINER(widget)->border_width; + requisition->width += 2 * border_width; + requisition->height += 2 * border_width; } else { - requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * GTK_CONTAINER(widget)->border_width; - requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * GTK_CONTAINER(widget)->border_width; + requisition->width = CHIMARA_GLK_MIN_WIDTH + 2 * border_width; + requisition->height = CHIMARA_GLK_MIN_HEIGHT + 2 * border_width; } } +/* Minimal implementation of width-for-height request, in terms of the old +GTK 2 mechanism. FIXME: make this more efficient. */ +static void +chimara_glk_get_preferred_width(GtkWidget *widget, int *minimal, int *natural) +{ + g_return_if_fail(widget || CHIMARA_IS_GLK(widget)); + g_return_if_fail(minimal); + g_return_if_fail(natural); + + GtkRequisition requisition; + + chimara_glk_size_request(widget, &requisition); + *minimal = *natural = requisition.width; +} + +/* Minimal implementation of height-for-width request, in terms of the old +GTK 2 mechanism. FIXME: make this more efficient. */ +static void +chimara_glk_get_preferred_height(GtkWidget *widget, int *minimal, int *natural) +{ + g_return_if_fail(widget || CHIMARA_IS_GLK(widget)); + g_return_if_fail(minimal); + g_return_if_fail(natural); + + GtkRequisition requisition; + + chimara_glk_size_request(widget, &requisition); + *minimal = *natural = requisition.height; +} + /* Recursively give the Glk windows their allocated space. Returns a window containing all children of this window that must be redrawn, or NULL if there are no children that require redrawing. */ @@ -519,8 +561,10 @@ allocate_recurse(winid_t win, GtkAllocation *allocation, guint spacing) /* It says in the spec that when a text grid window is resized smaller, the bottom or right area is thrown away; when it is resized larger, the bottom or right area is filled with blanks. */ - glui32 newwidth = (glui32)(win->widget->allocation.width / win->unit_width); - glui32 newheight = (glui32)(win->widget->allocation.height / win->unit_height); + GtkAllocation widget_allocation; + gtk_widget_get_allocation(win->widget, &widget_allocation); + glui32 newwidth = (glui32)(widget_allocation.width / win->unit_width); + glui32 newheight = (glui32)(widget_allocation.height / win->unit_height); gint line; GtkTextBuffer *textbuffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW(win->widget) ); GtkTextIter start, end; @@ -595,14 +639,15 @@ chimara_glk_size_allocate(GtkWidget *widget, GtkAllocation *allocation) ChimaraGlkPrivate *priv = CHIMARA_GLK_PRIVATE(widget); - widget->allocation = *allocation; + gtk_widget_set_allocation(widget, allocation); if(priv->root_window) { GtkAllocation child; - child.x = allocation->x + GTK_CONTAINER(widget)->border_width; - child.y = allocation->y + GTK_CONTAINER(widget)->border_width; - child.width = CLAMP(allocation->width - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->width); - child.height = CLAMP(allocation->height - 2 * GTK_CONTAINER(widget)->border_width, 0, allocation->height); + guint border_width = gtk_container_get_border_width(GTK_CONTAINER(widget)); + child.x = allocation->x + border_width; + child.y = allocation->y + border_width; + child.width = CLAMP(allocation->width - 2 * border_width, 0, allocation->width); + child.height = CLAMP(allocation->height - 2 * border_width, 0, allocation->height); winid_t arrange = allocate_recurse(priv->root_window->data, &child, priv->spacing); /* arrange points to a window that contains all text grid and graphics @@ -701,18 +746,6 @@ chimara_glk_iliad_screen_update(ChimaraGlk *self, gboolean typing) /* Default signal handler */ } -/* COMPAT: G_PARAM_STATIC_STRINGS only appeared in GTK 2.13.0 */ -#ifndef G_PARAM_STATIC_STRINGS - -/* COMPAT: G_PARAM_STATIC_NAME and friends only appeared in GTK 2.8 */ -#if GTK_CHECK_VERSION(2,8,0) -#define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB) -#else -#define G_PARAM_STATIC_STRINGS (0) -#endif - -#endif - static void chimara_glk_class_init(ChimaraGlkClass *klass) { @@ -723,7 +756,8 @@ chimara_glk_class_init(ChimaraGlkClass *klass) object_class->finalize = chimara_glk_finalize; GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); - widget_class->size_request = chimara_glk_size_request; + widget_class->get_preferred_width = chimara_glk_get_preferred_width; + widget_class->get_preferred_height = chimara_glk_get_preferred_height; widget_class->size_allocate = chimara_glk_size_allocate; GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass); @@ -919,6 +953,17 @@ chimara_glk_class_init(ChimaraGlkClass *klass) NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS) ); + /** + * ChimaraGlk:running: + * + * Whether this Glk widget is currently running a game or not. + */ + g_object_class_install_property(object_class, PROP_RUNNING, + g_param_spec_boolean("running", _("Running"), + _("Whether there is a program currently running"), + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS) ); + /* Private data */ g_type_class_add_private(klass, sizeof(ChimaraGlkPrivate)); } @@ -1229,8 +1274,7 @@ chimara_glk_run(ChimaraGlk *glk, const gchar *plugin, int argc, char *argv[], GE g_assert( g_module_supported() ); /* If there is already a module loaded, free it first -- you see, we want to * keep modules loaded as long as possible to avoid crashes in stack unwinding */ - if( priv->program && !g_module_close(priv->program) ) - g_warning( "Error closing module :%s", g_module_error() ); + chimara_glk_unload_plugin(glk); /* Open the module to run */ priv->program = g_module_open(plugin, G_MODULE_BIND_LAZY); @@ -1272,6 +1316,35 @@ chimara_glk_run(ChimaraGlk *glk, const gchar *plugin, int argc, char *argv[], GE return !(priv->thread == NULL); } +/** + * chimara_glk_run_file: + * @self: a #ChimaraGlk widget + * @plugin_file: a #GFile pointing to a plugin module compiled with glk.h + * @argc: Number of command line arguments in @argv + * @argv: Array of command line arguments to pass to the plugin + * @error: location to store a GError, or %NULL + * + * Opens a Glk program compiled as a plugin, from a #GFile. See + * chimara_glk_run() for details. + * + * Return value: %TRUE if the Glk program was started successfully. + */ +gboolean +chimara_glk_run_file(ChimaraGlk *self, GFile *plugin_file, int argc, char *argv[], GError **error) +{ + g_return_val_if_fail(self || CHIMARA_IS_GLK(self), FALSE); + g_return_val_if_fail(plugin_file || G_IS_FILE(plugin_file), FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + char *path = g_file_get_path(plugin_file); + gboolean retval = chimara_glk_run(self, path, argc, argv, error); + g_free(path); + + return retval; +} + /** * chimara_glk_stop: * @glk: a #ChimaraGlk widget @@ -1279,6 +1352,8 @@ chimara_glk_run(ChimaraGlk *glk, const gchar *plugin, int argc, char *argv[], GE * Signals the Glk program running in @glk to abort. Note that if the program is * caught in an infinite loop in which glk_tick() is not called, this may not * work. + * + * This function does nothing if no Glk program is running. */ void chimara_glk_stop(ChimaraGlk *glk) @@ -1309,6 +1384,8 @@ chimara_glk_stop(ChimaraGlk *glk) * * Holds up the main thread and waits for the Glk program running in @glk to * finish. + * + * This function does nothing if no Glk program is running. */ void chimara_glk_wait(ChimaraGlk *glk) @@ -1324,6 +1401,26 @@ chimara_glk_wait(ChimaraGlk *glk) gdk_threads_enter(); } +/** + * chimara_glk_unload_plugin: + * @glk: a #ChimaraGlk widget + * + * The plugin containing the Glk program is unloaded as late as possible before + * loading a new plugin, in order to prevent crashes while printing stack + * backtraces during debugging. Sometimes this behavior is not desirable. This + * function forces @glk to unload the plugin running in it. + * + * This function does nothing if there is no plugin loaded. + */ +void +chimara_glk_unload_plugin(ChimaraGlk *glk) +{ + g_return_if_fail(glk || CHIMARA_IS_GLK(glk)); + CHIMARA_GLK_USE_PRIVATE(glk, priv); + if( priv->program && !g_module_close(priv->program) ) + g_warning( "Error closing module :%s", g_module_error() ); +} + /** * chimara_glk_get_running: * @glk: a #ChimaraGlk widget @@ -1519,6 +1616,7 @@ chimara_glk_update_style(ChimaraGlk *glk) * @glk: a #ChimaraGlk widget * @func: a function to call for loading resources, or %NULL * @user_data: user data to pass to @func, or %NULL + * @destroy_user_data: a function to call for freeing @user_data, or %NULL * * Sometimes it is preferable to load image and sound resources from somewhere * else than a Blorb file, for example while developing a game. Section 14 of @@ -1531,12 +1629,27 @@ chimara_glk_update_style(ChimaraGlk *glk) * Note that @func is only called if no Blorb resource map has been set; having * a resource map in place overrides this function. * + * If you pass non-%NULL for @destroy_user_data, then @glk takes ownership of + * @user_data. When it is not needed anymore, it will be freed by calling + * @destroy_user_data on it. If you wish to retain ownership of @user_data, pass + * %NULL for @destroy_user_data. + * * To deactivate the callback, call this function with @func set to %NULL. */ void -chimara_glk_set_resource_load_callback(ChimaraGlk *glk, ChimaraResourceLoadFunc func, gpointer user_data) +chimara_glk_set_resource_load_callback(ChimaraGlk *glk, ChimaraResourceLoadFunc func, gpointer user_data, GDestroyNotify destroy_user_data) { CHIMARA_GLK_USE_PRIVATE(glk, priv); + + if(priv->resource_load_callback == func + && priv->resource_load_callback_data == user_data + && priv->resource_load_callback_destroy_data == destroy_user_data) + return; + + if(priv->resource_load_callback_destroy_data) + priv->resource_load_callback_destroy_data(priv->resource_load_callback_data); + priv->resource_load_callback = func; priv->resource_load_callback_data = user_data; + priv->resource_load_callback_destroy_data = destroy_user_data; }