1 /*
2    Copyright (C) 2002/2003 Kai Sterker <kai.sterker@gmail.com>
3    Part of the Adonthell Project  <http://adonthell.nongnu.org>
4 
5    Dlgedit is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9 
10    Dlgedit is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with Dlgedit.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 /**
20  * @file gui_dlgedit.cc
21  *
22  * @author Kai Sterker
23  * @brief The Dialogue Editor's main window
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29 
30 #ifdef MAC_INTEGRATION
31 #include <ige-mac-integration.h>
32 #endif
33 
34 #ifdef WIN32
35 #define WIN32_LEAN_AND_MEAN
36 #include <windows.h>
37 #endif
38 
39 #define MIME_TYPE "application/x-adonthell-dlg"
40 
41 #include <algorithm>
42 #include <gdk-pixbuf/gdk-pixbuf.h>
43 #include <gdk/gdkkeysyms.h>
44 #include <glib/gstdio.h>
45 #include <sys/stat.h>
46 #include <unistd.h>
47 #include <cstring>
48 #include "gettext.h"
49 #include "cfg_data.h"
50 #include "dlg_cmdline.h"
51 #include "dlg_compiler.h"
52 #include "gui_code.h"
53 #include "gui_settings.h"
54 #include "gui_resources.h"
55 #include "gui_dlgedit.h"
56 #include "gui_dlgedit_events.h"
57 
58 /**
59  * Point parser to another file to load sub-dialogue.
60  * Defined in loadlg.l
61  */
62 extern void parser_switch_input ();
63 
64 /**
65  * Return to previous input file after loading sub-dialogue - or
66  * after failing to do so.
67  * Defined in loadlg.l
68  */
69 extern void parser_restore_input ();
70 
71 /**
72  * Global pointer to the main window
73  */
74 GuiDlgedit *GuiDlgedit::window = NULL;
75 
76 /**
77  * Strings describing the various program states
78  */
79 const char *GuiDlgedit::progState[NUM_MODES] =
80     { " IDLE", " SELECTED", " HIGHLIGHTED", " DRAGGED", " PREVIEW" };
81 
82 // Create the main window
GuiDlgedit()83 GuiDlgedit::GuiDlgedit ()
84 {
85     GtkWidget *vbox;
86     GtkWidget *hbox;
87     GtkWidget *menu;
88     GtkWidget *submenu;
89     GtkWidget *menuitem;
90     GtkWidget *hpaned, *vpaned;
91 
92     window = this;
93     number = 0;
94 
95     // recent file management
96     std::string cmdline = "-g" + DlgCmdline::datadir + " -p" + DlgCmdline::project;
97     RecentFiles = new GuiRecentFiles ("dlgedit", cmdline);
98     RecentFiles->setListener(this);
99 
100     // Statusbar for displaying help and error messages
101     GtkWidget *status_help = gtk_statusbar_new ();
102     // gtk_statusbar_set_has_resize_grip (GTK_STATUSBAR(status_help), FALSE);
103     message = new GuiMessages (status_help);
104 
105     // Main Window
106     wnd = gtk_window_new (GTK_WINDOW_TOPLEVEL);
107     gtk_window_set_default_size(GTK_WINDOW(wnd), 900, 700);
108     g_signal_connect (G_OBJECT (wnd), "delete_event", G_CALLBACK (on_widget_destroy), NULL);
109 
110     // Menu Accelerators
111     GtkAccelGroup *accel_group = gtk_accel_group_new ();
112 
113     // Main Windows Menu
114     menu = gtk_menu_bar_new ();
115 
116     // Attach Menubar
117     vbox = gtk_vbox_new (FALSE, 0);
118     gtk_container_add (GTK_CONTAINER (wnd), vbox);
119     gtk_widget_show (vbox);
120 
121     gtk_box_pack_start (GTK_BOX (vbox), menu, FALSE, FALSE, 2);
122 
123     // File Menu
124     submenu = gtk_menu_new ();
125 
126     // New
127     menuitem =gtk_image_menu_item_new_from_stock ("gtk-new", accel_group);
128     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
129     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (1));
130     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
131     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
132     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_file_new_activate), (gpointer) this);
133     gtk_widget_show (menuitem);
134 
135     // Open
136     menuitem =  gtk_image_menu_item_new_from_stock ("gtk-open", accel_group);
137     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
138     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (2));
139     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
140     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
141     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_file_load_activate), (gpointer) this);
142     gtk_widget_show (menuitem);
143 
144     // Open Previous >
145     menuitem = gtk_menu_item_new_with_label ("Open Previous");
146     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
147     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (2));
148     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
149     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
150     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), RecentFiles->recentFileMenu());
151 
152     // Save
153     menuitem = gtk_image_menu_item_new_from_stock ("gtk-save", accel_group);
154     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
155     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (3));
156     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
157     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
158     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_file_save_activate), (gpointer) this);
159     menuItem[SAVE] = menuitem;
160 
161     // Save As
162     menuitem = gtk_image_menu_item_new_from_stock ("gtk-save-as", accel_group);
163     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
164     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (4));
165     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
166     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
167     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_file_save_as_activate), (gpointer) this);
168     menuItem[SAVE_AS] = menuitem;
169 
170     // Seperator
171     menuitem = gtk_menu_item_new ();
172     gtk_menu_shell_append (GTK_MENU_SHELL (submenu), menuitem);
173     gtk_widget_set_sensitive (menuitem, FALSE);
174 
175     // Revert to Saved
176     menuitem = gtk_image_menu_item_new_from_stock ("gtk-revert-to-saved", accel_group);
177     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
178     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (7));
179     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
180     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
181     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_file_revert_activate), (gpointer) NULL);
182     menuItem[REVERT] = menuitem;
183 
184     // Close
185     menuitem = gtk_image_menu_item_new_from_stock ("gtk-close", accel_group);
186     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
187     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (5));
188     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
189     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
190     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_file_close_activate), (gpointer) NULL);
191     menuItem[CLOSE] = menuitem;
192 
193     // Seperator
194 #ifndef MAC_INTEGRATION
195     menuitem = gtk_menu_item_new ();
196     gtk_menu_shell_append (GTK_MENU_SHELL (submenu), menuitem);
197     gtk_widget_set_sensitive (menuitem, FALSE);
198 #endif
199 
200     // Quit
201     GtkWidget *quit_item = gtk_image_menu_item_new_from_stock ("gtk-quit", accel_group);
202     gtk_menu_shell_append (GTK_MENU_SHELL (submenu), quit_item);
203     g_object_set_data (G_OBJECT (quit_item), "help-id", GINT_TO_POINTER (6));
204     g_signal_connect (G_OBJECT (quit_item), "enter-notify-event", G_CALLBACK (on_display_help), message);
205     g_signal_connect (G_OBJECT (quit_item), "leave-notify-event", G_CALLBACK (on_clear_help), message);
206     g_signal_connect (G_OBJECT (quit_item), "activate", G_CALLBACK (on_widget_destroy), (gpointer) NULL);
207 
208     // Attach File Menu
209     menuitem = gtk_menu_item_new_with_mnemonic ("_File");
210     gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
211     gtk_container_add (GTK_CONTAINER (menu), menuitem);
212 
213     // Dialogue Menu
214     submenu = gtk_menu_new ();
215 
216     // Settings
217     menuitem = gtk_image_menu_item_new_with_mnemonic ("_Settings ...");
218     gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), gtk_image_new_from_stock ("gtk-preferences", GTK_ICON_SIZE_MENU));
219     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
220     gtk_widget_add_accelerator (menuitem, "activate", accel_group, GDK_KEY_t, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
221     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (10));
222     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
223     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
224     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_dialogue_player_activate), (gpointer) NULL);
225     menuItem[SETTINGS] = menuitem;
226 
227     // Custom Functions
228     menuitem = gtk_image_menu_item_new_with_mnemonic ("_Python Code ...");
229     gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), gtk_image_new_from_stock ("gtk-justify-fill", GTK_ICON_SIZE_MENU));
230     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
231     gtk_widget_add_accelerator (menuitem, "activate", accel_group, GDK_KEY_p, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
232     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (11));
233     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
234     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
235     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_dialogue_functions_activate), (gpointer) this);
236     menuItem[FUNCTIONS] = menuitem;
237 
238     // Seperator
239     menuitem = gtk_menu_item_new ();
240     gtk_menu_shell_append (GTK_MENU_SHELL (submenu), menuitem);
241     gtk_widget_set_sensitive (menuitem, FALSE);
242 
243     // Compile
244     menuitem = gtk_image_menu_item_new_with_mnemonic ("_Compile");
245     gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), gtk_image_new_from_stock ("gtk-convert", GTK_ICON_SIZE_MENU));
246     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
247     gtk_widget_add_accelerator (menuitem, "activate", accel_group, GDK_KEY_c, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
248     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (12));
249     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
250     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
251     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_dialogue_compile_activate), (gpointer) NULL);
252     menuItem[COMPILE] = menuitem;
253 
254     // Preview i18n
255 #ifdef ENABLE_NLS
256     menuitem = gtk_menu_item_new_with_label ("Preview L10n");
257     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
258     gtk_widget_add_accelerator (menuitem, "activate", accel_group, GDK_KEY_v, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
259     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (13));
260     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
261     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
262     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_dialogue_preview_activate), (gpointer) this);
263     gtk_widget_show (menuitem);
264     menuItem[PREVIEW] = menuitem;
265 #endif
266 
267     // Run
268     menuitem = gtk_image_menu_item_new_with_mnemonic ("_Execute ...");
269     gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menuitem), gtk_image_new_from_stock ("gtk-execute", GTK_ICON_SIZE_MENU));
270     gtk_container_add (GTK_CONTAINER (submenu), menuitem);
271     gtk_widget_add_accelerator (menuitem, "activate", accel_group, GDK_KEY_e, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
272     g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (14));
273     g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
274     g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
275     g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_dialogue_run_activate), (gpointer) NULL);
276     gtk_widget_show (menuitem);
277     menuItem[RUN] = menuitem;
278 
279     // Attach Dialogue Menu
280     menuitem = gtk_menu_item_new_with_mnemonic ("_Dialogue");
281     gtk_widget_show (menuitem);
282     gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
283     gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
284 
285     // Window Menu
286     windowMenu = gtk_menu_new ();
287 
288     // Attach Window Menu
289     menuitem = gtk_menu_item_new_with_mnemonic ("_Windows");
290     gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), windowMenu);
291     gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
292 
293     gtk_widget_show_all (menu);
294 
295 #ifdef MAC_INTEGRATION
296     // Mac OSX-Style menu
297     gtk_widget_hide (menu);
298 
299     mainMenu = menu;
300 
301     ige_mac_menu_set_menu_bar (GTK_MENU_SHELL (menu));
302     ige_mac_menu_set_quit_menu_item (GTK_MENU_ITEM (quit_item));
303 
304     // IgeMacMenuGroup *group = ige_mac_menu_add_app_menu_group ();
305     // ige_mac_menu_add_app_menu_item (group, GTK_MENU_ITEM (quit_item), NULL);
306 #endif
307 
308     vpaned = gtk_vpaned_new ();
309     gtk_box_pack_start (GTK_BOX (vbox), vpaned, TRUE, TRUE, 2);
310     gtk_widget_show (vpaned);
311 
312     hpaned = gtk_hpaned_new ();
313     gtk_paned_add1 (GTK_PANED (vpaned), hpaned);
314     gtk_widget_show (hpaned);
315 
316     // Accelerators
317     gtk_window_add_accel_group (GTK_WINDOW (wnd), accel_group);
318     gtk_widget_realize (wnd);
319 
320     // Tree
321     tree_ = new GuiTree (hpaned);
322 
323     // Drawing Area
324     graph_ = new GuiGraph (hpaned);
325 
326     // List
327     list_ = new GuiList (vpaned);
328 
329     // Status bars
330     hbox = gtk_hbox_new (FALSE, 0);
331     g_object_ref (hbox);
332     g_object_set_data_full (G_OBJECT (wnd), "hbox", hbox, (GDestroyNotify)  g_object_unref);
333     gtk_widget_show (hbox);
334     gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, TRUE, 0);
335     gtk_widget_set_size_request (hbox, -1, 20);
336 
337     // help message
338     g_object_ref (status_help);
339     g_object_set_data_full (G_OBJECT (wnd), "status_help", status_help, (GDestroyNotify)  g_object_unref);
340     gtk_widget_show (status_help);
341     gtk_box_pack_start (GTK_BOX (hbox), status_help, TRUE, TRUE, 0);
342     gtk_widget_set_size_request (status_help, -1, 20);
343 
344     // program mode
345     status_mode = gtk_statusbar_new ();
346     g_object_ref (status_mode);
347     g_object_set_data_full (G_OBJECT (wnd), "status_mode", status_mode, (GDestroyNotify)  g_object_unref);
348     gtk_widget_show (status_mode);
349     gtk_box_pack_start (GTK_BOX (hbox), status_mode, FALSE, TRUE, 0);
350     gtk_widget_set_size_request (status_mode, 150, -1);
351 
352     // Display MainWindow
353     gtk_widget_show (wnd);
354 
355     // set the programm mode
356     setMode (IDLE);
357 
358     // Display welcome message
359     message->display (1000);
360 
361     // get the current working directory
362     directory_ = g_get_current_dir ();
363 
364     // init the resources we will need
365     GuiResources::init (graph_->drawingArea ());
366 
367     clear ();
368 }
369 
370 // dtor
~GuiDlgedit()371 GuiDlgedit::~GuiDlgedit ()
372 {
373     // cleanup if in preview mode
374     if (mode_ == L10N_PREVIEW)
375     {
376         unlink ("/tmp/locale/xy/LC_MESSAGES/preview.mo");
377         rmdir ("/tmp/locale/xy/LC_MESSAGES");
378         rmdir ("/tmp/locale/xy");
379         rmdir ("/tmp/locale/");
380     }
381 }
382 
383 // starts a new dialogue
newDialogue()384 void GuiDlgedit::newDialogue ()
385 {
386     // the new dialogue
387     DlgModule *module = initDialogue ("untitled");
388 
389     // Display the dialogue
390     showDialogue (module);
391 }
392 
393 // open recent file
OnRecentFileActivated(const std::string & file)394 void GuiDlgedit::OnRecentFileActivated (const std::string & file)
395 {
396     loadDialogue (file);
397 }
398 
399 // load a new dialogue
loadDialogue(const std::string & f)400 void GuiDlgedit::loadDialogue (const std::string &f)
401 {
402     // make sure that file has an absolute path
403     std::string file = ((f[0] == '/' || f[1] == ':') ? f : directory_ + std::string ("/") + f);
404     gchar *fname = g_path_get_basename (file.c_str ());
405 
406     // test if we have a valid dialogue
407     if (!checkDialogue (file))
408     {
409         message->display (-2, fname);
410         g_free(fname);
411         return;
412     }
413 
414     // remember the current directory for later use
415     directory_ = g_dirname (file.c_str ());
416 
417     // get the name to use for the dialogue
418     std::string filename (fname);
419 
420     // remove file extension
421     unsigned long pos = filename.rfind (FILE_EXT);
422     if (pos != filename.npos) filename.erase (pos);
423 
424     // the new dialogue
425     DlgModule *module = initDialogue (filename);
426 
427     // try to load from file
428     if (!module->load ())
429     {
430         message->display (-3, filename.c_str ());
431         closeDialogue ();
432     }
433 
434     // Display the dialogue
435     else
436     {
437         // update list of previously opened files
438         RecentFiles->registerFile(file, MIME_TYPE);
439 
440         message->display (200);
441         showDialogue (module, true);
442     }
443 
444     g_free(fname);
445 }
446 
447 // load a sub-dialogue
loadSubdialogue(const std::string & file)448 DlgModule* GuiDlgedit::loadSubdialogue (const std::string &file)
449 {
450     // test if we have a valid dialogue
451     if (!checkDialogue (file)) return NULL;
452 
453     // remember the current directory for later use
454     directory_ = g_dirname (file.c_str ());
455 
456     // get the name to use for the dialogue
457     gchar *fname = g_path_get_basename (file.c_str ());
458     std::string filename (fname);
459     g_free (fname);
460 
461     // remove file extension
462     unsigned long pos = filename.rfind (FILE_EXT);
463     if (pos != filename.npos) filename.erase (pos);
464 
465     // the sub-dialogue
466     DlgModule *module = new DlgModule (directory_, filename, "", "");
467 
468     // parser needs to read from sub-dialogue source file
469     parser_switch_input ();
470 
471     // try to load from file
472     if (!module->load ())
473     {
474         delete module;
475         parser_restore_input ();
476         return NULL;
477     }
478 
479     // return the sub-dialogue
480     return module;
481 }
482 
483 // revert Dialogue to state on disk
revertDialogue()484 void GuiDlgedit::revertDialogue ()
485 {
486     DlgModule *module = graph_->dialogue ();
487     if (module == NULL) return;
488 
489     // check whether dialogue (still) exists on disk
490     if (!checkDialogue (module->fullName ()))
491     {
492         gtk_widget_set_sensitive (menuItem[REVERT], FALSE);
493         message->display (-2, module->name ().c_str ());
494         return;
495     }
496 
497     // cleanup
498     module->clear ();
499 
500     // reload
501     if (!module->load ())
502     {
503         message->display (-3, module->name ().c_str ());
504         closeDialogue ();
505         return;
506     }
507 
508     // redisplay
509     graph_->detachModule ();
510     tree_->updateModule (module);
511     graph_->attachModule (module);
512 }
513 
514 // save a dialogue
saveDialogue(const std::string & file)515 void GuiDlgedit::saveDialogue (const std::string &file)
516 {
517     DlgModule *module = graph_->dialogue ();
518     if (module == NULL) return;
519 
520     // remember the current directory for later use
521     directory_ = g_dirname (file.c_str ());
522 
523     // get the filename
524     gchar *fname = g_path_get_basename (file.c_str ());
525     std::string filename (fname);
526     g_free (fname);
527 
528     // remove file extension
529     unsigned long pos = filename.rfind (FILE_EXT);
530     if (pos != filename.npos) filename.erase (pos);
531 
532     // try to save file
533     if (!module->save (directory_, filename))
534         message->display (-4, filename.c_str ());
535     else
536     {
537         message->display (201);
538 
539         // update list of previously opened files
540         RecentFiles->registerFile(file, MIME_TYPE);
541 
542         // update 'Revert to Saved' menu item
543         gtk_widget_set_sensitive (menuItem[REVERT], TRUE);
544 
545         // update the dialogue's name in case it has changed
546         tree_->setName (module);
547         initMenu ();
548     }
549 }
550 
551 // close the dialogue being displayed
closeDialogue()552 void GuiDlgedit::closeDialogue ()
553 {
554     DlgModule *module = graph_->dialogue ();
555     if (module == NULL) return;
556 
557     // check whether dialogue has been saved
558     // if (module->hasChanged ())
559 
560     // remove the dialogue from the list of open dialogues
561     dialogues_.erase (remove (dialogues_.begin (), dialogues_.end (), module), dialogues_.end ());
562 
563     // detach module
564     graph_->detachModule ();
565 
566     // if another dialogue is open, display that one
567     if (dialogues_.size () > 0) showDialogue (dialogues_.front ());
568     // otherwise just clear the GUI
569     else clear ();
570 
571     // rebuild the 'windows' menu
572     initMenu ();
573 
574     // delete the dialogue
575     delete module;
576 }
577 
578 // display a certain dialogue
showDialogue(DlgModule * module,bool center)579 void GuiDlgedit::showDialogue (DlgModule *module, bool center)
580 {
581     // remove the current module from the view
582     graph_->detachModule ();
583 
584     // update the tree view
585     // NOTE that this method does some magic: it will select and attach
586     // the sub-dialogue of 'module' that has been viewed before. In that
587     // case, 'module' must not be attached, as it is the toplevel dialogue.
588     tree_->addModule (module);
589 
590     // attach the dialogue to the view
591     // In case of a newly created or (re)loaded 'module', none of it's
592     // sub-dialogues will have been in the view. Therefore, the call
593     // above can't attach anything. Therefore we can attach 'module'.
594     if (!graph_->getAttached ()) graph_->attachModule (module, center);
595 
596     // update the custom code entry if neccessary
597     if (GuiCode::dialog != NULL)
598         GuiCode::dialog->display (module->entry (), module->shortName ());
599 
600     // update the settings if neccessary
601     if (GuiSettings::dialog != NULL)
602         GuiSettings::dialog->display (module->entry (), module->shortName ());
603 
604     // update 'Revert to Saved' menu item
605     if (!checkDialogue (module->fullName ()))
606         gtk_widget_set_sensitive (menuItem[REVERT], FALSE);
607     else
608         gtk_widget_set_sensitive (menuItem[REVERT], TRUE);
609 
610     // update the window title
611     initTitle ();
612 }
613 
614 // compile a dialogue
compileDialogue()615 void GuiDlgedit::compileDialogue ()
616 {
617     DlgModule *module = graph_->dialogue ();
618     if (module == NULL) return;
619 
620     // init the compiler
621     DlgCompiler compiler (module);
622 
623     // compile
624     compiler.run ();
625 
626     // enable the 'run' menuitem after successful compilation
627     gtk_widget_set_sensitive (menuItem[RUN], TRUE);
628 
629     // report success
630     message->display (212);
631 }
632 
633 // edit the genral dialogu settings
settings()634 void GuiDlgedit::settings ()
635 {
636     DlgModule *module = graph_->dialogue ();
637     if (module == NULL) return;
638 
639     // if the dialog isn't already open ...
640     if (GuiSettings::dialog == NULL)
641         GuiSettings::dialog = new GuiSettings ();
642 
643     // otherwise just show it
644     std::string project = module->entry ()->project ();
645     GuiSettings::dialog->display (module->entry (), module->shortName ());
646 }
647 
648 // edit custom code of current module
customCode()649 void GuiDlgedit::customCode ()
650 {
651     DlgModule *module = graph_->dialogue ();
652     if (module == NULL) return;
653 
654     // if the dialog isn't already open ...
655     if (GuiCode::dialog == NULL)
656         GuiCode::dialog = new GuiCode ();
657 
658     // otherwise just show it
659     GuiCode::dialog->display (module->entry (), module->shortName ());
660 }
661 
662 // preview the translated dialogue
previewTranslation(const std::string & catalogue)663 void GuiDlgedit::previewTranslation (const std::string &catalogue)
664 {
665     DlgModule *module = graph_->dialogue ();
666     if (module == NULL) return;
667 
668     // check if we have a proper catalogue
669     if (strncmp (catalogue.substr (catalogue.length ()-3).c_str (), ".mo", 3))
670     {
671         gchar *cname = g_path_get_basename (catalogue.c_str ());
672         message->display (-130, cname);
673         g_free (cname);
674         return;
675     }
676 
677     // see if the file exists at all
678     FILE *exists = g_fopen (catalogue.c_str (), "rb");
679 
680     if (!exists)
681     {
682         gchar *cname = g_path_get_basename (catalogue.c_str ());
683         message->display (-2, cname);
684         g_free (cname);
685         return;
686     }
687     // if it does, check magic number of catalogue file
688     else
689     {
690         unsigned int magic;
691 
692         // read magic number
693         fread (&magic, 4, 1, exists);
694         fclose (exists);
695 
696         if (magic != 0x950412de)
697         {
698             gchar *cname = g_path_get_basename (catalogue.c_str ());
699             message->display (-130, cname);
700             g_free (cname);
701             return;
702         }
703     }
704 
705     // create temporary locale directory
706 #ifndef WIN32
707     if (mkdir ("/tmp/locale/", 0750) ||
708         mkdir ("/tmp/locale/xy", 0750) ||
709         mkdir ("/tmp/locale/xy/LC_MESSAGES", 0750))
710 #else
711     if (mkdir ("tmp/locale/") ||
712         mkdir ("tmp/locale/xy") ||
713         mkdir ("tmp/locale/xy/LC_MESSAGES"))
714 #endif
715     {
716         message->display (-131);
717         return;
718     }
719 
720 #ifndef WIN32
721     // create a symlink to the given catalogue
722     symlink (catalogue.c_str (), "/tmp/locale/xy/LC_MESSAGES/preview.mo");
723 
724     // set the language to use
725     setenv ("LANGUAGE", "xy", 1);
726 #else
727     // create a symlink to the given catalogue
728     // VISTA only: CreateSymbolicLink ()
729 
730     // set the language to use
731     SetEnvironmentVariable ("LANGUAGE", "xy");
732 #endif
733 
734 #ifdef ENABLE_NLS
735     setlocale(LC_MESSAGES, "xy");
736 
737     {
738         // tell gettext that the language has changed
739         extern int _nl_msg_cat_cntr;
740         ++_nl_msg_cat_cntr;
741     }
742 #endif
743 
744     // open the catalogue
745     bindtextdomain ("preview", "/tmp/locale");
746     textdomain ("preview");
747 
748     // deselect selected node, if any
749     DlgNode *node = module->deselectNode ();
750 
751     // update menuitem
752     gtk_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN(menuItem[PREVIEW]))), "Exit Preview mode");
753 
754     // set program mode
755     setMode (L10N_PREVIEW);
756     message->display (130);
757 
758     // reselect node with proper translation
759     if (node != NULL) module->selectNode (node);
760 }
761 
762 // stop preview mode
exitPreview()763 void GuiDlgedit::exitPreview ()
764 {
765     DlgModule *module = graph_->dialogue ();
766 
767     // clear the program mode
768     setMode (NUM_MODES);
769 
770     // restore the program mode
771     if (module != NULL)
772     {
773         setMode (module->state ());
774 
775         // deselect selected node, if any
776         DlgNode *node = module->deselectNode ();
777 
778         // reselect node without translation
779         if (node != NULL) module->selectNode (node);
780     }
781 
782     // update menuitem
783     gtk_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN(menuItem[PREVIEW]))), "Preview Translation");
784 
785     // cleanup
786     unlink ("/tmp/locale/xy/LC_MESSAGES/preview.mo");
787     rmdir ("/tmp/locale/xy/LC_MESSAGES");
788     rmdir ("/tmp/locale/xy");
789     rmdir ("/tmp/locale/");
790 
791     // clear the statusbar
792     message->clear ();
793 }
794 
setChanged()795 void GuiDlgedit::setChanged ()
796 {
797     // update tree
798     tree_->setChanged (graph_->dialogue ());
799 
800     // update title bar
801     initTitle ();
802 }
803 
updateProject()804 void GuiDlgedit::updateProject ()
805 {
806     // update tree
807     tree_->updateProject (graph_->dialogue ());
808 }
809 
checkDialogue(const std::string & file)810 bool GuiDlgedit::checkDialogue (const std::string &file)
811 {
812     // first, open the file
813     FILE *test = g_fopen (file.c_str (), "rb");
814 
815     if (!test)
816         return false;
817 
818     // check if it's a regular file
819     struct stat statbuf;
820     fstat (fileno (test), &statbuf);
821     if (!S_ISREG (statbuf.st_mode))
822     {
823         fclose (test);
824         return false;
825     }
826 
827     loadlgin = test;
828     return true;
829 }
830 
initDialogue(std::string name)831 DlgModule *GuiDlgedit::initDialogue (std::string name)
832 {
833     // serial number
834     std::string serial = g_strdup_printf ("-%i", ++number);
835 
836     // the new dialogue
837     DlgModule *dlg = new DlgModule (directory_, name, serial, "New Dialogue");
838 
839     // set project if dlgedit started with '-j' option
840     dlg->entry()->setProject (DlgCmdline::project);
841 
842     // insert into the list of open dialogues
843     dialogues_.push_back (dlg);
844 
845     // rebuild the 'Window' menu
846     initMenu ();
847 
848     // activate all dialogue related menu-items
849     gtk_widget_set_sensitive (menuItem[SAVE], TRUE);
850     gtk_widget_set_sensitive (menuItem[SAVE_AS], TRUE);
851     gtk_widget_set_sensitive (menuItem[CLOSE], TRUE);
852     gtk_widget_set_sensitive (menuItem[SETTINGS], TRUE);
853     gtk_widget_set_sensitive (menuItem[FUNCTIONS], TRUE);
854     gtk_widget_set_sensitive (menuItem[COMPILE], TRUE);
855 #ifdef ENABLE_NLS
856     gtk_widget_set_sensitive (menuItem[PREVIEW], TRUE);
857 #endif
858 
859     return dlg;
860 }
861 
862 // sets the window title
initTitle()863 void GuiDlgedit::initTitle ()
864 {
865     gchar *title = (char*) "Adonthell Dialogue Editor v" _VERSION_;
866     DlgModule *module = graph_->dialogue ();
867 
868     if (module != NULL)
869     {
870         if (module->changed ())
871             title = g_strjoin (NULL, title, " - [",
872                 module->shortName ().c_str (), " (modified)]", NULL);
873         else
874             title = g_strjoin (NULL, title, " - [",
875                 module->shortName ().c_str (), "]", NULL);
876     }
877     gtk_window_set_title (GTK_WINDOW (wnd), title);
878 }
879 
880 // init the 'windows' menu
initMenu()881 void GuiDlgedit::initMenu ()
882 {
883     // first, remove everything from the menu
884     gtk_container_foreach (GTK_CONTAINER (windowMenu), (GtkCallback)gtk_widget_destroy, NULL);
885 
886     // Now rebuild the menu if any dialogues remain
887     if (dialogues_.size () == 0) return;
888 
889     int position = 0;
890     std::vector<DlgModule*>::iterator i;
891     // GtkAccelGroup *accel_group = gtk_accel_group_get_default ();
892     GtkWidget *menuitem;
893 
894     for (i = dialogues_.begin (); i != dialogues_.end (); i++, position++)
895     {
896         menuitem = gtk_menu_item_new_with_label ((*i)->shortName ().c_str ());
897         gtk_container_add (GTK_CONTAINER (windowMenu), menuitem);
898         g_signal_connect (G_OBJECT (menuitem), "activate", G_CALLBACK (on_window_activate), (gpointer) *i);
899         g_object_set_data (G_OBJECT (menuitem), "help-id", GINT_TO_POINTER (20));
900         g_signal_connect (G_OBJECT (menuitem), "enter-notify-event", G_CALLBACK (on_display_help), message);
901         g_signal_connect (G_OBJECT (menuitem), "leave-notify-event", G_CALLBACK (on_clear_help), message);
902         gtk_widget_show (menuitem);
903 
904         // Menu Accelerators
905         // if (position < 9)
906         //    gtk_widget_add_accelerator (menuitem, "activate", accel_group,
907         //        GDK_1 + position, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
908     }
909 
910 #ifdef MAC_INTEGRATION
911     ige_mac_menu_sync(GTK_MENU_SHELL(mainMenu));
912 #endif
913 }
914 
clear()915 void GuiDlgedit::clear ()
916 {
917     // update the window title
918     initTitle ();
919 
920     // make the various menu-items insensitive
921     gtk_widget_set_sensitive (menuItem[SAVE], FALSE);
922     gtk_widget_set_sensitive (menuItem[SAVE_AS], FALSE);
923     gtk_widget_set_sensitive (menuItem[REVERT], FALSE);
924     gtk_widget_set_sensitive (menuItem[CLOSE], FALSE);
925     gtk_widget_set_sensitive (menuItem[SETTINGS], FALSE);
926     gtk_widget_set_sensitive (menuItem[FUNCTIONS], FALSE);
927     gtk_widget_set_sensitive (menuItem[COMPILE], FALSE);
928 #ifdef ENABLE_NLS
929     gtk_widget_set_sensitive (menuItem[PREVIEW], FALSE);
930 #endif
931     gtk_widget_set_sensitive (menuItem[RUN], FALSE);
932 
933     // empty the graph and list widget
934     graph_->clear ();
935     list_->clear ();
936 }
937 
setMode(mode_type mode)938 void GuiDlgedit::setMode (mode_type mode)
939 {
940     const char *text;
941 
942     // get the string representing the current program state
943     if (mode < IDLE || mode >= NUM_MODES)
944     {
945         text = " INVALID MODE";
946         mode_ = IDLE;
947     }
948     else
949     {
950         // ignore everything else as long as we are in preview mode
951         if (mode_ == L10N_PREVIEW) return;
952 
953         text = progState[mode];
954         mode_ = mode;
955     }
956 
957     // some context id the statusbar needs for some reason
958     int id = gtk_statusbar_get_context_id (GTK_STATUSBAR (status_mode), "Mode");
959 
960     // remove the previous message
961     gtk_statusbar_pop (GTK_STATUSBAR (status_mode), id);
962 
963     // add the new status
964     gtk_statusbar_push (GTK_STATUSBAR (status_mode), id, text);
965 }
966 
967 // get the full path/name/extension of a dialogue
filename()968 std::string GuiDlgedit::filename ()
969 {
970     DlgModule *module = graph_->dialogue ();
971 
972     return module->fullName ();
973 }
974