Add configure UI to Frotz plugin
[projects/chimara/chimara.git] / interpreters / chimara-frotz-plugin.c
1 #include <glib-object.h>
2 #include <config.h>
3 #include <glib/gi18n-lib.h>
4 #include <gtk/gtk.h>
5 #include <libpeas/peas.h>
6 #include <libpeas-gtk/peas-gtk.h>
7 #include "chimara-frotz-plugin.h"
8 #include "frotz/frotz.h"
9
10 typedef struct _ChimaraFrotzPluginPrivate {
11         int random_seed;
12         gboolean random_seed_set;
13         gboolean tandy_bit;
14 } ChimaraFrotzPluginPrivate;
15
16 #define CHIMARA_FROTZ_PLUGIN_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), CHIMARA_TYPE_FROTZ_PLUGIN, ChimaraFrotzPluginPrivate))
17 #define CHIMARA_FROTZ_PLUGIN_USE_PRIVATE ChimaraFrotzPluginPrivate *priv = CHIMARA_FROTZ_PLUGIN_PRIVATE(self)
18
19 static void chimara_frotz_plugin_configurable_init(PeasGtkConfigurableInterface *);
20 static GtkWidget *chimara_frotz_plugin_create_configure_widget(PeasGtkConfigurable *);
21
22 G_DEFINE_DYNAMIC_TYPE_EXTENDED(ChimaraFrotzPlugin, chimara_frotz_plugin, PEAS_TYPE_EXTENSION_BASE, 0,
23         G_IMPLEMENT_INTERFACE_DYNAMIC(PEAS_GTK_TYPE_CONFIGURABLE, chimara_frotz_plugin_configurable_init));
24
25 enum {
26         PROP_0,
27         PROP_DEBUG_MESSAGES,
28         PROP_IGNORE_ERRORS,
29         PROP_PIRACY_MODE,
30         PROP_QUETZAL_SAVE_FORMAT,
31         PROP_TANDY_BIT,
32         PROP_EXPAND_ABBREVIATIONS,
33         PROP_RANDOM_SEED,
34         PROP_RANDOM_SEED_SET,
35         PROP_TRANSCRIPT_COLUMNS,
36         PROP_UNDO_SLOTS
37 };
38
39 G_MODULE_EXPORT void
40 peas_register_types(PeasObjectModule *module)
41 {
42         chimara_frotz_plugin_register_type(G_TYPE_MODULE(module));
43         peas_object_module_register_extension_type(module, PEAS_GTK_TYPE_CONFIGURABLE, CHIMARA_TYPE_FROTZ_PLUGIN);
44 }
45
46 static void
47 chimara_frotz_plugin_init(ChimaraFrotzPlugin *self)
48 {
49         CHIMARA_FROTZ_PLUGIN_USE_PRIVATE;
50         priv->random_seed_set = FALSE;
51         priv->tandy_bit = FALSE;
52 }
53
54 #define PROCESS_FLAG(name) ((flags & (name))? 1 : 0)
55
56 static void
57 chimara_frotz_plugin_set_property(GObject *self, unsigned prop_id, const GValue *value, GParamSpec *pspec)
58 {
59         CHIMARA_FROTZ_PLUGIN_USE_PRIVATE;
60
61         switch(prop_id) {
62                 case PROP_DEBUG_MESSAGES:
63                 {
64                         unsigned flags = g_value_get_uint(value);
65                         option_attribute_assignment = PROCESS_FLAG(CHIMARA_FROTZ_DEBUG_ATTRIBUTE_SETTING);
66                         option_attribute_testing = PROCESS_FLAG(CHIMARA_FROTZ_DEBUG_ATTRIBUTE_TESTING);
67                         option_object_movement = PROCESS_FLAG(CHIMARA_FROTZ_DEBUG_OBJECT_MOVEMENT);
68                         option_object_locating = PROCESS_FLAG(CHIMARA_FROTZ_DEBUG_OBJECT_LOCATING);
69                         g_object_notify(self, "debug-messages");
70                         break;
71                 }
72                 case PROP_IGNORE_ERRORS:
73                         option_ignore_errors = g_value_get_boolean(value);
74                         g_object_notify(self, "ignore-errors");
75                         break;
76                 case PROP_PIRACY_MODE:
77                         option_piracy = g_value_get_boolean(value);
78                         g_object_notify(self, "piracy-mode");
79                         break;
80                 case PROP_QUETZAL_SAVE_FORMAT:
81                         option_save_quetzal = g_value_get_boolean(value);
82                         g_object_notify(self, "quetzal-save-format");
83                         break;
84                 case PROP_TANDY_BIT:
85                         priv->tandy_bit = g_value_get_boolean(value);
86                         g_object_notify(self, "tandy-bit");
87                         break;
88                 case PROP_EXPAND_ABBREVIATIONS:
89                         option_expand_abbreviations = g_value_get_boolean(value);
90                         g_object_notify(self, "expand-abbreviations");
91                         break;
92                 case PROP_RANDOM_SEED:
93                         priv->random_seed = g_value_get_int(value);
94                         g_object_notify(self, "random-seed");
95                         break;
96                 case PROP_RANDOM_SEED_SET:
97                         priv->random_seed_set = g_value_get_boolean(value);
98                         g_object_notify(self, "random-seed-set");
99                         break;
100                 case PROP_TRANSCRIPT_COLUMNS:
101                         option_script_cols = g_value_get_uint(value);
102                         g_object_notify(self, "transcript-columns");
103                         break;
104                 case PROP_UNDO_SLOTS:
105                         option_undo_slots = g_value_get_uint(value);
106                         g_object_notify(self, "undo-slots");
107                         break;
108                 default:
109                         G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
110         }
111 }
112
113 #undef PROCESS_FLAG
114
115 static void
116 chimara_frotz_plugin_get_property(GObject *self, unsigned prop_id, GValue *value, GParamSpec *pspec)
117 {
118         CHIMARA_FROTZ_PLUGIN_USE_PRIVATE;
119
120         switch(prop_id) {
121                 case PROP_DEBUG_MESSAGES:
122                 {
123                         unsigned flags = option_attribute_assignment << 0
124                                 | option_attribute_testing << 1
125                                 | option_object_movement << 2
126                                 | option_object_locating << 3;
127                         g_value_set_uint(value, flags);
128                         break;
129                 }
130                 case PROP_IGNORE_ERRORS:
131                         g_value_set_boolean(value, option_ignore_errors);
132                         break;
133                 case PROP_PIRACY_MODE:
134                         g_value_set_boolean(value, option_piracy);
135                         break;
136                 case PROP_QUETZAL_SAVE_FORMAT:
137                         g_value_set_boolean(value, option_save_quetzal);
138                         break;
139                 case PROP_TANDY_BIT:
140                         g_value_set_boolean(value, priv->tandy_bit);
141                         break;
142                 case PROP_EXPAND_ABBREVIATIONS:
143                         g_value_set_boolean(value, option_expand_abbreviations);
144                         break;
145                 case PROP_RANDOM_SEED:
146                         g_value_set_int(value, priv->random_seed);
147                         break;
148                 case PROP_RANDOM_SEED_SET:
149                         g_value_set_boolean(value, priv->random_seed_set);
150                         break;
151                 case PROP_TRANSCRIPT_COLUMNS:
152                         g_value_set_uint(value, option_script_cols);
153                         break;
154                 case PROP_UNDO_SLOTS:
155                         g_value_set_uint(value, option_undo_slots);
156                         break;
157                 default:
158                         G_OBJECT_WARN_INVALID_PROPERTY_ID(self, prop_id, pspec);
159         }
160 }
161
162 static void
163 chimara_frotz_plugin_class_init(ChimaraFrotzPluginClass *klass)
164 {
165         GObjectClass *object_class = G_OBJECT_CLASS(klass);
166         object_class->set_property = chimara_frotz_plugin_set_property;
167         object_class->get_property = chimara_frotz_plugin_get_property;
168
169         /* Private data */
170         g_type_class_add_private(klass, sizeof(ChimaraFrotzPluginPrivate));
171
172         /* Properties */
173         /**
174          * ChimaraFrotzPlugin:debug-messages:
175          *
176          * Set of flags to control which debugging messages, if any, Frotz prints
177          * while interpreting the story. See #ChimaraFrotzDebugFlags.
178          */
179         /* TODO: register a flags type and use g_param_spec_flags() */
180         g_object_class_install_property(object_class, PROP_DEBUG_MESSAGES,
181                 g_param_spec_uint("debug-messages", _("Kinds of debugging messages"),
182                         _("Control which kinds of debugging messages to print"),
183                         0, 255, CHIMARA_FROTZ_DEBUG_NONE,
184                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
185         /**
186          * ChimaraFrotzPlugin:ignore-errors:
187          *
188          * Setting this property to %TRUE will cause the interpreter to ignore
189          * fatal Z-machine runtime errors.
190          */
191         g_object_class_install_property(object_class, PROP_IGNORE_ERRORS,
192                 g_param_spec_boolean("ignore-errors", _("Ignore errors"),
193                 _("Do not warn the user about fatal Z-machine errors"), FALSE,
194                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
195         /**
196          * ChimaraFrotzPlugin:piracy-mode:
197          *
198          * The Z-machine specification defines a facility for games to ask the
199          * interpreter they are running on whether this copy of the game is pirated.
200          * How the interpreter is supposed to magically determine that it is running
201          * pirate software is unclear, and so the majority of games and interpreters
202          * ignore this feature. Set this property to %TRUE if you want the
203          * interpreter to pretend it has detected a pirated game.
204          */
205         g_object_class_install_property(object_class, PROP_PIRACY_MODE,
206                 g_param_spec_boolean("piracy-mode", _("Piracy mode"),
207                 _("Pretend the game is pirated"), FALSE,
208                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
209         /**
210          * ChimaraFrotzPlugin:quetzal-save-format:
211          *
212          * If set to %TRUE, use the newer-style Quetzal format for saved games.
213          * (This is the default.)
214          */
215         g_object_class_install_property(object_class, PROP_QUETZAL_SAVE_FORMAT,
216                 g_param_spec_boolean("quetzal-save-format", _("Use Quetzal save format"),
217                         _("Use the Quetzal format for saved games"), TRUE,
218                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
219         /**
220          * ChimaraFrotzPlugin:tandy-bit:
221          *
222          * Some early Infocom games were sold by the Tandy Corporation. Setting this
223          * property to %TRUE changes the wording of some Version 3 Infocom games
224          * slightly, so as to be less offensive. See <ulink
225          * url="http://www.ifarchive.org/if-archive/infocom/info/tandy_bits.html">
226          * http://www.ifarchive.org/if-archive/infocom/info/tandy_bits.html</ulink>.
227          *
228          * Only affects Z-machine interpreters.
229          */
230         g_object_class_install_property(object_class, PROP_TANDY_BIT,
231                 g_param_spec_boolean("tandy-bit", _("Tandy bit"),
232                 _("Censor certain Infocom games"), FALSE,
233                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
234         /**
235          * ChimaraIF:expand-abbreviations:
236          *
237          * Most Z-machine games, in particular ones compiled with the Inform
238          * library, support the following one-letter abbreviations:
239          * <simplelist>
240          * <member>D &mdash; Down</member>
241          * <member>E &mdash; East</member>
242          * <member>G &mdash; aGain</member>
243          * <member>I &mdash; Inventory</member>
244          * <member>L &mdash; Look</member>
245          * <member>N &mdash; North</member>
246          * <member>O &mdash; Oops</member>
247          * <member>Q &mdash; Quit</member>
248          * <member>S &mdash; South</member>
249          * <member>U &mdash; Up</member>
250          * <member>W &mdash; West</member>
251          * <member>X &mdash; eXamine</member>
252          * <member>Y &mdash; Yes</member>
253          * <member>Z &mdash; wait (ZZZZ...)</member>
254          * </simplelist>
255          * Some early Infocom games might not recognize these abbreviations.
256          * However, Frotz can expand G, X, and Z regardless of what the game
257          * recognizes. Setting this property to %TRUE will cause Frotz to expand
258          * these abbreviations to the full words before passing the commands on to
259          * the game.
260          */
261         g_object_class_install_property(object_class, PROP_EXPAND_ABBREVIATIONS,
262                 g_param_spec_boolean("expand-abbreviations", _("Expand abbreviations"),
263                 _("Expand abbreviations such as X for EXAMINE"), FALSE,
264                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
265         /**
266          * ChimaraFrotzPlugin:random-seed:
267          *
268          * If the #ChimaraFrotzPlugin:random-seed-set property is %TRUE, then the
269          * interpreter will use the value of this property as a seed for the random
270          * number generator. Use this feature to duplicate sequences of random
271          * numbers for testing games.
272          *
273          * Note that the value -1 will cause Frotz to pick an arbitrary seed even
274          * when #ChimaraFrotzPlugin:random-seed-set is %TRUE.
275          */
276         g_object_class_install_property(object_class, PROP_RANDOM_SEED,
277                 g_param_spec_int("random-seed", _("Random seed"),
278                 _("Seed for the random number generator"), G_MININT, G_MAXINT, 0,
279                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
280         /**
281          * ChimaraFrotzPlugin:random-seed-set:
282          *
283          * Whether to use or ignore the #ChimaraFrotzPlugin:random-seed property.
284          */
285         g_object_class_install_property(object_class, PROP_RANDOM_SEED_SET,
286                 g_param_spec_boolean("random-seed-set", _("Random seed set"),
287                 _("Whether the seed for the random number generator should be set manually"), FALSE,
288                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
289         /**
290          * ChimaraFrotzPlugin:transcript-columns:
291          *
292          * How many columns to make the transcript output.
293          */
294         g_object_class_install_property(object_class, PROP_TRANSCRIPT_COLUMNS,
295                 g_param_spec_uint("transcript-columns", _("Transcript columns"),
296                         _("Number of columns for transcript output"),
297                         0, G_MAXUINT, 80,
298                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
299         /**
300          * ChimaraFrotzPlugin:undo-slots:
301          *
302          * How many slots to reserve for multiple Undo commands.
303          */
304         g_object_class_install_property(object_class, PROP_UNDO_SLOTS,
305                 g_param_spec_uint("undo-slots", _("Undo slots"),
306                         _("Number of slots to reserve for multiple undo"),
307                         0, MAX_UNDO_SLOTS, MAX_UNDO_SLOTS,
308                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_LAX_VALIDATION | G_PARAM_STATIC_STRINGS));
309 }
310
311 static void
312 chimara_frotz_plugin_class_finalize(ChimaraFrotzPluginClass *klass)
313 {
314 }
315
316 static void
317 chimara_frotz_plugin_configurable_init(PeasGtkConfigurableInterface *iface)
318 {
319         iface->create_configure_widget = chimara_frotz_plugin_create_configure_widget;
320 }
321
322 /* Helper function to transform flags value to boolean; @data contains the
323 GINT_TO_POINTER()'ed but position (0=LSB). */
324 static gboolean
325 debug_message_flags_transform_to(GBinding *binding, const GValue *source, GValue *target, gpointer data)
326 {
327         int bit_shift = GPOINTER_TO_INT(data);
328         unsigned flags = g_value_get_uint(source);
329         g_value_set_boolean(target, flags & (1 << bit_shift));
330         return TRUE; /* success */
331 }
332
333 /* Reverse of debug_message_flags_transform_to(). */
334 static gboolean
335 debug_message_flags_transform_from(GBinding *binding, const GValue *source, GValue *target, gpointer data)
336 {
337         int bit_shift = GPOINTER_TO_INT(data);
338         unsigned flags = g_value_get_uint(target);
339         int new_value = g_value_get_boolean(source)? 1 : 0;
340         g_value_set_uint(target, flags & (new_value << bit_shift));
341         return TRUE; /* success */
342 }
343
344 static GtkWidget *
345 chimara_frotz_plugin_create_configure_widget(PeasGtkConfigurable *self)
346 {
347         GError *error = NULL;
348         const char *datadir = peas_plugin_info_get_data_dir(peas_engine_get_plugin_info(peas_engine_get_default(), "frotz"));
349         char *glade_file = g_build_filename(datadir, "chimara-frotz-plugin.glade", NULL);
350         GtkBuilder *builder = gtk_builder_new();
351         if(!gtk_builder_add_from_file(builder, glade_file, &error)) {
352                 g_free(glade_file);
353                 g_critical("Error building Frotz configuration dialog: %s\n", error->message);
354                 return NULL;
355         }
356         g_free(glade_file);
357         GObject *retval = gtk_builder_get_object(builder, "frotz-configure-widget");
358
359         /* Bind GUI widget properties to this plugin's configuration properties */
360         g_object_bind_property_full(self, "debug-messages",
361                 gtk_builder_get_object(builder, "attribute-setting-button"), "active",
362                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
363                 debug_message_flags_transform_to,
364                 debug_message_flags_transform_from,
365                 GINT_TO_POINTER(0), NULL);
366         g_object_bind_property_full(self, "debug-messages",
367                 gtk_builder_get_object(builder, "attribute-testing-button"), "active",
368                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
369                 debug_message_flags_transform_to,
370                 debug_message_flags_transform_from,
371                 GINT_TO_POINTER(1), NULL);
372         g_object_bind_property_full(self, "debug-messages",
373                 gtk_builder_get_object(builder, "object-movement-button"), "active",
374                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
375                 debug_message_flags_transform_to,
376                 debug_message_flags_transform_from,
377                 GINT_TO_POINTER(2), NULL);
378         g_object_bind_property_full(self, "debug-messages",
379                 gtk_builder_get_object(builder, "object-location-button"), "active",
380                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
381                 debug_message_flags_transform_to,
382                 debug_message_flags_transform_from,
383                 GINT_TO_POINTER(3), NULL);
384         g_object_bind_property(self, "ignore-errors",
385                 gtk_builder_get_object(builder, "ignore-errors-button"), "active",
386                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
387         g_object_bind_property(self, "piracy-mode",
388                 gtk_builder_get_object(builder, "piracy-button"), "active",
389                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
390         g_object_bind_property(self, "quetzal-save-format",
391                 gtk_builder_get_object(builder, "quetzal-button"), "active",
392                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
393         g_object_bind_property(self, "tandy-bit",
394                 gtk_builder_get_object(builder, "tandy-button"), "active",
395                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
396         g_object_bind_property(self, "expand-abbreviations",
397                 gtk_builder_get_object(builder, "expand-abbreviations-button"), "active",
398                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
399         g_object_bind_property(self, "random-seed",
400                 gtk_builder_get_object(builder, "random-seed-adjustment"), "value",
401                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
402         g_object_bind_property(self, "random-seed-set",
403                 gtk_builder_get_object(builder, "random-seed-set-button"), "active",
404                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
405         g_object_bind_property(self, "random-seed-set",
406                 gtk_builder_get_object(builder, "random-seed-button"), "sensitive",
407                 G_BINDING_SYNC_CREATE);
408         g_object_bind_property(self, "transcript-columns",
409                 gtk_builder_get_object(builder, "columns-adjustment"), "value",
410                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
411         g_object_bind_property(self, "undo-slots",
412                 gtk_builder_get_object(builder, "undo-adjustment"), "value",
413                 G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
414
415         /* Make sure the widget is returned with only one reference */
416         g_object_ref_sink(G_OBJECT(retval));
417         g_object_unref(builder);
418         return GTK_WIDGET(retval);
419 }