1 /*
2 * File: doc_man.c
3 *
4 * Description: handles document switching, the 'Documents' menu, dialogs
5 * for loading/saving models, etc
6 *
7 *
8 * This source code is part of kludge3d, and is released under the
9 * GNU General Public License.
10 *
11 *
12 */
13
14
15 #include <string.h>
16
17 #include "doc_man.h"
18 #include "undo.h" /* for action_clear_stack(), etc */
19 #include "bottombar.h"
20 #include "model_load_save.h"
21 #include "documents.h"
22 #include "gui.h"
23
24 #ifdef MEMWATCH
25 #include "memwatch.h"
26 #endif
27
28
29 /* STRUCTS ****************************************************************/
30
31 /* load/save dialog internals */
32 struct fmt_dialog_data {
33 GtkFileSelection *selector;
34 gint fmt;
35 gint active : 1;
36 gint cancelled : 1;
37 Model *save_me;
38 };
39
40
41 /* PROTOTYPES *************************************************************/
42
43 gint fmt_overwrite_dlg( void );
44
45 void fmt_file_open_set_fmt(GtkWidget *widget, gint fmt_idx);
46 void fmt_file_open_ok(GtkWidget *widget, struct fmt_dialog_data *fdd);
47 void fmt_file_open_cancel(GtkWidget *widget, struct fmt_dialog_data *fdd);
fmt_file_open_cancel_2(GtkWidget * widget,GdkEvent * event,struct fmt_dialog_data * fdd)48 void fmt_file_open_cancel_2(GtkWidget *widget, GdkEvent *event,
49 struct fmt_dialog_data *fdd) {
50 fmt_file_open_cancel(widget, fdd);
51 }
52
53 void fmt_file_save_set_fmt(GtkWidget *widget, gint fmt_idx);
54 void fmt_file_save_ok(GtkWidget *widget, struct fmt_dialog_data *fdd);
55 void fmt_file_save_cancel(GtkWidget *widget, struct fmt_dialog_data *fdd);
fmt_file_save_cancel_2(GtkWidget * widget,GdkEvent * event,struct fmt_dialog_data * fdd)56 void fmt_file_save_cancel_2(GtkWidget *widget, GdkEvent *event,
57 struct fmt_dialog_data *fdd) {
58 fmt_file_save_cancel(widget, fdd);
59 }
60
61 gint fmt_load_dialog_available( void );
62 gint fmt_save_dialog_available( void );
63
64 void doc_man_add_model( Model *m ) ;
65 void doc_man_remove_model( Model *m ) ;
66 int doc_man_add_model_cb( gpointer no, Model *m, gpointer data ) ;
67 int doc_man_remove_model_cb( gpointer no, Model *m, gpointer data ) ;
68 int doc_man_switched_model_cb( gpointer no, Model *m, gpointer data ) ;
69 void doc_man_menutoggle_cb(
70 GtkCheckMenuItem *checkmenuitem, gpointer data );
71
72
73 /* FILE-SCOPE VARS ********************************************************/
74
75 struct fmt_dialog_data open_fdd[] = { { NULL, -1, FALSE, FALSE, NULL } };
76 struct fmt_dialog_data save_fdd[] = { { NULL, -1, FALSE, FALSE, NULL } };
77
78 GtkWidget *docmenu = NULL;
79 GSList *docmenu_radiobtn_group = NULL;
80 GHashTable *docmenu_hashtable = NULL;
81
82 /* FUNCS ******************************************************************/
83
84 /* LOAD DIALOG STUFF ****************************************************/
85
fmt_show_load_dialog(gchar * box_title)86 gint fmt_show_load_dialog(gchar *box_title)
87 {
88 const gchar *filename;
89 int result = FALSE;
90 Model *new_model = NULL;
91
92 if(open_fdd->selector == NULL)
93 {
94 int i = 0;
95 GtkWidget *widget, *hbox, *menu, *mi;
96
97 open_fdd->selector = (GtkFileSelection*) gtk_file_selection_new(NULL);
98
99 g_signal_connect(G_OBJECT(open_fdd->selector), "delete_event",
100 (GtkSignalFunc)fmt_file_open_cancel_2, open_fdd);
101 g_signal_connect(G_OBJECT(open_fdd->selector->ok_button),
102 "clicked", (GtkSignalFunc)fmt_file_open_ok, open_fdd);
103 g_signal_connect(G_OBJECT(open_fdd->selector->cancel_button),
104 "clicked", (GtkSignalFunc)fmt_file_open_cancel, open_fdd);
105
106 /* now build the menu */
107 menu = gtk_menu_new();
108 mi = gtk_menu_item_new_with_label("Use Extension");
109 g_signal_connect(G_OBJECT(mi), "activate",
110 G_CALLBACK(fmt_file_open_set_fmt), (gpointer) -1);
111 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
112 gtk_widget_show(mi);
113
114 mi = gtk_menu_item_new();
115 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
116 gtk_widget_set_sensitive(mi, FALSE);
117 gtk_widget_show(mi);
118
119 for(i = 0; fmt_list[i] != NULL; i++)
120 {
121 if(fmt_list[i]->load_file == NULL)
122 continue;
123 mi = gtk_menu_item_new_with_label(fmt_list[i]->descr);
124 g_signal_connect(G_OBJECT(mi), "activate",
125 G_CALLBACK(fmt_file_open_set_fmt), (gpointer) i);
126 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
127 gtk_widget_show(mi);
128 }
129
130 /* and the selector box infrastructure */
131 hbox = gtk_hbox_new(FALSE, 0);
132 widget = gtk_label_new("File Type:");
133 gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, FALSE, 0);
134 gtk_widget_show(widget);
135 widget = gtk_option_menu_new();
136 gtk_option_menu_set_menu(GTK_OPTION_MENU(widget), menu);
137 gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, FALSE, 0);
138 gtk_widget_show(widget);
139
140 gtk_box_pack_end(GTK_BOX(GTK_FILE_SELECTION(open_fdd->selector)->main_vbox),
141 hbox, FALSE, FALSE, 0);
142 gtk_widget_show(hbox);
143 }
144
145 if(open_fdd->active)
146 return FALSE;
147
148 open_fdd->active = TRUE;
149
150 if(box_title == NULL)
151 gtk_window_set_title(GTK_WINDOW(open_fdd->selector), "Open File");
152 else
153 gtk_window_set_title(GTK_WINDOW(open_fdd->selector), box_title);
154
155 gtk_widget_show(GTK_WIDGET(open_fdd->selector));
156
157 /* file open dialog box loop */
158 while(1) {
159 gtk_main();
160
161 /* examine the structure */
162 if(open_fdd->cancelled)
163 break;
164
165 filename = gtk_file_selection_get_filename(open_fdd->selector);
166 gtk_widget_set_sensitive(GTK_WIDGET(open_fdd->selector), FALSE);
167
168 new_model = model_load(filename, open_fdd->fmt);
169
170 if( new_model == NULL ) {
171 bb_push_message_f( 3.0, "Failed to load file %s", filename );
172 } else {
173 docs_add_model( new_model );
174 docs_switch_model( new_model );
175 result = TRUE;
176
177 bb_push_message_f( 2.5, "Loaded file %s", filename );
178 break;
179 }
180
181 gtk_widget_set_sensitive(GTK_WIDGET(open_fdd->selector), TRUE);
182 }
183
184 gtk_widget_hide(GTK_WIDGET(open_fdd->selector));
185 gtk_widget_set_sensitive(GTK_WIDGET(open_fdd->selector), TRUE);
186 open_fdd->active = FALSE;
187
188 //fixme - remove these, not needed
189 g_signal_emit_by_name( notificationObj,
190 "notify::model-structure-changed", NULL );
191 g_signal_emit_by_name( notificationObj,
192 "notify::model-appearance-changed", NULL );
193 g_signal_emit_by_name( notificationObj,
194 "notify::model-texturelist-changed", NULL );
195
196 return result;
197 }
198
fmt_file_open_ok(GtkWidget * widget,struct fmt_dialog_data * fdd)199 void fmt_file_open_ok(GtkWidget *widget, struct fmt_dialog_data *fdd)
200 {
201 fdd->cancelled = FALSE;
202 gtk_main_quit();
203 }
204
fmt_file_open_cancel(GtkWidget * widget,struct fmt_dialog_data * fdd)205 void fmt_file_open_cancel(GtkWidget *widget, struct fmt_dialog_data *fdd)
206 {
207 fdd->cancelled = TRUE;
208 gtk_main_quit();
209 }
210
fmt_file_open_set_fmt(GtkWidget * widget,gint fmt_idx)211 void fmt_file_open_set_fmt(GtkWidget *widget, gint fmt_idx)
212 {
213 open_fdd->fmt = fmt_idx;
214 }
215
fmt_load_dialog_available(void)216 gint fmt_load_dialog_available( void )
217 {
218 if(open_fdd->active == FALSE)
219 return TRUE;
220
221 return FALSE;
222 }
223
224
225 /* SAVE DIALOG STUFF ****************************************************/
226
fmt_show_save_dialog(Model * doc,gchar * box_title)227 gint fmt_show_save_dialog( Model *doc, gchar *box_title ) {
228 int file_exists;
229 if( doc->fname )
230 if( model_save( doc, doc->fname, TRUE, &file_exists ) == FALSE ) {
231 bb_push_message_f( 3.0, "Failed to save to file %s", doc->fname );
232 } else {
233 bb_push_message_f( 2.5, "Saved to file %s", doc->fname );
234 }
235 else
236 fmt_show_saveAs_dialog( doc, box_title );
237 return TRUE;
238 }
239
240
fmt_show_saveAs_dialog(Model * doc,gchar * box_title)241 gint fmt_show_saveAs_dialog(Model *doc, gchar *box_title)
242 {
243 const gchar *filename;
244 gint ret = FALSE;
245
246 if(save_fdd->selector == NULL)
247 {
248 int i = 0;
249 GtkWidget *widget, *hbox, *menu, *mi;
250
251 save_fdd->selector = (GtkFileSelection*) gtk_file_selection_new(NULL);
252
253 g_signal_connect(G_OBJECT(save_fdd->selector), "delete_event",
254 (GtkSignalFunc)fmt_file_save_cancel_2, save_fdd);
255 g_signal_connect(G_OBJECT(save_fdd->selector->ok_button),
256 "clicked", (GtkSignalFunc)fmt_file_save_ok, save_fdd);
257 g_signal_connect(G_OBJECT(save_fdd->selector->cancel_button),
258 "clicked", (GtkSignalFunc)fmt_file_save_cancel, save_fdd);
259
260 /* now build the menu */
261 menu = gtk_menu_new();
262 mi = gtk_menu_item_new_with_label("Use Extension");
263 g_signal_connect(G_OBJECT(mi), "activate",
264 G_CALLBACK(fmt_file_save_set_fmt), (gpointer) -1);
265 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
266 gtk_widget_show(mi);
267
268 mi = gtk_menu_item_new();
269 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
270 gtk_widget_set_sensitive(mi, FALSE);
271 gtk_widget_show(mi);
272
273 for(i = 0; fmt_list[i] != NULL; i++)
274 {
275 if(fmt_list[i]->save_file == NULL)
276 continue;
277 mi = gtk_menu_item_new_with_label(fmt_list[i]->descr);
278 g_signal_connect(G_OBJECT(mi), "activate",
279 G_CALLBACK(fmt_file_save_set_fmt), (gpointer) i);
280 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi);
281 gtk_widget_show(mi);
282 }
283
284 /* and the selector box infrastructure */
285 hbox = gtk_hbox_new(FALSE, 0);
286 widget = gtk_label_new("File Type:");
287 gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, FALSE, 0);
288 gtk_widget_show(widget);
289 widget = gtk_option_menu_new();
290 gtk_option_menu_set_menu(GTK_OPTION_MENU(widget), menu);
291 gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, FALSE, 0);
292 gtk_widget_show(widget);
293
294 gtk_box_pack_end(GTK_BOX(GTK_FILE_SELECTION(save_fdd->selector)->main_vbox),
295 hbox, FALSE, FALSE, 0);
296 gtk_widget_show(hbox);
297 }
298
299 if(save_fdd->active)
300 return FALSE;
301
302 save_fdd->active = TRUE;
303 save_fdd->save_me = doc;
304
305 if(box_title == NULL)
306 gtk_window_set_title(GTK_WINDOW(save_fdd->selector), "Save File");
307 else
308 gtk_window_set_title(GTK_WINDOW(save_fdd->selector), box_title);
309
310 // view_set_sensitive_docwide(save_fdd->save_me, FALSE);
311 gtk_widget_show(GTK_WIDGET(save_fdd->selector));
312
313 while(1) {
314 int file_exists = FALSE;
315 gtk_main();
316
317 if(save_fdd->cancelled)
318 break;
319
320 filename = gtk_file_selection_get_filename(save_fdd->selector);
321
322 /* adapt to a potential change in save format */
323 /* the idea is that if this function gets called, the user wants */
324 /* to set the misc data over again anyway */
325 model_ls_clean_miscdata(save_fdd->save_me);
326 save_fdd->save_me->fmt_idx = save_fdd->fmt;
327
328 if(model_save(save_fdd->save_me, filename, FALSE, &file_exists) == FALSE) {
329 /* if the file exists, ask the user if it is ok to overwrite,
330 then try to save again */
331 if( file_exists ) {
332 if( fmt_overwrite_dlg() == FALSE ) {
333 bb_push_message_f( 3.0, "Canceled save: file already exists" );
334 } else {
335 if( model_save( save_fdd->save_me, filename, TRUE,
336 &file_exists) == FALSE)
337 {
338 /* save still didn't succeed */
339 bb_push_message_f( 3.0, "Failed to save to file %s", filename );
340 }
341 }
342 } else {
343 bb_push_message_f( 3.0, "Failed to save to file %s", filename );
344 }
345 } else {
346 ret = TRUE;
347 bb_push_message_f( 2.5, "Saved to file %s", filename );
348 break;
349 }
350
351 gtk_widget_set_sensitive(GTK_WIDGET(save_fdd->selector), TRUE);
352 }
353
354 gtk_widget_hide(GTK_WIDGET(save_fdd->selector));
355 gtk_widget_set_sensitive(GTK_WIDGET(save_fdd->selector), TRUE);
356 // view_set_sensitive_docwide(save_fdd->save_me, TRUE);
357 save_fdd->active = FALSE;
358
359 return ret;
360 }
361
fmt_file_save_ok(GtkWidget * widget,struct fmt_dialog_data * fdd)362 void fmt_file_save_ok(GtkWidget *widget, struct fmt_dialog_data *fdd)
363 {
364 fdd->cancelled = FALSE;
365 gtk_main_quit();
366 }
367
fmt_file_save_cancel(GtkWidget * widget,struct fmt_dialog_data * fdd)368 void fmt_file_save_cancel(GtkWidget *widget, struct fmt_dialog_data *fdd)
369 {
370 fdd->cancelled = TRUE;
371 gtk_main_quit();
372 }
373
fmt_file_save_set_fmt(GtkWidget * widget,gint fmt_idx)374 void fmt_file_save_set_fmt(GtkWidget *widget, gint fmt_idx)
375 {
376 save_fdd->fmt = fmt_idx;
377 }
378
fmt_save_dialog_available(void)379 gint fmt_save_dialog_available( void )
380 {
381 if(save_fdd->active == FALSE)
382 return TRUE;
383
384 return FALSE;
385 }
386
387
388 /* OVERWRITE DIALOG STUFF *************************************************/
389
fmt_overwrite_dlg(void)390 gint fmt_overwrite_dlg( void )
391 {
392 gint decision = FALSE;
393 GtkWidget *dialog;
394 GtkWidget *label;
395 gint dlg_response;
396
397 dialog = gtk_dialog_new_with_buttons ("Confirm",
398 GTK_WINDOW( TopLevelWindow ),
399 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
400 GTK_STOCK_YES,
401 GTK_RESPONSE_OK,
402 GTK_STOCK_NO,
403 GTK_RESPONSE_CANCEL,
404 NULL);
405
406 label = gtk_label_new("File Exists. Overwrite?");
407 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label, TRUE, TRUE, 0);
408
409 /* set up the OK button as the default widget */
410 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_OK );
411
412 /* shows the dialog, and since it's modal, prevents rest of
413 kludge3d from doing stuff */
414 gtk_widget_show_all (dialog);
415
416 /* runs a gtkmain until the user clicks on something */
417 dlg_response = gtk_dialog_run( GTK_DIALOG (dialog) );
418
419 switch( dlg_response ) {
420 case GTK_RESPONSE_OK:
421 decision = TRUE;
422 break;
423 default:
424 decision = FALSE;
425 break;
426 }
427
428 gtk_widget_destroy( dialog );
429
430 return decision;
431 }
432
433
434 /* DOCUMENTS MENU *********************************************************/
435
doc_man_create_menu(GtkWidget * parent)436 void doc_man_create_menu( GtkWidget *parent ) {
437 GtkWidget *menuitem;
438 GSList *l;
439
440 docmenu = parent;
441
442 menuitem = gtk_tearoff_menu_item_new();
443 gtk_menu_shell_append( GTK_MENU_SHELL( docmenu ), menuitem );
444 gtk_widget_show( menuitem );
445
446 for( l = models; l; l = l->next ) {
447 doc_man_add_model( (Model*)l->data );
448 }
449
450 /* listen for added-model signal */
451 g_signal_connect( notificationObj, "added-model",
452 G_CALLBACK(doc_man_add_model_cb), NULL );
453 /* listen for removed-model signal */
454 g_signal_connect( notificationObj, "removed-model",
455 G_CALLBACK(doc_man_remove_model_cb), NULL );
456 /* listen for switched-model signal */
457 g_signal_connect( notificationObj, "switched-model",
458 G_CALLBACK(doc_man_switched_model_cb), NULL );
459 }
460
461
doc_man_add_model(Model * m)462 void doc_man_add_model( Model *m ) {
463 GtkWidget *menuitem;
464
465 menuitem = gtk_radio_menu_item_new_with_label(
466 docmenu_radiobtn_group, m->name );
467 docmenu_radiobtn_group =
468 gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( menuitem ) );
469 gtk_menu_shell_append( GTK_MENU_SHELL( docmenu ), menuitem );
470 gtk_widget_show( menuitem );
471
472 g_signal_connect( menuitem, "activate",
473 G_CALLBACK(doc_man_menutoggle_cb), m );
474
475 if( docmenu_hashtable == NULL )
476 docmenu_hashtable = g_hash_table_new( NULL, NULL );
477 g_hash_table_insert( docmenu_hashtable, m, menuitem );
478 }
479
480
doc_man_remove_model(Model * m)481 void doc_man_remove_model( Model *m ) {
482 GtkWidget *menuitem;
483
484 menuitem = g_hash_table_lookup( docmenu_hashtable, m );
485 if( menuitem )
486 gtk_widget_destroy( menuitem );
487
488 /* update the docmenu_radiobtn_group list */
489 if( models == NULL ) {
490 docmenu_radiobtn_group = NULL;
491 } else {
492 /* use the first model to look up one of the menu items
493 (doesn't matter which) */
494 menuitem = g_hash_table_lookup( docmenu_hashtable,
495 (Model*)models->data );
496 docmenu_radiobtn_group =
497 gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( menuitem ) );
498 }
499
500 /* update the hash table */
501 g_hash_table_remove( docmenu_hashtable, m );
502 }
503
504
doc_man_add_model_cb(gpointer no,Model * m,gpointer data)505 int doc_man_add_model_cb( gpointer no, Model *m, gpointer data ) {
506
507 doc_man_add_model( m );
508
509 return FALSE;
510 }
511
doc_man_remove_model_cb(gpointer no,Model * m,gpointer data)512 int doc_man_remove_model_cb( gpointer no, Model *m, gpointer data ) {
513
514 doc_man_remove_model( m );
515
516 return FALSE;
517 }
518
doc_man_switched_model_cb(gpointer no,Model * m,gpointer data)519 int doc_man_switched_model_cb( gpointer no, Model *m, gpointer data ) {
520 GtkWidget *menuitem;
521
522 /* first, look up m in the table */
523 menuitem = g_hash_table_lookup( docmenu_hashtable, m );
524 if( !menuitem )
525 return FALSE;
526
527 /* now make this radiobutton the selected one */
528 if( !gtk_check_menu_item_get_active( GTK_CHECK_MENU_ITEM(menuitem) ) ) {
529 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(menuitem), TRUE );
530 }
531
532 return FALSE;
533 }
534
doc_man_menutoggle_cb(GtkCheckMenuItem * checkmenuitem,gpointer data)535 void doc_man_menutoggle_cb(
536 GtkCheckMenuItem *checkmenuitem, gpointer data )
537 {
538
539 /* bail if this isn't the newly-activated item */
540 if( ! checkmenuitem->active )
541 return;
542
543 docs_switch_model( (Model *)data );
544 }
545
546
547