Compilation: clean up dialog including.
[gnumeric.git] / src / application.c
blobcc05afe96d595495f375a205ea810a2bcd0fc6c3
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * application.c: Manage the data common to all workbooks
5 * Author:
6 * Jody Goldberg <jody@gnome.org>
7 */
8 #include <gnumeric-config.h>
9 #include <string.h>
10 #include "gnumeric.h"
11 #include "application.h"
13 #include "clipboard.h"
14 #include "selection.h"
15 #include "workbook-control.h"
16 #include "workbook-view.h"
17 #include "workbook.h"
18 #include "sheet.h"
19 #include "sheet-view.h"
20 #include "sheet-private.h"
21 #include "sheet-object.h"
22 #include "auto-correct.h"
23 #include "gutils.h"
24 #include "ranges.h"
25 #include "sheet-object.h"
26 #include "commands.h"
27 #include "gui-clipboard.h"
28 #include "expr-name.h"
29 #include "workbook-priv.h"
31 #include <gnumeric-conf.h>
32 #include <goffice/goffice.h>
33 #include <gsf/gsf-impl-utils.h>
34 #include "gnm-i18n.h"
35 #include <gtk/gtk.h>
37 #define GNM_APP(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GNM_APP_TYPE, GnmApp))
38 #define GNM_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GNM_APP_TYPE))
40 enum {
41 PROP_0,
42 PROP_HISTORY_LIST,
43 PROP_SHUTTING_DOWN,
44 PROP_INITIAL_OPEN_COMPLETE
46 /* Signals */
47 enum {
48 WORKBOOK_ADDED,
49 WORKBOOK_REMOVED,
50 WINDOW_LIST_CHANGED,
51 CUSTOM_UI_ADDED,
52 CUSTOM_UI_REMOVED,
53 CLIPBOARD_MODIFIED,
54 RECALC_FINISHED,
55 RECALC_CLEAR_CACHES,
56 LAST_SIGNAL
59 static guint signals[LAST_SIGNAL] = { 0 };
61 struct _GnmApp {
62 GObject base;
64 /* Clipboard */
65 SheetView *clipboard_sheet_view;
66 GnmCellRegion *clipboard_copied_contents;
67 GnmRange *clipboard_cut_range;
69 GList *workbook_list;
71 /* Recalculation manager. */
72 int recalc_count;
74 GtkRecentManager *recent;
75 gulong recent_sig;
77 gboolean shutting_down;
78 gboolean initial_open_complete;
81 typedef struct {
82 GObjectClass parent;
84 void (*workbook_added) (GnmApp *gnm_app, Workbook *wb);
85 void (*workbook_removed) (GnmApp *gnm_app, Workbook *wb);
86 void (*window_list_changed) (GnmApp *gnm_app);
87 void (*custom_ui_added) (GnmApp *gnm_app, GnmAppExtraUI *ui);
88 void (*custom_ui_removed) (GnmApp *gnm_app, GnmAppExtraUI *ui);
89 void (*clipboard_modified) (GnmApp *gnm_app);
90 void (*recalc_finished) (GnmApp *gnm_app);
91 void (*recalc_clear_caches) (GnmApp *gnm_app);
92 } GnmAppClass;
94 static GObjectClass *parent_klass;
95 static GnmApp *app;
97 static Workbook *gnm_app_workbook_get_by_uri (char const *uri);
99 /**
100 * gnm_app_get_app:
102 * Returns: (transfer none): the #GnmApp instance.
104 GObject *
105 gnm_app_get_app (void)
107 return G_OBJECT (app);
111 * gnm_app_workbook_list_add:
112 * @wb: A #Workbook
114 * Add @wb to the application's list of workbooks.
116 void
117 gnm_app_workbook_list_add (Workbook *wb)
119 g_return_if_fail (GNM_IS_WORKBOOK (wb));
120 g_return_if_fail (app != NULL);
122 app->workbook_list = g_list_prepend (app->workbook_list, wb);
123 g_signal_connect (G_OBJECT (wb),
124 "notify::uri",
125 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
126 _gnm_app_flag_windows_changed ();
127 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_ADDED], 0, wb);
131 * gnm_app_workbook_list_remove:
132 * @wb: A #Workbook
134 * Remove @wb from the application's list of workbooks.
136 void
137 gnm_app_workbook_list_remove (Workbook *wb)
139 g_return_if_fail (wb != NULL);
140 g_return_if_fail (app != NULL);
142 app->workbook_list = g_list_remove (app->workbook_list, wb);
143 g_signal_handlers_disconnect_by_func (G_OBJECT (wb),
144 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
145 _gnm_app_flag_windows_changed ();
146 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_REMOVED], 0, wb);
150 * gnm_app_workbook_list:
152 * Returns: (element-type Workbook) (transfer none): the workbook list.
154 GList *
155 gnm_app_workbook_list (void)
157 g_return_val_if_fail (app != NULL, NULL);
159 return app->workbook_list;
162 void
163 gnm_app_sanity_check (void)
165 GList *l;
166 gboolean err = FALSE;
167 for (l = gnm_app_workbook_list (); l; l = l->next) {
168 Workbook *wb = l->data;
169 if (gnm_named_expr_collection_sanity_check (wb->names, "workbook"))
170 err = TRUE;
172 if (err)
173 g_error ("Sanity check failed\n");
179 * gnm_app_clipboard_clear:
181 * Clear and free the contents of the clipboard if it is
182 * not empty.
184 void
185 gnm_app_clipboard_clear (gboolean drop_selection)
187 g_return_if_fail (app != NULL);
189 if (app->clipboard_copied_contents) {
190 cellregion_unref (app->clipboard_copied_contents);
191 app->clipboard_copied_contents = NULL;
193 if (app->clipboard_sheet_view != NULL) {
194 gnm_sheet_view_unant (app->clipboard_sheet_view);
196 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
198 gnm_sheet_view_weak_unref (&(app->clipboard_sheet_view));
200 /* Release the selection */
201 if (drop_selection)
202 gnm_x_disown_clipboard ();
206 void
207 gnm_app_clipboard_invalidate_sheet (Sheet *sheet)
209 /* Clear the cliboard to avoid dangling references to the deleted sheet */
210 if (sheet == gnm_app_clipboard_sheet_get ())
211 gnm_app_clipboard_clear (TRUE);
212 else if (app->clipboard_copied_contents)
213 cellregion_invalidate_sheet (app->clipboard_copied_contents, sheet);
216 void
217 gnm_app_clipboard_unant (void)
219 g_return_if_fail (app != NULL);
221 if (app->clipboard_sheet_view != NULL)
222 gnm_sheet_view_unant (app->clipboard_sheet_view);
226 * gnm_app_clipboard_cut_copy:
227 * @wbc: the workbook control that requested the operation.
228 * @is_cut: is this a cut or a copy.
229 * @sv: The source sheet for the copy.
230 * @area: A single rectangular range to be copied.
231 * @animate_range: Do we want ot add an animated cursor around things.
233 * When Cutting we
234 * Clear and free the contents of the clipboard and save the sheet and area
235 * to be cut. DO NOT ACTUALLY CUT! Paste will move the region if this was a
236 * cut operation.
238 * When Copying we
239 * Clear and free the contents of the clipboard and COPY the designated region
240 * into the clipboard.
242 * we need to pass @wbc as a control rather than a simple command-context so
243 * that the control can claim the selection.
245 void
246 gnm_app_clipboard_cut_copy (WorkbookControl *wbc, gboolean is_cut,
247 SheetView *sv, GnmRange const *area,
248 gboolean animate_cursor)
250 Sheet *sheet;
252 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
253 g_return_if_fail (area != NULL);
254 g_return_if_fail (app != NULL);
256 gnm_app_clipboard_clear (FALSE);
257 sheet = sv_sheet (sv);
258 g_free (app->clipboard_cut_range);
259 app->clipboard_cut_range = gnm_range_dup (area);
260 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
262 if (!is_cut)
263 app->clipboard_copied_contents =
264 clipboard_copy_range (sheet, area);
265 if (animate_cursor) {
266 GList *l = g_list_append (NULL, (gpointer)area);
267 gnm_sheet_view_ant (sv, l);
268 g_list_free (l);
271 if (wb_control_claim_selection (wbc)) {
272 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
273 } else {
274 gnm_app_clipboard_clear (FALSE);
275 g_warning ("Unable to set selection?");
280 * gnm_app_clipboard_cut_copy_obj:
281 * @wbc: #WorkbookControl
282 * @is_cut: %TRUE for cut, %FALSE for copy
283 * @sv: #SheetView
284 * @objects: (element-type SheetObject) (transfer container): a list
285 * of #SheetObject
287 * Different than copying/cutting a region, this can actually cut an object
289 void
290 gnm_app_clipboard_cut_copy_obj (WorkbookControl *wbc, gboolean is_cut,
291 SheetView *sv, GSList *objects)
293 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
294 g_return_if_fail (objects != NULL);
295 g_return_if_fail (app != NULL);
297 gnm_app_clipboard_clear (FALSE);
298 g_free (app->clipboard_cut_range);
299 app->clipboard_cut_range = NULL;
300 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
301 app->clipboard_copied_contents
302 = clipboard_copy_obj (sv_sheet (sv), objects);
303 if (is_cut) {
304 cmd_objects_delete (wbc, objects, _("Cut Object"));
305 objects = NULL;
307 if (wb_control_claim_selection (wbc)) {
308 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
309 } else {
310 gnm_app_clipboard_clear (FALSE);
311 g_warning ("Unable to set selection?");
313 g_slist_free (objects);
316 gboolean
317 gnm_app_clipboard_is_empty (void)
319 g_return_val_if_fail (app != NULL, TRUE);
321 return app->clipboard_sheet_view == NULL;
324 gboolean
325 gnm_app_clipboard_is_cut (void)
327 g_return_val_if_fail (app != NULL, FALSE);
329 if (app->clipboard_sheet_view != NULL)
330 return app->clipboard_copied_contents ? FALSE : TRUE;
331 return FALSE;
335 * gnm_app_clipboard_sheet_get:
337 * Returns: (transfer none) (nullable): the current clipboard #Sheet.
339 Sheet *
340 gnm_app_clipboard_sheet_get (void)
342 g_return_val_if_fail (app != NULL, NULL);
344 if (app->clipboard_sheet_view == NULL)
345 return NULL;
346 return sv_sheet (app->clipboard_sheet_view);
350 * gnm_app_clipboard_sheet_view_get:
352 * Returns: (transfer none) (nullable): the current clipboard #SheetView.
354 SheetView *
355 gnm_app_clipboard_sheet_view_get (void)
357 g_return_val_if_fail (app != NULL, NULL);
358 return app->clipboard_sheet_view;
362 * gnm_app_clipboard_contents_get:
364 * Returns: (nullable) (transfer none): the current contents of the clipboard.
366 GnmCellRegion *
367 gnm_app_clipboard_contents_get (void)
369 g_return_val_if_fail (app != NULL, NULL);
370 return app->clipboard_copied_contents;
374 * gnm_app_clipboard_area_get:
376 * Returns: (nullable) (transfer none): the current range in the clipboard.
378 GnmRange const *
379 gnm_app_clipboard_area_get (void)
381 g_return_val_if_fail (app != NULL, NULL);
383 * Only return the range if the sheet has been set.
384 * The range will still contain data even after
385 * the clipboard has been cleared so we need to be extra
386 * safe and only return a range if there is a valid selection
388 if (app->clipboard_sheet_view != NULL)
389 return app->clipboard_cut_range;
390 return NULL;
394 * gnm_app_workbook_get_by_name:
395 * @name: the workbook name.
396 * @ref_uri:
398 * Returns: (transfer none): the #Workbook or %NULL.
400 Workbook *
401 gnm_app_workbook_get_by_name (char const *name,
402 char const *ref_uri)
404 Workbook *wb;
405 char *filename = NULL;
407 if (name == NULL || *name == 0)
408 return NULL;
410 /* Try as URI. */
411 wb = gnm_app_workbook_get_by_uri (name);
412 if (wb)
413 goto out;
415 filename = g_filename_from_utf8 (name, -1, NULL, NULL, NULL);
417 /* Try as absolute filename. */
418 if (filename && g_path_is_absolute (filename)) {
419 char *uri = go_filename_to_uri (filename);
420 if (uri) {
421 wb = gnm_app_workbook_get_by_uri (uri);
422 g_free (uri);
424 if (wb)
425 goto out;
428 if (filename && ref_uri) {
429 char *rel_uri = go_url_encode (filename, 1);
430 char *uri = go_url_resolve_relative (ref_uri, rel_uri);
431 g_free (rel_uri);
432 if (uri) {
433 wb = gnm_app_workbook_get_by_uri (uri);
434 g_free (uri);
436 if (wb)
437 goto out;
440 out:
441 g_free (filename);
442 return wb;
445 struct wb_uri_closure {
446 Workbook *wb;
447 char const *uri;
450 static gboolean
451 cb_workbook_uri (Workbook * wb, gpointer closure)
453 struct wb_uri_closure *dat = closure;
454 char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
456 if (wb_uri && strcmp (wb_uri, dat->uri) == 0) {
457 dat->wb = wb;
458 return FALSE;
460 return TRUE;
463 static gboolean
464 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data);
466 static Workbook *
467 gnm_app_workbook_get_by_uri (char const *uri)
469 struct wb_uri_closure closure;
471 g_return_val_if_fail (uri != NULL, NULL);
473 closure.wb = NULL;
474 closure.uri = uri;
475 gnm_app_workbook_foreach (&cb_workbook_uri, &closure);
477 return closure.wb;
480 static gboolean
481 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data)
483 GList *l;
485 g_return_val_if_fail (app != NULL, FALSE);
487 for (l = app->workbook_list; l; l = l->next){
488 Workbook *wb = l->data;
490 if (!(*cback)(wb, data))
491 return FALSE;
493 return TRUE;
497 * gnm_app_workbook_get_by_index:
498 * @i: index
500 * Get nth workbook. Index is zero-based.
501 * Return value: (nullable) (transfer none): the nth workbook, if any.
503 Workbook *
504 gnm_app_workbook_get_by_index (int i)
506 return g_list_nth_data (app->workbook_list, i);
509 double
510 gnm_app_display_dpi_get (gboolean horizontal)
512 return horizontal
513 ? gnm_conf_get_core_gui_screen_horizontaldpi ()
514 : gnm_conf_get_core_gui_screen_verticaldpi ();
517 double
518 gnm_app_dpi_to_pixels (void)
520 return MIN (gnm_app_display_dpi_get (TRUE),
521 gnm_app_display_dpi_get (FALSE)) / 72.;
524 /* GtkFileFilter */
526 * gnm_app_create_opener_filter:
527 * @openers: (element-type GOFileOpener): a list of file openers.
529 * Creates a #GtkFileFilter from the list of file types supported by the
530 * openers in the list.
531 * Returns: (transfer full): the newly allocated #GtkFileFilter.
533 void *
534 gnm_app_create_opener_filter (GList *openers)
536 /* See below. */
537 static const char *const bad_suffixes[] = {
538 "txt",
539 "html", "htm",
540 "xml",
541 NULL
544 GtkFileFilter *filter = gtk_file_filter_new ();
545 gboolean for_history = (openers == NULL);
547 if (openers == NULL)
548 openers = go_get_file_openers ();
550 for (; openers; openers = openers->next) {
551 GOFileOpener *opener = openers->data;
552 if (opener != NULL) {
553 const GSList *mimes = go_file_opener_get_mimes (opener);
554 const GSList *suffixes = go_file_opener_get_suffixes (opener);
556 if (!for_history)
557 while (mimes) {
558 const char *mime = mimes->data;
560 * See 438918. Too many things
561 * like *.xml and *.txt get added
562 * to be useful for the file history
564 gtk_file_filter_add_mime_type (filter, mime);
565 if (0)
566 g_print ("%s: Adding mime %s\n", go_file_opener_get_description (opener), mime);
567 mimes = mimes->next;
570 while (suffixes) {
571 const char *suffix = suffixes->data;
572 GString *pattern;
573 int i;
575 if (for_history)
576 for (i = 0; bad_suffixes[i]; i++)
577 if (strcmp (suffix, bad_suffixes[i]) == 0)
578 goto bad_suffix;
580 /* Create "*.[xX][lL][sS]" */
581 pattern = g_string_new ("*.");
582 while (*suffix) {
583 gunichar uc = g_utf8_get_char (suffix);
584 suffix = g_utf8_next_char (suffix);
585 if (g_unichar_islower (uc)) {
586 g_string_append_c (pattern, '[');
587 g_string_append_unichar (pattern, uc);
588 uc = g_unichar_toupper (uc);
589 g_string_append_unichar (pattern, uc);
590 g_string_append_c (pattern, ']');
591 } else
592 g_string_append_unichar (pattern, uc);
595 gtk_file_filter_add_pattern (filter, pattern->str);
596 if (0)
597 g_print ("%s: Adding %s\n", go_file_opener_get_description (opener), pattern->str);
598 g_string_free (pattern, TRUE);
600 bad_suffix:
601 suffixes = suffixes->next;
605 return filter;
608 static gint
609 compare_mru (GtkRecentInfo *a, GtkRecentInfo *b)
611 time_t ta = MAX (gtk_recent_info_get_visited (a), gtk_recent_info_get_modified (a));
612 time_t tb = MAX (gtk_recent_info_get_visited (b), gtk_recent_info_get_modified (b));
614 return ta < tb;
618 * gnm_app_history_get_list:
620 * creating it if necessary.
622 * Return value: (element-type utf8) (transfer full): the list, which must be
623 * freed along with the strings in it.
625 GSList *
626 gnm_app_history_get_list (int max_elements)
628 GSList *res = NULL;
629 GList *items, *l;
630 GtkFileFilter *filter;
631 int n_elements = 0;
633 if (app->recent == NULL)
634 return NULL;
636 items = gtk_recent_manager_get_items (app->recent);
637 items = g_list_sort (items, (GCompareFunc)compare_mru);
639 filter = gnm_app_create_opener_filter (NULL);
641 for (l = items; l && n_elements < max_elements; l = l->next) {
642 GtkRecentInfo *ri = l->data;
643 const char *uri = gtk_recent_info_get_uri (ri);
644 gboolean want_it;
646 if (gtk_recent_info_has_application (ri, g_get_application_name ())) {
647 want_it = TRUE;
648 } else {
649 GtkFileFilterInfo fi;
650 char *display_name = g_filename_display_basename (uri);
652 memset (&fi, 0, sizeof (fi));
653 fi.contains = (GTK_FILE_FILTER_MIME_TYPE |
654 GTK_FILE_FILTER_URI |
655 GTK_FILE_FILTER_DISPLAY_NAME);
656 fi.uri = uri;
657 fi.mime_type = gtk_recent_info_get_mime_type (ri);
658 fi.display_name = display_name;
659 want_it = gtk_file_filter_filter (filter, &fi);
660 g_free (display_name);
663 if (want_it) {
664 char *filename = go_filename_from_uri (uri);
665 if (filename && !g_file_test (filename, G_FILE_TEST_EXISTS))
666 want_it = FALSE;
667 g_free (filename);
670 if (want_it) {
671 res = g_slist_prepend (res, g_strdup (uri));
672 n_elements++;
676 g_list_free_full (items, (GDestroyNotify)gtk_recent_info_unref);
677 g_object_ref_sink (filter);
678 g_object_unref (filter);
680 return g_slist_reverse (res);
684 * application_history_update_list:
685 * @uri:
686 * @mimetype: (nullable): the mime type for @uri
688 * Adds @uri to the application's history of files.
690 void
691 gnm_app_history_add (char const *uri, const char *mimetype)
693 GtkRecentData rd;
695 if (app->recent == NULL)
696 return;
698 memset (&rd, 0, sizeof (rd));
700 #if 0
701 g_print ("uri: %s\nmime: %s\n\n", uri, mimetype ? mimetype : "-");
702 #endif
704 rd.mime_type =
705 g_strdup (mimetype ? mimetype : "application/octet-stream");
707 rd.app_name = g_strdup (g_get_application_name ());
708 rd.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
709 rd.groups = NULL;
710 rd.is_private = FALSE;
712 if (!gtk_recent_manager_add_full (app->recent, uri, &rd)) {
713 /* Now what? */
714 g_printerr ("Warning: failed to update recent document.\n");
717 g_free (rd.mime_type);
718 g_free (rd.app_name);
719 g_free (rd.app_exec);
721 g_object_notify (G_OBJECT (app), "file-history-list");
724 static void
725 cb_recent_changed (G_GNUC_UNUSED GtkRecentManager *recent, GnmApp *app)
727 g_object_notify (G_OBJECT (app), "file-history-list");
730 static void
731 gnm_app_finalize (GObject *obj)
733 GnmApp *application = GNM_APP (obj);
735 g_free (application->clipboard_cut_range);
736 application->clipboard_cut_range = NULL;
738 application->recent = NULL;
740 if (app == application)
741 app = NULL;
743 G_OBJECT_CLASS (parent_klass)->finalize (obj);
746 static void
747 gnm_app_get_property (GObject *obj, guint param_id,
748 GValue *value, GParamSpec *pspec)
750 #if 0
751 GnmApp *application = GNM_APP (obj);
752 #endif
754 switch (param_id) {
755 case PROP_HISTORY_LIST:
756 g_value_set_pointer (value, gnm_app_history_get_list (G_MAXINT));
757 break;
758 case PROP_SHUTTING_DOWN:
759 g_value_set_boolean (value, gnm_app_shutting_down ());
760 break;
761 case PROP_INITIAL_OPEN_COMPLETE:
762 g_value_set_boolean (value, gnm_app_initial_open_complete ());
763 break;
764 default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
765 break;
769 static void
770 gnm_app_set_property (GObject *object, guint property_id,
771 GValue const *value, GParamSpec *pspec)
773 GnmApp *app = (GnmApp *)object;
775 switch (property_id) {
776 case PROP_SHUTTING_DOWN:
777 app->shutting_down = g_value_get_boolean (value);
778 break;
779 case PROP_INITIAL_OPEN_COMPLETE:
780 app->initial_open_complete = g_value_get_boolean (value);
781 break;
782 default:
783 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
784 break;
788 static void
789 gnm_app_class_init (GObjectClass *gobject_klass)
791 parent_klass = g_type_class_peek_parent (gobject_klass);
793 /* Object class method overrides */
794 gobject_klass->finalize = gnm_app_finalize;
795 gobject_klass->get_property = gnm_app_get_property;
796 gobject_klass->set_property = gnm_app_set_property;
798 g_object_class_install_property (gobject_klass, PROP_HISTORY_LIST,
799 g_param_spec_pointer ("file-history-list",
800 P_("File History List"),
801 P_("A list of filenames that have been read recently"),
802 GSF_PARAM_STATIC | G_PARAM_READABLE));
803 g_object_class_install_property (gobject_klass, PROP_SHUTTING_DOWN,
804 g_param_spec_boolean ("shutting-down",
805 P_("Shutting Down"),
806 P_("In the process of shutting down?"),
807 FALSE,
808 GSF_PARAM_STATIC | G_PARAM_READWRITE));
809 g_object_class_install_property (gobject_klass, PROP_INITIAL_OPEN_COMPLETE,
810 g_param_spec_boolean ("initial-open-complete",
811 P_("Initial Open Complete"),
812 P_("All command-line files open?"),
813 FALSE,
814 GSF_PARAM_STATIC | G_PARAM_READWRITE));
817 signals[WORKBOOK_ADDED] = g_signal_new ("workbook_added",
818 GNM_APP_TYPE,
819 G_SIGNAL_RUN_LAST,
820 G_STRUCT_OFFSET (GnmAppClass, workbook_added),
821 NULL, NULL,
822 g_cclosure_marshal_VOID__OBJECT,
823 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
824 signals[WORKBOOK_REMOVED] = g_signal_new ("workbook_removed",
825 GNM_APP_TYPE,
826 G_SIGNAL_RUN_LAST,
827 G_STRUCT_OFFSET (GnmAppClass, workbook_removed),
828 NULL, NULL,
829 g_cclosure_marshal_VOID__POINTER,
830 G_TYPE_NONE, 1, G_TYPE_POINTER);
831 signals[WINDOW_LIST_CHANGED] = g_signal_new ("window-list-changed",
832 GNM_APP_TYPE,
833 G_SIGNAL_RUN_LAST,
834 G_STRUCT_OFFSET (GnmAppClass, window_list_changed),
835 NULL, NULL,
836 g_cclosure_marshal_VOID__VOID,
837 G_TYPE_NONE, 0);
838 signals[CUSTOM_UI_ADDED] = g_signal_new ("custom-ui-added",
839 GNM_APP_TYPE,
840 G_SIGNAL_RUN_LAST,
841 G_STRUCT_OFFSET (GnmAppClass, custom_ui_added),
842 NULL, NULL,
843 g_cclosure_marshal_VOID__POINTER,
844 G_TYPE_NONE, 1, G_TYPE_POINTER);
845 signals[CUSTOM_UI_REMOVED] = g_signal_new ("custom-ui-removed",
846 GNM_APP_TYPE,
847 G_SIGNAL_RUN_LAST,
848 G_STRUCT_OFFSET (GnmAppClass, custom_ui_removed),
849 NULL, NULL,
850 g_cclosure_marshal_VOID__POINTER,
851 G_TYPE_NONE, 1, G_TYPE_POINTER);
852 signals[CLIPBOARD_MODIFIED] = g_signal_new ("clipboard_modified",
853 GNM_APP_TYPE,
854 G_SIGNAL_RUN_LAST,
855 G_STRUCT_OFFSET (GnmAppClass, clipboard_modified),
856 NULL, NULL,
857 g_cclosure_marshal_VOID__VOID,
858 G_TYPE_NONE, 0);
859 signals[RECALC_FINISHED] = g_signal_new ("recalc_finished",
860 GNM_APP_TYPE,
861 G_SIGNAL_RUN_LAST,
862 G_STRUCT_OFFSET (GnmAppClass, recalc_finished),
863 NULL, NULL,
864 g_cclosure_marshal_VOID__VOID,
865 G_TYPE_NONE, 0);
866 signals[RECALC_FINISHED] = g_signal_new ("recalc_clear_caches",
867 GNM_APP_TYPE,
868 G_SIGNAL_RUN_LAST,
869 G_STRUCT_OFFSET (GnmAppClass, recalc_clear_caches),
870 NULL, NULL,
871 g_cclosure_marshal_VOID__VOID,
872 G_TYPE_NONE, 0);
875 static void
876 gnm_app_init (GObject *obj)
878 GnmApp *gnm_app = GNM_APP (obj);
880 gnm_app->clipboard_copied_contents = NULL;
881 gnm_app->clipboard_sheet_view = NULL;
883 gnm_app->workbook_list = NULL;
885 if (gdk_display_get_default ()) {
887 * Only allocate a GtkRecentManager if we have a gui.
888 * This is, in part, because it currently throws an error.
889 * deep inside gtk+.
891 gnm_app->recent = gtk_recent_manager_get_default ();
892 g_signal_connect_object (G_OBJECT (gnm_app->recent),
893 "changed",
894 G_CALLBACK (cb_recent_changed),
895 gnm_app, 0);
898 app = gnm_app;
901 GSF_CLASS (GnmApp, gnm_app,
902 gnm_app_class_init, gnm_app_init,
903 G_TYPE_OBJECT)
905 /**********************************************************************/
906 static GSList *extra_uis = NULL;
909 * gnm_action_new:
910 * @name: action ID.
911 * @label: label.
912 * @icon: icon name.
913 * @always_available: whether the action should always be available.
914 * @handler: (scope async): the handler.
916 * Returns: (transfer full): the newly allocated #GnmAction.
918 GnmAction *
919 gnm_action_new (char const *id, char const *label,
920 char const *icon_name, gboolean always_available,
921 GnmActionHandler handler)
923 GnmAction *res = g_new0 (GnmAction, 1);
924 res->id = g_strdup (id);
925 res->label = g_strdup (label);
926 res->icon_name = g_strdup (icon_name);
927 res->always_available = always_available;
928 res->handler = handler;
929 return res;
932 void
933 gnm_action_free (GnmAction *action)
935 if (NULL != action) {
936 g_free (action->id);
937 g_free (action->label);
938 g_free (action->icon_name);
939 g_free (action);
943 static GnmAction *
944 gnm_action_copy (GnmAction const *action)
946 return gnm_action_new (action->id, action->label, action->icon_name,
947 action->always_available, action->handler);
950 GType
951 gnm_action_get_type (void)
953 static GType t = 0;
955 if (t == 0) {
956 t = g_boxed_type_register_static ("GnmAction",
957 (GBoxedCopyFunc)gnm_action_copy,
958 (GBoxedFreeFunc)gnm_action_free);
960 return t;
963 /***************
964 * making GnmAppExtraUI a boxed type for introspection. copy and free don't do
965 anything which might be critical, crossing fingers.*/
967 static GnmAppExtraUI *
968 gnm_app_extra_ui_ref (GnmAppExtraUI *ui)
970 // Nothing
971 return ui;
974 static GnmAppExtraUI *
975 gnm_app_extra_ui_unref (GnmAppExtraUI *ui)
977 // Nothing
978 return ui;
981 GType
982 gnm_app_extra_ui_get_type (void)
984 static GType t = 0;
986 if (t == 0) {
987 t = g_boxed_type_register_static ("GnmAppExtraUI",
988 (GBoxedCopyFunc)gnm_app_extra_ui_ref,
989 (GBoxedFreeFunc)gnm_app_extra_ui_unref);
991 return t;
995 * gnm_app_add_extra_ui:
996 * @group_name: action group name.
997 * @actions: (element-type GnmAction): list of actions.
998 * @layout: the xml string describing the menus and toolbars.
999 * @domain: localization domain.
1000 * @user_data: user data
1002 * Returns: (transfer full): the newly allocated #GnmAppExtraUI.
1004 GnmAppExtraUI *
1005 gnm_app_add_extra_ui (char const *group_name,
1006 GSList *actions,
1007 const char *layout,
1008 char const *domain,
1009 gpointer user_data)
1011 GnmAppExtraUI *extra_ui = g_new0 (GnmAppExtraUI, 1);
1012 extra_uis = g_slist_prepend (extra_uis, extra_ui);
1013 extra_ui->group_name = g_strdup (group_name);
1014 extra_ui->actions = actions;
1015 extra_ui->layout = g_strdup (layout);
1016 extra_ui->user_data = user_data;
1017 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_ADDED], 0, extra_ui);
1018 if (gnm_debug_flag ("extra-ui"))
1019 g_printerr ("Adding extra ui [%s] %p\n", group_name, extra_ui);
1020 return extra_ui;
1023 void
1024 gnm_app_remove_extra_ui (GnmAppExtraUI *extra_ui)
1026 if (gnm_debug_flag ("extra-ui"))
1027 g_printerr ("Removing extra ui %p\n", extra_ui);
1028 extra_uis = g_slist_remove (extra_uis, extra_ui);
1029 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_REMOVED], 0, extra_ui);
1030 g_free (extra_ui->group_name);
1031 g_free (extra_ui->layout);
1032 g_free (extra_ui);
1036 * gnm_app_foreach_extra_ui:
1037 * @func: (scope call): #GFunc
1038 * @data: user data.
1040 * Applies @func to each #GnmAppExtraUI.
1042 void
1043 gnm_app_foreach_extra_ui (GFunc func, gpointer data)
1045 g_slist_foreach (extra_uis, func, data);
1048 /**********************************************************************/
1050 static gint windows_update_timer = 0;
1051 static gboolean
1052 cb_flag_windows_changed (void)
1054 if (app)
1055 g_signal_emit (G_OBJECT (app), signals[WINDOW_LIST_CHANGED], 0);
1056 windows_update_timer = 0;
1057 return FALSE;
1061 * _gnm_app_flag_windows_changed:
1063 * An internal utility routine to flag a regeneration of the window lists
1065 void
1066 _gnm_app_flag_windows_changed (void)
1068 if (windows_update_timer == 0)
1069 windows_update_timer = g_timeout_add (100,
1070 (GSourceFunc)cb_flag_windows_changed, NULL);
1073 /**********************************************************************/
1076 * gnm_app_recalc:
1078 * Recalculate everything dirty in all workbooks that have automatic
1079 * recalc turned on.
1081 void
1082 gnm_app_recalc (void)
1084 GList *l;
1086 g_return_if_fail (app != NULL);
1088 gnm_app_recalc_start ();
1090 for (l = app->workbook_list; l; l = l->next) {
1091 Workbook *wb = l->data;
1093 if (workbook_get_recalcmode (wb))
1094 workbook_recalc (wb);
1097 gnm_app_recalc_finish ();
1100 void
1101 gnm_app_recalc_start (void)
1103 g_return_if_fail (app->recalc_count >= 0);
1104 app->recalc_count++;
1107 void
1108 gnm_app_recalc_finish (void)
1110 g_return_if_fail (app->recalc_count > 0);
1111 app->recalc_count--;
1112 if (app->recalc_count == 0) {
1113 gnm_app_recalc_clear_caches ();
1114 g_signal_emit_by_name (gnm_app_get_app (), "recalc-finished");
1118 void
1119 gnm_app_recalc_clear_caches (void)
1121 g_signal_emit_by_name (gnm_app_get_app (), "recalc-clear-caches");
1124 gboolean
1125 gnm_app_shutting_down (void)
1127 return app->shutting_down;
1130 gboolean
1131 gnm_app_initial_open_complete (void)
1133 return app->initial_open_complete;