GnmAction: introspection fixes.
[gnumeric.git] / src / application.c
blob72fc11979812ccd68263eae4776adb19f1c41938
1 /*
2 * application.c: Manage the data common to all workbooks
4 * Author:
5 * Jody Goldberg <jody@gnome.org>
6 */
7 #include <gnumeric-config.h>
8 #include <string.h>
9 #include <gnumeric.h>
10 #include <application.h>
12 #include <clipboard.h>
13 #include <selection.h>
14 #include <workbook-control.h>
15 #include <workbook-view.h>
16 #include <workbook.h>
17 #include <sheet.h>
18 #include <sheet-view.h>
19 #include <sheet-private.h>
20 #include <sheet-object.h>
21 #include <gutils.h>
22 #include <ranges.h>
23 #include <sheet-object.h>
24 #include <commands.h>
25 #include <gui-clipboard.h>
26 #include <expr-name.h>
27 #include <workbook-priv.h>
29 #include <gnumeric-conf.h>
30 #include <goffice/goffice.h>
31 #include <gsf/gsf-impl-utils.h>
32 #include <gnm-i18n.h>
34 #define GNM_APP(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GNM_APP_TYPE, GnmApp))
35 #define GNM_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GNM_APP_TYPE))
37 enum {
38 PROP_0,
39 PROP_HISTORY_LIST,
40 PROP_SHUTTING_DOWN,
41 PROP_INITIAL_OPEN_COMPLETE
43 /* Signals */
44 enum {
45 WORKBOOK_ADDED,
46 WORKBOOK_REMOVED,
47 WINDOW_LIST_CHANGED,
48 CUSTOM_UI_ADDED,
49 CUSTOM_UI_REMOVED,
50 CLIPBOARD_MODIFIED,
51 RECALC_FINISHED,
52 RECALC_CLEAR_CACHES,
53 LAST_SIGNAL
56 static guint signals[LAST_SIGNAL] = { 0 };
58 struct _GnmApp {
59 GObject base;
61 /* Clipboard */
62 SheetView *clipboard_sheet_view;
63 GnmCellRegion *clipboard_copied_contents;
64 GnmRange *clipboard_cut_range;
66 GList *workbook_list;
68 /* Recalculation manager. */
69 int recalc_count;
71 GtkRecentManager *recent;
72 gulong recent_sig;
74 gboolean shutting_down;
75 gboolean initial_open_complete;
78 typedef struct {
79 GObjectClass parent;
81 void (*workbook_added) (GnmApp *gnm_app, Workbook *wb);
82 void (*workbook_removed) (GnmApp *gnm_app, Workbook *wb);
83 void (*window_list_changed) (GnmApp *gnm_app);
84 void (*custom_ui_added) (GnmApp *gnm_app, GnmAppExtraUI *ui);
85 void (*custom_ui_removed) (GnmApp *gnm_app, GnmAppExtraUI *ui);
86 void (*clipboard_modified) (GnmApp *gnm_app);
87 void (*recalc_finished) (GnmApp *gnm_app);
88 void (*recalc_clear_caches) (GnmApp *gnm_app);
89 } GnmAppClass;
91 static GObjectClass *parent_klass;
92 static GnmApp *app;
94 static Workbook *gnm_app_workbook_get_by_uri (char const *uri);
96 /**
97 * gnm_app_get_app:
99 * Returns: (transfer none): the #GnmApp instance.
101 GObject *
102 gnm_app_get_app (void)
104 return G_OBJECT (app);
108 * gnm_app_workbook_list_add:
109 * @wb: A #Workbook
111 * Add @wb to the application's list of workbooks.
113 void
114 gnm_app_workbook_list_add (Workbook *wb)
116 g_return_if_fail (GNM_IS_WORKBOOK (wb));
117 g_return_if_fail (app != NULL);
119 app->workbook_list = g_list_prepend (app->workbook_list, wb);
120 g_signal_connect (G_OBJECT (wb),
121 "notify::uri",
122 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
123 _gnm_app_flag_windows_changed ();
124 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_ADDED], 0, wb);
128 * gnm_app_workbook_list_remove:
129 * @wb: A #Workbook
131 * Remove @wb from the application's list of workbooks.
133 void
134 gnm_app_workbook_list_remove (Workbook *wb)
136 g_return_if_fail (wb != NULL);
137 g_return_if_fail (app != NULL);
139 app->workbook_list = g_list_remove (app->workbook_list, wb);
140 g_signal_handlers_disconnect_by_func (G_OBJECT (wb),
141 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
142 _gnm_app_flag_windows_changed ();
143 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_REMOVED], 0, wb);
147 * gnm_app_workbook_list:
149 * Returns: (element-type Workbook) (transfer none): the workbook list.
151 GList *
152 gnm_app_workbook_list (void)
154 g_return_val_if_fail (app != NULL, NULL);
156 return app->workbook_list;
159 void
160 gnm_app_sanity_check (void)
162 GList *l;
163 gboolean err = FALSE;
164 for (l = gnm_app_workbook_list (); l; l = l->next) {
165 Workbook *wb = l->data;
166 if (gnm_named_expr_collection_sanity_check (wb->names, "workbook"))
167 err = TRUE;
169 if (err)
170 g_error ("Sanity check failed\n");
176 * gnm_app_clipboard_clear:
178 * Clear and free the contents of the clipboard if it is
179 * not empty.
181 void
182 gnm_app_clipboard_clear (gboolean drop_selection)
184 g_return_if_fail (app != NULL);
186 if (app->clipboard_copied_contents) {
187 cellregion_unref (app->clipboard_copied_contents);
188 app->clipboard_copied_contents = NULL;
190 if (app->clipboard_sheet_view != NULL) {
191 gnm_sheet_view_unant (app->clipboard_sheet_view);
193 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
195 gnm_sheet_view_weak_unref (&(app->clipboard_sheet_view));
197 /* Release the selection */
198 if (drop_selection)
199 gnm_x_disown_clipboard ();
203 void
204 gnm_app_clipboard_invalidate_sheet (Sheet *sheet)
206 /* Clear the cliboard to avoid dangling references to the deleted sheet */
207 if (sheet == gnm_app_clipboard_sheet_get ())
208 gnm_app_clipboard_clear (TRUE);
209 else if (app->clipboard_copied_contents)
210 cellregion_invalidate_sheet (app->clipboard_copied_contents, sheet);
213 void
214 gnm_app_clipboard_unant (void)
216 g_return_if_fail (app != NULL);
218 if (app->clipboard_sheet_view != NULL)
219 gnm_sheet_view_unant (app->clipboard_sheet_view);
223 * gnm_app_clipboard_cut_copy:
224 * @wbc: the workbook control that requested the operation.
225 * @is_cut: is this a cut or a copy.
226 * @sv: The source sheet for the copy.
227 * @area: A single rectangular range to be copied.
228 * @animate_range: Do we want ot add an animated cursor around things.
230 * When Cutting we
231 * Clear and free the contents of the clipboard and save the sheet and area
232 * to be cut. DO NOT ACTUALLY CUT! Paste will move the region if this was a
233 * cut operation.
235 * When Copying we
236 * Clear and free the contents of the clipboard and COPY the designated region
237 * into the clipboard.
239 * we need to pass @wbc as a control rather than a simple command-context so
240 * that the control can claim the selection.
242 void
243 gnm_app_clipboard_cut_copy (WorkbookControl *wbc, gboolean is_cut,
244 SheetView *sv, GnmRange const *area,
245 gboolean animate_cursor)
247 Sheet *sheet;
249 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
250 g_return_if_fail (area != NULL);
251 g_return_if_fail (app != NULL);
253 gnm_app_clipboard_clear (FALSE);
254 sheet = sv_sheet (sv);
255 g_free (app->clipboard_cut_range);
256 app->clipboard_cut_range = gnm_range_dup (area);
257 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
259 if (!is_cut)
260 app->clipboard_copied_contents =
261 clipboard_copy_range (sheet, area);
262 if (animate_cursor) {
263 GList *l = g_list_append (NULL, (gpointer)area);
264 gnm_sheet_view_ant (sv, l);
265 g_list_free (l);
268 if (wb_control_claim_selection (wbc)) {
269 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
270 } else {
271 gnm_app_clipboard_clear (FALSE);
272 g_warning ("Unable to set selection?");
277 * gnm_app_clipboard_cut_copy_obj:
278 * @wbc: #WorkbookControl
279 * @is_cut: %TRUE for cut, %FALSE for copy
280 * @sv: #SheetView
281 * @objects: (element-type SheetObject) (transfer container): a list
282 * of #SheetObject
284 * Different than copying/cutting a region, this can actually cut an object
286 void
287 gnm_app_clipboard_cut_copy_obj (WorkbookControl *wbc, gboolean is_cut,
288 SheetView *sv, GSList *objects)
290 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
291 g_return_if_fail (objects != NULL);
292 g_return_if_fail (app != NULL);
294 gnm_app_clipboard_clear (FALSE);
295 g_free (app->clipboard_cut_range);
296 app->clipboard_cut_range = NULL;
297 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
298 app->clipboard_copied_contents
299 = clipboard_copy_obj (sv_sheet (sv), objects);
300 if (is_cut) {
301 cmd_objects_delete (wbc, objects, _("Cut Object"));
302 objects = NULL;
304 if (wb_control_claim_selection (wbc)) {
305 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
306 } else {
307 gnm_app_clipboard_clear (FALSE);
308 g_warning ("Unable to set selection?");
310 g_slist_free (objects);
313 gboolean
314 gnm_app_clipboard_is_empty (void)
316 g_return_val_if_fail (app != NULL, TRUE);
318 return app->clipboard_sheet_view == NULL;
321 gboolean
322 gnm_app_clipboard_is_cut (void)
324 g_return_val_if_fail (app != NULL, FALSE);
326 if (app->clipboard_sheet_view != NULL)
327 return app->clipboard_copied_contents ? FALSE : TRUE;
328 return FALSE;
332 * gnm_app_clipboard_sheet_get:
334 * Returns: (transfer none) (nullable): the current clipboard #Sheet.
336 Sheet *
337 gnm_app_clipboard_sheet_get (void)
339 g_return_val_if_fail (app != NULL, NULL);
341 if (app->clipboard_sheet_view == NULL)
342 return NULL;
343 return sv_sheet (app->clipboard_sheet_view);
347 * gnm_app_clipboard_sheet_view_get:
349 * Returns: (transfer none) (nullable): the current clipboard #SheetView.
351 SheetView *
352 gnm_app_clipboard_sheet_view_get (void)
354 g_return_val_if_fail (app != NULL, NULL);
355 return app->clipboard_sheet_view;
359 * gnm_app_clipboard_contents_get:
361 * Returns: (nullable) (transfer none): the current contents of the clipboard.
363 GnmCellRegion *
364 gnm_app_clipboard_contents_get (void)
366 g_return_val_if_fail (app != NULL, NULL);
367 return app->clipboard_copied_contents;
371 * gnm_app_clipboard_area_get:
373 * Returns: (nullable) (transfer none): the current range in the clipboard.
375 GnmRange const *
376 gnm_app_clipboard_area_get (void)
378 g_return_val_if_fail (app != NULL, NULL);
380 * Only return the range if the sheet has been set.
381 * The range will still contain data even after
382 * the clipboard has been cleared so we need to be extra
383 * safe and only return a range if there is a valid selection
385 if (app->clipboard_sheet_view != NULL)
386 return app->clipboard_cut_range;
387 return NULL;
391 * gnm_app_workbook_get_by_name:
392 * @name: the workbook name.
393 * @ref_uri:
395 * Returns: (transfer none): the #Workbook or %NULL.
397 Workbook *
398 gnm_app_workbook_get_by_name (char const *name,
399 char const *ref_uri)
401 Workbook *wb;
402 char *filename = NULL;
404 if (name == NULL || *name == 0)
405 return NULL;
407 /* Try as URI. */
408 wb = gnm_app_workbook_get_by_uri (name);
409 if (wb)
410 goto out;
412 filename = g_filename_from_utf8 (name, -1, NULL, NULL, NULL);
414 /* Try as absolute filename. */
415 if (filename && g_path_is_absolute (filename)) {
416 char *uri = go_filename_to_uri (filename);
417 if (uri) {
418 wb = gnm_app_workbook_get_by_uri (uri);
419 g_free (uri);
421 if (wb)
422 goto out;
425 if (filename && ref_uri) {
426 char *rel_uri = go_url_encode (filename, 1);
427 char *uri = go_url_resolve_relative (ref_uri, rel_uri);
428 g_free (rel_uri);
429 if (uri) {
430 wb = gnm_app_workbook_get_by_uri (uri);
431 g_free (uri);
433 if (wb)
434 goto out;
437 out:
438 g_free (filename);
439 return wb;
442 struct wb_uri_closure {
443 Workbook *wb;
444 char const *uri;
447 static gboolean
448 cb_workbook_uri (Workbook * wb, gpointer closure)
450 struct wb_uri_closure *dat = closure;
451 char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
453 if (wb_uri && strcmp (wb_uri, dat->uri) == 0) {
454 dat->wb = wb;
455 return FALSE;
457 return TRUE;
460 static gboolean
461 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data);
463 static Workbook *
464 gnm_app_workbook_get_by_uri (char const *uri)
466 struct wb_uri_closure closure;
468 g_return_val_if_fail (uri != NULL, NULL);
470 closure.wb = NULL;
471 closure.uri = uri;
472 gnm_app_workbook_foreach (&cb_workbook_uri, &closure);
474 return closure.wb;
477 static gboolean
478 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data)
480 GList *l;
482 g_return_val_if_fail (app != NULL, FALSE);
484 for (l = app->workbook_list; l; l = l->next){
485 Workbook *wb = l->data;
487 if (!(*cback)(wb, data))
488 return FALSE;
490 return TRUE;
494 * gnm_app_workbook_get_by_index:
495 * @i: index
497 * Get nth workbook. Index is zero-based.
498 * Return value: (nullable) (transfer none): the nth workbook, if any.
500 Workbook *
501 gnm_app_workbook_get_by_index (int i)
503 return g_list_nth_data (app->workbook_list, i);
506 double
507 gnm_app_display_dpi_get (gboolean horizontal)
509 return horizontal
510 ? gnm_conf_get_core_gui_screen_horizontaldpi ()
511 : gnm_conf_get_core_gui_screen_verticaldpi ();
514 double
515 gnm_app_dpi_to_pixels (void)
517 return MIN (gnm_app_display_dpi_get (TRUE),
518 gnm_app_display_dpi_get (FALSE)) / 72.;
521 /* GtkFileFilter */
523 * gnm_app_create_opener_filter:
524 * @openers: (element-type GOFileOpener): a list of file openers.
526 * Creates a #GtkFileFilter from the list of file types supported by the
527 * openers in the list.
528 * Returns: (transfer full): the newly allocated #GtkFileFilter.
530 void *
531 gnm_app_create_opener_filter (GList *openers)
533 /* See below. */
534 static const char *const bad_suffixes[] = {
535 "txt",
536 "html", "htm",
537 "xml",
538 NULL
541 GtkFileFilter *filter = gtk_file_filter_new ();
542 gboolean for_history = (openers == NULL);
544 if (openers == NULL)
545 openers = go_get_file_openers ();
547 for (; openers; openers = openers->next) {
548 GOFileOpener *opener = openers->data;
549 if (opener != NULL) {
550 const GSList *mimes = go_file_opener_get_mimes (opener);
551 const GSList *suffixes = go_file_opener_get_suffixes (opener);
553 if (!for_history)
554 while (mimes) {
555 const char *mime = mimes->data;
557 * See 438918. Too many things
558 * like *.xml and *.txt get added
559 * to be useful for the file history
561 gtk_file_filter_add_mime_type (filter, mime);
562 if (0)
563 g_print ("%s: Adding mime %s\n", go_file_opener_get_description (opener), mime);
564 mimes = mimes->next;
567 while (suffixes) {
568 const char *suffix = suffixes->data;
569 GString *pattern;
570 int i;
572 if (for_history)
573 for (i = 0; bad_suffixes[i]; i++)
574 if (strcmp (suffix, bad_suffixes[i]) == 0)
575 goto bad_suffix;
577 /* Create "*.[xX][lL][sS]" */
578 pattern = g_string_new ("*.");
579 while (*suffix) {
580 gunichar uc = g_utf8_get_char (suffix);
581 suffix = g_utf8_next_char (suffix);
582 if (g_unichar_islower (uc)) {
583 g_string_append_c (pattern, '[');
584 g_string_append_unichar (pattern, uc);
585 uc = g_unichar_toupper (uc);
586 g_string_append_unichar (pattern, uc);
587 g_string_append_c (pattern, ']');
588 } else
589 g_string_append_unichar (pattern, uc);
592 gtk_file_filter_add_pattern (filter, pattern->str);
593 if (0)
594 g_print ("%s: Adding %s\n", go_file_opener_get_description (opener), pattern->str);
595 g_string_free (pattern, TRUE);
597 bad_suffix:
598 suffixes = suffixes->next;
602 return filter;
605 static gint
606 compare_mru (GtkRecentInfo *a, GtkRecentInfo *b)
608 time_t ta = MAX (gtk_recent_info_get_visited (a), gtk_recent_info_get_modified (a));
609 time_t tb = MAX (gtk_recent_info_get_visited (b), gtk_recent_info_get_modified (b));
611 return ta < tb;
615 * gnm_app_history_get_list:
617 * creating it if necessary.
619 * Return value: (element-type utf8) (transfer full): the list, which must be
620 * freed along with the strings in it.
622 GSList *
623 gnm_app_history_get_list (int max_elements)
625 GSList *res = NULL;
626 GList *items, *l;
627 GtkFileFilter *filter;
628 int n_elements = 0;
630 if (app->recent == NULL)
631 return NULL;
633 items = gtk_recent_manager_get_items (app->recent);
634 items = g_list_sort (items, (GCompareFunc)compare_mru);
636 filter = gnm_app_create_opener_filter (NULL);
638 for (l = items; l && n_elements < max_elements; l = l->next) {
639 GtkRecentInfo *ri = l->data;
640 const char *uri = gtk_recent_info_get_uri (ri);
641 gboolean want_it;
643 if (gtk_recent_info_has_application (ri, g_get_application_name ())) {
644 want_it = TRUE;
645 } else {
646 GtkFileFilterInfo fi;
647 char *display_name = g_filename_display_basename (uri);
649 memset (&fi, 0, sizeof (fi));
650 fi.contains = (GTK_FILE_FILTER_MIME_TYPE |
651 GTK_FILE_FILTER_URI |
652 GTK_FILE_FILTER_DISPLAY_NAME);
653 fi.uri = uri;
654 fi.mime_type = gtk_recent_info_get_mime_type (ri);
655 fi.display_name = display_name;
656 want_it = gtk_file_filter_filter (filter, &fi);
657 g_free (display_name);
660 if (want_it) {
661 char *filename = go_filename_from_uri (uri);
662 if (filename && !g_file_test (filename, G_FILE_TEST_EXISTS))
663 want_it = FALSE;
664 g_free (filename);
667 if (want_it) {
668 res = g_slist_prepend (res, g_strdup (uri));
669 n_elements++;
673 g_list_free_full (items, (GDestroyNotify)gtk_recent_info_unref);
674 g_object_ref_sink (filter);
675 g_object_unref (filter);
677 return g_slist_reverse (res);
681 * application_history_update_list:
682 * @uri:
683 * @mimetype: (nullable): the mime type for @uri
685 * Adds @uri to the application's history of files.
687 void
688 gnm_app_history_add (char const *uri, const char *mimetype)
690 GtkRecentData rd;
692 if (app->recent == NULL)
693 return;
695 memset (&rd, 0, sizeof (rd));
697 #if 0
698 g_print ("uri: %s\nmime: %s\n\n", uri, mimetype ? mimetype : "-");
699 #endif
701 rd.mime_type =
702 g_strdup (mimetype ? mimetype : "application/octet-stream");
704 rd.app_name = g_strdup (g_get_application_name ());
705 rd.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
706 rd.groups = NULL;
707 rd.is_private = FALSE;
709 if (!gtk_recent_manager_add_full (app->recent, uri, &rd)) {
710 /* Now what? */
711 g_printerr ("Warning: failed to update recent document.\n");
714 g_free (rd.mime_type);
715 g_free (rd.app_name);
716 g_free (rd.app_exec);
718 g_object_notify (G_OBJECT (app), "file-history-list");
721 static void
722 cb_recent_changed (G_GNUC_UNUSED GtkRecentManager *recent, GnmApp *app)
724 g_object_notify (G_OBJECT (app), "file-history-list");
727 static void
728 gnm_app_finalize (GObject *obj)
730 GnmApp *application = GNM_APP (obj);
732 g_free (application->clipboard_cut_range);
733 application->clipboard_cut_range = NULL;
735 application->recent = NULL;
737 if (app == application)
738 app = NULL;
740 G_OBJECT_CLASS (parent_klass)->finalize (obj);
743 static void
744 gnm_app_get_property (GObject *obj, guint param_id,
745 GValue *value, GParamSpec *pspec)
747 #if 0
748 GnmApp *application = GNM_APP (obj);
749 #endif
751 switch (param_id) {
752 case PROP_HISTORY_LIST:
753 g_value_set_pointer (value, gnm_app_history_get_list (G_MAXINT));
754 break;
755 case PROP_SHUTTING_DOWN:
756 g_value_set_boolean (value, gnm_app_shutting_down ());
757 break;
758 case PROP_INITIAL_OPEN_COMPLETE:
759 g_value_set_boolean (value, gnm_app_initial_open_complete ());
760 break;
761 default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
762 break;
766 static void
767 gnm_app_set_property (GObject *object, guint property_id,
768 GValue const *value, GParamSpec *pspec)
770 GnmApp *app = (GnmApp *)object;
772 switch (property_id) {
773 case PROP_SHUTTING_DOWN:
774 app->shutting_down = g_value_get_boolean (value);
775 break;
776 case PROP_INITIAL_OPEN_COMPLETE:
777 app->initial_open_complete = g_value_get_boolean (value);
778 break;
779 default:
780 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
781 break;
785 static void
786 gnm_app_class_init (GObjectClass *gobject_klass)
788 parent_klass = g_type_class_peek_parent (gobject_klass);
790 /* Object class method overrides */
791 gobject_klass->finalize = gnm_app_finalize;
792 gobject_klass->get_property = gnm_app_get_property;
793 gobject_klass->set_property = gnm_app_set_property;
795 g_object_class_install_property (gobject_klass, PROP_HISTORY_LIST,
796 g_param_spec_pointer ("file-history-list",
797 P_("File History List"),
798 P_("A list of filenames that have been read recently"),
799 GSF_PARAM_STATIC | G_PARAM_READABLE));
800 g_object_class_install_property (gobject_klass, PROP_SHUTTING_DOWN,
801 g_param_spec_boolean ("shutting-down",
802 P_("Shutting Down"),
803 P_("In the process of shutting down?"),
804 FALSE,
805 GSF_PARAM_STATIC | G_PARAM_READWRITE));
806 g_object_class_install_property (gobject_klass, PROP_INITIAL_OPEN_COMPLETE,
807 g_param_spec_boolean ("initial-open-complete",
808 P_("Initial Open Complete"),
809 P_("All command-line files open?"),
810 FALSE,
811 GSF_PARAM_STATIC | G_PARAM_READWRITE));
814 signals[WORKBOOK_ADDED] = g_signal_new ("workbook_added",
815 GNM_APP_TYPE,
816 G_SIGNAL_RUN_LAST,
817 G_STRUCT_OFFSET (GnmAppClass, workbook_added),
818 NULL, NULL,
819 g_cclosure_marshal_VOID__OBJECT,
820 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
821 signals[WORKBOOK_REMOVED] = g_signal_new ("workbook_removed",
822 GNM_APP_TYPE,
823 G_SIGNAL_RUN_LAST,
824 G_STRUCT_OFFSET (GnmAppClass, workbook_removed),
825 NULL, NULL,
826 g_cclosure_marshal_VOID__POINTER,
827 G_TYPE_NONE, 1, G_TYPE_POINTER);
828 signals[WINDOW_LIST_CHANGED] = g_signal_new ("window-list-changed",
829 GNM_APP_TYPE,
830 G_SIGNAL_RUN_LAST,
831 G_STRUCT_OFFSET (GnmAppClass, window_list_changed),
832 NULL, NULL,
833 g_cclosure_marshal_VOID__VOID,
834 G_TYPE_NONE, 0);
835 signals[CUSTOM_UI_ADDED] = g_signal_new ("custom-ui-added",
836 GNM_APP_TYPE,
837 G_SIGNAL_RUN_LAST,
838 G_STRUCT_OFFSET (GnmAppClass, custom_ui_added),
839 NULL, NULL,
840 g_cclosure_marshal_VOID__POINTER,
841 G_TYPE_NONE, 1, G_TYPE_POINTER);
842 signals[CUSTOM_UI_REMOVED] = g_signal_new ("custom-ui-removed",
843 GNM_APP_TYPE,
844 G_SIGNAL_RUN_LAST,
845 G_STRUCT_OFFSET (GnmAppClass, custom_ui_removed),
846 NULL, NULL,
847 g_cclosure_marshal_VOID__POINTER,
848 G_TYPE_NONE, 1, G_TYPE_POINTER);
849 signals[CLIPBOARD_MODIFIED] = g_signal_new ("clipboard_modified",
850 GNM_APP_TYPE,
851 G_SIGNAL_RUN_LAST,
852 G_STRUCT_OFFSET (GnmAppClass, clipboard_modified),
853 NULL, NULL,
854 g_cclosure_marshal_VOID__VOID,
855 G_TYPE_NONE, 0);
856 signals[RECALC_FINISHED] = g_signal_new ("recalc_finished",
857 GNM_APP_TYPE,
858 G_SIGNAL_RUN_LAST,
859 G_STRUCT_OFFSET (GnmAppClass, recalc_finished),
860 NULL, NULL,
861 g_cclosure_marshal_VOID__VOID,
862 G_TYPE_NONE, 0);
863 signals[RECALC_FINISHED] = g_signal_new ("recalc_clear_caches",
864 GNM_APP_TYPE,
865 G_SIGNAL_RUN_LAST,
866 G_STRUCT_OFFSET (GnmAppClass, recalc_clear_caches),
867 NULL, NULL,
868 g_cclosure_marshal_VOID__VOID,
869 G_TYPE_NONE, 0);
872 static void
873 gnm_app_init (GObject *obj)
875 GnmApp *gnm_app = GNM_APP (obj);
877 gnm_app->clipboard_copied_contents = NULL;
878 gnm_app->clipboard_sheet_view = NULL;
880 gnm_app->workbook_list = NULL;
882 if (gdk_display_get_default ()) {
884 * Only allocate a GtkRecentManager if we have a gui.
885 * This is, in part, because it currently throws an error.
886 * deep inside gtk+.
888 gnm_app->recent = gtk_recent_manager_get_default ();
889 g_signal_connect_object (G_OBJECT (gnm_app->recent),
890 "changed",
891 G_CALLBACK (cb_recent_changed),
892 gnm_app, 0);
895 app = gnm_app;
898 GSF_CLASS (GnmApp, gnm_app,
899 gnm_app_class_init, gnm_app_init,
900 G_TYPE_OBJECT)
902 /**********************************************************************/
903 static GSList *extra_uis = NULL;
906 * gnm_action_new:
907 * @name: action ID.
908 * @label: label.
909 * @icon: icon name.
910 * @always_available: whether the action should always be available.
911 * @handler: (scope notified): the handler.
912 * @data: user data for @handler
913 * @notify: destroy notification for @data
915 * Returns: (transfer full): the newly allocated #GnmAction.
917 GnmAction *
918 gnm_action_new (char const *id, char const *label,
919 char const *icon_name, gboolean always_available,
920 GnmActionHandler handler,
921 gpointer data, GDestroyNotify notify)
923 GnmAction *res = g_new0 (GnmAction, 1);
924 res->ref_count = 1;
925 res->id = g_strdup (id);
926 res->label = g_strdup (label);
927 res->icon_name = g_strdup (icon_name);
928 res->always_available = always_available;
929 res->handler = handler;
930 res->data = data;
931 res->notify = notify;
932 return res;
936 * gnm_action_unref:
937 * @action: (transfer full) (nullable): #GnmAction
939 void
940 gnm_action_unref (GnmAction *action)
942 if (!action || action->ref_count-- > 1)
943 return;
945 if (action->notify)
946 action->notify (action->data);
948 g_free (action->id);
949 g_free (action->label);
950 g_free (action->icon_name);
951 g_free (action);
955 * gnm_action_ref:
956 * @action: (transfer none) (nullable): #GnmAction
958 * Returns: (transfer full) (nullable): a new reference to @action.
960 GnmAction *
961 gnm_action_ref (GnmAction *action)
963 if (action)
964 action->ref_count++;
965 return action;
968 GType
969 gnm_action_get_type (void)
971 static GType t = 0;
973 if (t == 0) {
974 t = g_boxed_type_register_static ("GnmAction",
975 (GBoxedCopyFunc)gnm_action_ref,
976 (GBoxedFreeFunc)gnm_action_unref);
978 return t;
981 /***************
982 * making GnmAppExtraUI a boxed type for introspection. copy and free don't do
983 anything which might be critical, crossing fingers.*/
985 static GnmAppExtraUI *
986 gnm_app_extra_ui_ref (GnmAppExtraUI *ui)
988 // Nothing
989 return ui;
992 static GnmAppExtraUI *
993 gnm_app_extra_ui_unref (GnmAppExtraUI *ui)
995 // Nothing
996 return ui;
999 GType
1000 gnm_app_extra_ui_get_type (void)
1002 static GType t = 0;
1004 if (t == 0) {
1005 t = g_boxed_type_register_static ("GnmAppExtraUI",
1006 (GBoxedCopyFunc)gnm_app_extra_ui_ref,
1007 (GBoxedFreeFunc)gnm_app_extra_ui_unref);
1009 return t;
1013 * gnm_app_add_extra_ui:
1014 * @group_name: action group name.
1015 * @actions: (element-type GnmAction): list of actions.
1016 * @layout: the xml string describing the menus and toolbars.
1017 * @domain: localization domain.
1019 * Returns: (transfer full): the newly allocated #GnmAppExtraUI.
1021 GnmAppExtraUI *
1022 gnm_app_add_extra_ui (char const *group_name,
1023 GSList *actions,
1024 const char *layout,
1025 char const *domain)
1027 GnmAppExtraUI *extra_ui = g_new0 (GnmAppExtraUI, 1);
1028 extra_uis = g_slist_prepend (extra_uis, extra_ui);
1029 extra_ui->group_name = g_strdup (group_name);
1030 extra_ui->actions = actions;
1031 extra_ui->layout = g_strdup (layout);
1032 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_ADDED], 0, extra_ui);
1033 if (gnm_debug_flag ("extra-ui"))
1034 g_printerr ("Adding extra ui [%s] %p\n", group_name, extra_ui);
1035 return extra_ui;
1038 void
1039 gnm_app_remove_extra_ui (GnmAppExtraUI *extra_ui)
1041 if (gnm_debug_flag ("extra-ui"))
1042 g_printerr ("Removing extra ui %p\n", extra_ui);
1043 extra_uis = g_slist_remove (extra_uis, extra_ui);
1044 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_REMOVED], 0, extra_ui);
1045 g_free (extra_ui->group_name);
1046 g_free (extra_ui->layout);
1047 g_free (extra_ui);
1051 * gnm_app_foreach_extra_ui:
1052 * @func: (scope call): #GFunc
1053 * @data: user data.
1055 * Applies @func to each #GnmAppExtraUI.
1057 void
1058 gnm_app_foreach_extra_ui (GFunc func, gpointer data)
1060 g_slist_foreach (extra_uis, func, data);
1063 /**********************************************************************/
1065 static gint windows_update_timer = 0;
1066 static gboolean
1067 cb_flag_windows_changed (void)
1069 if (app)
1070 g_signal_emit (G_OBJECT (app), signals[WINDOW_LIST_CHANGED], 0);
1071 windows_update_timer = 0;
1072 return FALSE;
1076 * _gnm_app_flag_windows_changed:
1078 * An internal utility routine to flag a regeneration of the window lists
1080 void
1081 _gnm_app_flag_windows_changed (void)
1083 if (windows_update_timer == 0)
1084 windows_update_timer = g_timeout_add (100,
1085 (GSourceFunc)cb_flag_windows_changed, NULL);
1088 /**********************************************************************/
1091 * gnm_app_recalc:
1093 * Recalculate everything dirty in all workbooks that have automatic
1094 * recalc turned on.
1096 void
1097 gnm_app_recalc (void)
1099 GList *l;
1101 g_return_if_fail (app != NULL);
1103 gnm_app_recalc_start ();
1105 for (l = app->workbook_list; l; l = l->next) {
1106 Workbook *wb = l->data;
1108 if (workbook_get_recalcmode (wb))
1109 workbook_recalc (wb);
1112 gnm_app_recalc_finish ();
1115 void
1116 gnm_app_recalc_start (void)
1118 g_return_if_fail (app->recalc_count >= 0);
1119 app->recalc_count++;
1122 void
1123 gnm_app_recalc_finish (void)
1125 g_return_if_fail (app->recalc_count > 0);
1126 app->recalc_count--;
1127 if (app->recalc_count == 0) {
1128 gnm_app_recalc_clear_caches ();
1129 g_signal_emit_by_name (gnm_app_get_app (), "recalc-finished");
1133 void
1134 gnm_app_recalc_clear_caches (void)
1136 g_signal_emit_by_name (gnm_app_get_app (), "recalc-clear-caches");
1139 gboolean
1140 gnm_app_shutting_down (void)
1142 return app->shutting_down;
1145 gboolean
1146 gnm_app_initial_open_complete (void)
1148 return app->initial_open_complete;