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