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