Compilation: fix up tools includes.
[gnumeric.git] / src / application.c
blobd1e9c4d82573026651e593ac5ecde38101598f85
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 "gutils.h"
23 #include "ranges.h"
24 #include "sheet-object.h"
25 #include "commands.h"
26 #include "gui-clipboard.h"
27 #include "expr-name.h"
28 #include "workbook-priv.h"
30 #include <gnumeric-conf.h>
31 #include <goffice/goffice.h>
32 #include <gsf/gsf-impl-utils.h>
33 #include "gnm-i18n.h"
34 #include <gtk/gtk.h>
36 #define GNM_APP(o) (G_TYPE_CHECK_INSTANCE_CAST((o), GNM_APP_TYPE, GnmApp))
37 #define GNM_IS_APP(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), GNM_APP_TYPE))
39 enum {
40 PROP_0,
41 PROP_HISTORY_LIST,
42 PROP_SHUTTING_DOWN,
43 PROP_INITIAL_OPEN_COMPLETE
45 /* Signals */
46 enum {
47 WORKBOOK_ADDED,
48 WORKBOOK_REMOVED,
49 WINDOW_LIST_CHANGED,
50 CUSTOM_UI_ADDED,
51 CUSTOM_UI_REMOVED,
52 CLIPBOARD_MODIFIED,
53 RECALC_FINISHED,
54 RECALC_CLEAR_CACHES,
55 LAST_SIGNAL
58 static guint signals[LAST_SIGNAL] = { 0 };
60 struct _GnmApp {
61 GObject base;
63 /* Clipboard */
64 SheetView *clipboard_sheet_view;
65 GnmCellRegion *clipboard_copied_contents;
66 GnmRange *clipboard_cut_range;
68 GList *workbook_list;
70 /* Recalculation manager. */
71 int recalc_count;
73 GtkRecentManager *recent;
74 gulong recent_sig;
76 gboolean shutting_down;
77 gboolean initial_open_complete;
80 typedef struct {
81 GObjectClass parent;
83 void (*workbook_added) (GnmApp *gnm_app, Workbook *wb);
84 void (*workbook_removed) (GnmApp *gnm_app, Workbook *wb);
85 void (*window_list_changed) (GnmApp *gnm_app);
86 void (*custom_ui_added) (GnmApp *gnm_app, GnmAppExtraUI *ui);
87 void (*custom_ui_removed) (GnmApp *gnm_app, GnmAppExtraUI *ui);
88 void (*clipboard_modified) (GnmApp *gnm_app);
89 void (*recalc_finished) (GnmApp *gnm_app);
90 void (*recalc_clear_caches) (GnmApp *gnm_app);
91 } GnmAppClass;
93 static GObjectClass *parent_klass;
94 static GnmApp *app;
96 static Workbook *gnm_app_workbook_get_by_uri (char const *uri);
98 /**
99 * gnm_app_get_app:
101 * Returns: (transfer none): the #GnmApp instance.
103 GObject *
104 gnm_app_get_app (void)
106 return G_OBJECT (app);
110 * gnm_app_workbook_list_add:
111 * @wb: A #Workbook
113 * Add @wb to the application's list of workbooks.
115 void
116 gnm_app_workbook_list_add (Workbook *wb)
118 g_return_if_fail (GNM_IS_WORKBOOK (wb));
119 g_return_if_fail (app != NULL);
121 app->workbook_list = g_list_prepend (app->workbook_list, wb);
122 g_signal_connect (G_OBJECT (wb),
123 "notify::uri",
124 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
125 _gnm_app_flag_windows_changed ();
126 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_ADDED], 0, wb);
130 * gnm_app_workbook_list_remove:
131 * @wb: A #Workbook
133 * Remove @wb from the application's list of workbooks.
135 void
136 gnm_app_workbook_list_remove (Workbook *wb)
138 g_return_if_fail (wb != NULL);
139 g_return_if_fail (app != NULL);
141 app->workbook_list = g_list_remove (app->workbook_list, wb);
142 g_signal_handlers_disconnect_by_func (G_OBJECT (wb),
143 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
144 _gnm_app_flag_windows_changed ();
145 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_REMOVED], 0, wb);
149 * gnm_app_workbook_list:
151 * Returns: (element-type Workbook) (transfer none): the workbook list.
153 GList *
154 gnm_app_workbook_list (void)
156 g_return_val_if_fail (app != NULL, NULL);
158 return app->workbook_list;
161 void
162 gnm_app_sanity_check (void)
164 GList *l;
165 gboolean err = FALSE;
166 for (l = gnm_app_workbook_list (); l; l = l->next) {
167 Workbook *wb = l->data;
168 if (gnm_named_expr_collection_sanity_check (wb->names, "workbook"))
169 err = TRUE;
171 if (err)
172 g_error ("Sanity check failed\n");
178 * gnm_app_clipboard_clear:
180 * Clear and free the contents of the clipboard if it is
181 * not empty.
183 void
184 gnm_app_clipboard_clear (gboolean drop_selection)
186 g_return_if_fail (app != NULL);
188 if (app->clipboard_copied_contents) {
189 cellregion_unref (app->clipboard_copied_contents);
190 app->clipboard_copied_contents = NULL;
192 if (app->clipboard_sheet_view != NULL) {
193 gnm_sheet_view_unant (app->clipboard_sheet_view);
195 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
197 gnm_sheet_view_weak_unref (&(app->clipboard_sheet_view));
199 /* Release the selection */
200 if (drop_selection)
201 gnm_x_disown_clipboard ();
205 void
206 gnm_app_clipboard_invalidate_sheet (Sheet *sheet)
208 /* Clear the cliboard to avoid dangling references to the deleted sheet */
209 if (sheet == gnm_app_clipboard_sheet_get ())
210 gnm_app_clipboard_clear (TRUE);
211 else if (app->clipboard_copied_contents)
212 cellregion_invalidate_sheet (app->clipboard_copied_contents, sheet);
215 void
216 gnm_app_clipboard_unant (void)
218 g_return_if_fail (app != NULL);
220 if (app->clipboard_sheet_view != NULL)
221 gnm_sheet_view_unant (app->clipboard_sheet_view);
225 * gnm_app_clipboard_cut_copy:
226 * @wbc: the workbook control that requested the operation.
227 * @is_cut: is this a cut or a copy.
228 * @sv: The source sheet for the copy.
229 * @area: A single rectangular range to be copied.
230 * @animate_range: Do we want ot add an animated cursor around things.
232 * When Cutting we
233 * Clear and free the contents of the clipboard and save the sheet and area
234 * to be cut. DO NOT ACTUALLY CUT! Paste will move the region if this was a
235 * cut operation.
237 * When Copying we
238 * Clear and free the contents of the clipboard and COPY the designated region
239 * into the clipboard.
241 * we need to pass @wbc as a control rather than a simple command-context so
242 * that the control can claim the selection.
244 void
245 gnm_app_clipboard_cut_copy (WorkbookControl *wbc, gboolean is_cut,
246 SheetView *sv, GnmRange const *area,
247 gboolean animate_cursor)
249 Sheet *sheet;
251 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
252 g_return_if_fail (area != NULL);
253 g_return_if_fail (app != NULL);
255 gnm_app_clipboard_clear (FALSE);
256 sheet = sv_sheet (sv);
257 g_free (app->clipboard_cut_range);
258 app->clipboard_cut_range = gnm_range_dup (area);
259 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
261 if (!is_cut)
262 app->clipboard_copied_contents =
263 clipboard_copy_range (sheet, area);
264 if (animate_cursor) {
265 GList *l = g_list_append (NULL, (gpointer)area);
266 gnm_sheet_view_ant (sv, l);
267 g_list_free (l);
270 if (wb_control_claim_selection (wbc)) {
271 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
272 } else {
273 gnm_app_clipboard_clear (FALSE);
274 g_warning ("Unable to set selection?");
279 * gnm_app_clipboard_cut_copy_obj:
280 * @wbc: #WorkbookControl
281 * @is_cut: %TRUE for cut, %FALSE for copy
282 * @sv: #SheetView
283 * @objects: (element-type SheetObject) (transfer container): a list
284 * of #SheetObject
286 * Different than copying/cutting a region, this can actually cut an object
288 void
289 gnm_app_clipboard_cut_copy_obj (WorkbookControl *wbc, gboolean is_cut,
290 SheetView *sv, GSList *objects)
292 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
293 g_return_if_fail (objects != NULL);
294 g_return_if_fail (app != NULL);
296 gnm_app_clipboard_clear (FALSE);
297 g_free (app->clipboard_cut_range);
298 app->clipboard_cut_range = NULL;
299 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
300 app->clipboard_copied_contents
301 = clipboard_copy_obj (sv_sheet (sv), objects);
302 if (is_cut) {
303 cmd_objects_delete (wbc, objects, _("Cut Object"));
304 objects = NULL;
306 if (wb_control_claim_selection (wbc)) {
307 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
308 } else {
309 gnm_app_clipboard_clear (FALSE);
310 g_warning ("Unable to set selection?");
312 g_slist_free (objects);
315 gboolean
316 gnm_app_clipboard_is_empty (void)
318 g_return_val_if_fail (app != NULL, TRUE);
320 return app->clipboard_sheet_view == NULL;
323 gboolean
324 gnm_app_clipboard_is_cut (void)
326 g_return_val_if_fail (app != NULL, FALSE);
328 if (app->clipboard_sheet_view != NULL)
329 return app->clipboard_copied_contents ? FALSE : TRUE;
330 return FALSE;
334 * gnm_app_clipboard_sheet_get:
336 * Returns: (transfer none) (nullable): the current clipboard #Sheet.
338 Sheet *
339 gnm_app_clipboard_sheet_get (void)
341 g_return_val_if_fail (app != NULL, NULL);
343 if (app->clipboard_sheet_view == NULL)
344 return NULL;
345 return sv_sheet (app->clipboard_sheet_view);
349 * gnm_app_clipboard_sheet_view_get:
351 * Returns: (transfer none) (nullable): the current clipboard #SheetView.
353 SheetView *
354 gnm_app_clipboard_sheet_view_get (void)
356 g_return_val_if_fail (app != NULL, NULL);
357 return app->clipboard_sheet_view;
361 * gnm_app_clipboard_contents_get:
363 * Returns: (nullable) (transfer none): the current contents of the clipboard.
365 GnmCellRegion *
366 gnm_app_clipboard_contents_get (void)
368 g_return_val_if_fail (app != NULL, NULL);
369 return app->clipboard_copied_contents;
373 * gnm_app_clipboard_area_get:
375 * Returns: (nullable) (transfer none): the current range in the clipboard.
377 GnmRange const *
378 gnm_app_clipboard_area_get (void)
380 g_return_val_if_fail (app != NULL, NULL);
382 * Only return the range if the sheet has been set.
383 * The range will still contain data even after
384 * the clipboard has been cleared so we need to be extra
385 * safe and only return a range if there is a valid selection
387 if (app->clipboard_sheet_view != NULL)
388 return app->clipboard_cut_range;
389 return NULL;
393 * gnm_app_workbook_get_by_name:
394 * @name: the workbook name.
395 * @ref_uri:
397 * Returns: (transfer none): the #Workbook or %NULL.
399 Workbook *
400 gnm_app_workbook_get_by_name (char const *name,
401 char const *ref_uri)
403 Workbook *wb;
404 char *filename = NULL;
406 if (name == NULL || *name == 0)
407 return NULL;
409 /* Try as URI. */
410 wb = gnm_app_workbook_get_by_uri (name);
411 if (wb)
412 goto out;
414 filename = g_filename_from_utf8 (name, -1, NULL, NULL, NULL);
416 /* Try as absolute filename. */
417 if (filename && g_path_is_absolute (filename)) {
418 char *uri = go_filename_to_uri (filename);
419 if (uri) {
420 wb = gnm_app_workbook_get_by_uri (uri);
421 g_free (uri);
423 if (wb)
424 goto out;
427 if (filename && ref_uri) {
428 char *rel_uri = go_url_encode (filename, 1);
429 char *uri = go_url_resolve_relative (ref_uri, rel_uri);
430 g_free (rel_uri);
431 if (uri) {
432 wb = gnm_app_workbook_get_by_uri (uri);
433 g_free (uri);
435 if (wb)
436 goto out;
439 out:
440 g_free (filename);
441 return wb;
444 struct wb_uri_closure {
445 Workbook *wb;
446 char const *uri;
449 static gboolean
450 cb_workbook_uri (Workbook * wb, gpointer closure)
452 struct wb_uri_closure *dat = closure;
453 char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
455 if (wb_uri && strcmp (wb_uri, dat->uri) == 0) {
456 dat->wb = wb;
457 return FALSE;
459 return TRUE;
462 static gboolean
463 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data);
465 static Workbook *
466 gnm_app_workbook_get_by_uri (char const *uri)
468 struct wb_uri_closure closure;
470 g_return_val_if_fail (uri != NULL, NULL);
472 closure.wb = NULL;
473 closure.uri = uri;
474 gnm_app_workbook_foreach (&cb_workbook_uri, &closure);
476 return closure.wb;
479 static gboolean
480 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data)
482 GList *l;
484 g_return_val_if_fail (app != NULL, FALSE);
486 for (l = app->workbook_list; l; l = l->next){
487 Workbook *wb = l->data;
489 if (!(*cback)(wb, data))
490 return FALSE;
492 return TRUE;
496 * gnm_app_workbook_get_by_index:
497 * @i: index
499 * Get nth workbook. Index is zero-based.
500 * Return value: (nullable) (transfer none): the nth workbook, if any.
502 Workbook *
503 gnm_app_workbook_get_by_index (int i)
505 return g_list_nth_data (app->workbook_list, i);
508 double
509 gnm_app_display_dpi_get (gboolean horizontal)
511 return horizontal
512 ? gnm_conf_get_core_gui_screen_horizontaldpi ()
513 : gnm_conf_get_core_gui_screen_verticaldpi ();
516 double
517 gnm_app_dpi_to_pixels (void)
519 return MIN (gnm_app_display_dpi_get (TRUE),
520 gnm_app_display_dpi_get (FALSE)) / 72.;
523 /* GtkFileFilter */
525 * gnm_app_create_opener_filter:
526 * @openers: (element-type GOFileOpener): a list of file openers.
528 * Creates a #GtkFileFilter from the list of file types supported by the
529 * openers in the list.
530 * Returns: (transfer full): the newly allocated #GtkFileFilter.
532 void *
533 gnm_app_create_opener_filter (GList *openers)
535 /* See below. */
536 static const char *const bad_suffixes[] = {
537 "txt",
538 "html", "htm",
539 "xml",
540 NULL
543 GtkFileFilter *filter = gtk_file_filter_new ();
544 gboolean for_history = (openers == NULL);
546 if (openers == NULL)
547 openers = go_get_file_openers ();
549 for (; openers; openers = openers->next) {
550 GOFileOpener *opener = openers->data;
551 if (opener != NULL) {
552 const GSList *mimes = go_file_opener_get_mimes (opener);
553 const GSList *suffixes = go_file_opener_get_suffixes (opener);
555 if (!for_history)
556 while (mimes) {
557 const char *mime = mimes->data;
559 * See 438918. Too many things
560 * like *.xml and *.txt get added
561 * to be useful for the file history
563 gtk_file_filter_add_mime_type (filter, mime);
564 if (0)
565 g_print ("%s: Adding mime %s\n", go_file_opener_get_description (opener), mime);
566 mimes = mimes->next;
569 while (suffixes) {
570 const char *suffix = suffixes->data;
571 GString *pattern;
572 int i;
574 if (for_history)
575 for (i = 0; bad_suffixes[i]; i++)
576 if (strcmp (suffix, bad_suffixes[i]) == 0)
577 goto bad_suffix;
579 /* Create "*.[xX][lL][sS]" */
580 pattern = g_string_new ("*.");
581 while (*suffix) {
582 gunichar uc = g_utf8_get_char (suffix);
583 suffix = g_utf8_next_char (suffix);
584 if (g_unichar_islower (uc)) {
585 g_string_append_c (pattern, '[');
586 g_string_append_unichar (pattern, uc);
587 uc = g_unichar_toupper (uc);
588 g_string_append_unichar (pattern, uc);
589 g_string_append_c (pattern, ']');
590 } else
591 g_string_append_unichar (pattern, uc);
594 gtk_file_filter_add_pattern (filter, pattern->str);
595 if (0)
596 g_print ("%s: Adding %s\n", go_file_opener_get_description (opener), pattern->str);
597 g_string_free (pattern, TRUE);
599 bad_suffix:
600 suffixes = suffixes->next;
604 return filter;
607 static gint
608 compare_mru (GtkRecentInfo *a, GtkRecentInfo *b)
610 time_t ta = MAX (gtk_recent_info_get_visited (a), gtk_recent_info_get_modified (a));
611 time_t tb = MAX (gtk_recent_info_get_visited (b), gtk_recent_info_get_modified (b));
613 return ta < tb;
617 * gnm_app_history_get_list:
619 * creating it if necessary.
621 * Return value: (element-type utf8) (transfer full): the list, which must be
622 * freed along with the strings in it.
624 GSList *
625 gnm_app_history_get_list (int max_elements)
627 GSList *res = NULL;
628 GList *items, *l;
629 GtkFileFilter *filter;
630 int n_elements = 0;
632 if (app->recent == NULL)
633 return NULL;
635 items = gtk_recent_manager_get_items (app->recent);
636 items = g_list_sort (items, (GCompareFunc)compare_mru);
638 filter = gnm_app_create_opener_filter (NULL);
640 for (l = items; l && n_elements < max_elements; l = l->next) {
641 GtkRecentInfo *ri = l->data;
642 const char *uri = gtk_recent_info_get_uri (ri);
643 gboolean want_it;
645 if (gtk_recent_info_has_application (ri, g_get_application_name ())) {
646 want_it = TRUE;
647 } else {
648 GtkFileFilterInfo fi;
649 char *display_name = g_filename_display_basename (uri);
651 memset (&fi, 0, sizeof (fi));
652 fi.contains = (GTK_FILE_FILTER_MIME_TYPE |
653 GTK_FILE_FILTER_URI |
654 GTK_FILE_FILTER_DISPLAY_NAME);
655 fi.uri = uri;
656 fi.mime_type = gtk_recent_info_get_mime_type (ri);
657 fi.display_name = display_name;
658 want_it = gtk_file_filter_filter (filter, &fi);
659 g_free (display_name);
662 if (want_it) {
663 char *filename = go_filename_from_uri (uri);
664 if (filename && !g_file_test (filename, G_FILE_TEST_EXISTS))
665 want_it = FALSE;
666 g_free (filename);
669 if (want_it) {
670 res = g_slist_prepend (res, g_strdup (uri));
671 n_elements++;
675 g_list_free_full (items, (GDestroyNotify)gtk_recent_info_unref);
676 g_object_ref_sink (filter);
677 g_object_unref (filter);
679 return g_slist_reverse (res);
683 * application_history_update_list:
684 * @uri:
685 * @mimetype: (nullable): the mime type for @uri
687 * Adds @uri to the application's history of files.
689 void
690 gnm_app_history_add (char const *uri, const char *mimetype)
692 GtkRecentData rd;
694 if (app->recent == NULL)
695 return;
697 memset (&rd, 0, sizeof (rd));
699 #if 0
700 g_print ("uri: %s\nmime: %s\n\n", uri, mimetype ? mimetype : "-");
701 #endif
703 rd.mime_type =
704 g_strdup (mimetype ? mimetype : "application/octet-stream");
706 rd.app_name = g_strdup (g_get_application_name ());
707 rd.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
708 rd.groups = NULL;
709 rd.is_private = FALSE;
711 if (!gtk_recent_manager_add_full (app->recent, uri, &rd)) {
712 /* Now what? */
713 g_printerr ("Warning: failed to update recent document.\n");
716 g_free (rd.mime_type);
717 g_free (rd.app_name);
718 g_free (rd.app_exec);
720 g_object_notify (G_OBJECT (app), "file-history-list");
723 static void
724 cb_recent_changed (G_GNUC_UNUSED GtkRecentManager *recent, GnmApp *app)
726 g_object_notify (G_OBJECT (app), "file-history-list");
729 static void
730 gnm_app_finalize (GObject *obj)
732 GnmApp *application = GNM_APP (obj);
734 g_free (application->clipboard_cut_range);
735 application->clipboard_cut_range = NULL;
737 application->recent = NULL;
739 if (app == application)
740 app = NULL;
742 G_OBJECT_CLASS (parent_klass)->finalize (obj);
745 static void
746 gnm_app_get_property (GObject *obj, guint param_id,
747 GValue *value, GParamSpec *pspec)
749 #if 0
750 GnmApp *application = GNM_APP (obj);
751 #endif
753 switch (param_id) {
754 case PROP_HISTORY_LIST:
755 g_value_set_pointer (value, gnm_app_history_get_list (G_MAXINT));
756 break;
757 case PROP_SHUTTING_DOWN:
758 g_value_set_boolean (value, gnm_app_shutting_down ());
759 break;
760 case PROP_INITIAL_OPEN_COMPLETE:
761 g_value_set_boolean (value, gnm_app_initial_open_complete ());
762 break;
763 default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
764 break;
768 static void
769 gnm_app_set_property (GObject *object, guint property_id,
770 GValue const *value, GParamSpec *pspec)
772 GnmApp *app = (GnmApp *)object;
774 switch (property_id) {
775 case PROP_SHUTTING_DOWN:
776 app->shutting_down = g_value_get_boolean (value);
777 break;
778 case PROP_INITIAL_OPEN_COMPLETE:
779 app->initial_open_complete = g_value_get_boolean (value);
780 break;
781 default:
782 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
783 break;
787 static void
788 gnm_app_class_init (GObjectClass *gobject_klass)
790 parent_klass = g_type_class_peek_parent (gobject_klass);
792 /* Object class method overrides */
793 gobject_klass->finalize = gnm_app_finalize;
794 gobject_klass->get_property = gnm_app_get_property;
795 gobject_klass->set_property = gnm_app_set_property;
797 g_object_class_install_property (gobject_klass, PROP_HISTORY_LIST,
798 g_param_spec_pointer ("file-history-list",
799 P_("File History List"),
800 P_("A list of filenames that have been read recently"),
801 GSF_PARAM_STATIC | G_PARAM_READABLE));
802 g_object_class_install_property (gobject_klass, PROP_SHUTTING_DOWN,
803 g_param_spec_boolean ("shutting-down",
804 P_("Shutting Down"),
805 P_("In the process of shutting down?"),
806 FALSE,
807 GSF_PARAM_STATIC | G_PARAM_READWRITE));
808 g_object_class_install_property (gobject_klass, PROP_INITIAL_OPEN_COMPLETE,
809 g_param_spec_boolean ("initial-open-complete",
810 P_("Initial Open Complete"),
811 P_("All command-line files open?"),
812 FALSE,
813 GSF_PARAM_STATIC | G_PARAM_READWRITE));
816 signals[WORKBOOK_ADDED] = g_signal_new ("workbook_added",
817 GNM_APP_TYPE,
818 G_SIGNAL_RUN_LAST,
819 G_STRUCT_OFFSET (GnmAppClass, workbook_added),
820 NULL, NULL,
821 g_cclosure_marshal_VOID__OBJECT,
822 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
823 signals[WORKBOOK_REMOVED] = g_signal_new ("workbook_removed",
824 GNM_APP_TYPE,
825 G_SIGNAL_RUN_LAST,
826 G_STRUCT_OFFSET (GnmAppClass, workbook_removed),
827 NULL, NULL,
828 g_cclosure_marshal_VOID__POINTER,
829 G_TYPE_NONE, 1, G_TYPE_POINTER);
830 signals[WINDOW_LIST_CHANGED] = g_signal_new ("window-list-changed",
831 GNM_APP_TYPE,
832 G_SIGNAL_RUN_LAST,
833 G_STRUCT_OFFSET (GnmAppClass, window_list_changed),
834 NULL, NULL,
835 g_cclosure_marshal_VOID__VOID,
836 G_TYPE_NONE, 0);
837 signals[CUSTOM_UI_ADDED] = g_signal_new ("custom-ui-added",
838 GNM_APP_TYPE,
839 G_SIGNAL_RUN_LAST,
840 G_STRUCT_OFFSET (GnmAppClass, custom_ui_added),
841 NULL, NULL,
842 g_cclosure_marshal_VOID__POINTER,
843 G_TYPE_NONE, 1, G_TYPE_POINTER);
844 signals[CUSTOM_UI_REMOVED] = g_signal_new ("custom-ui-removed",
845 GNM_APP_TYPE,
846 G_SIGNAL_RUN_LAST,
847 G_STRUCT_OFFSET (GnmAppClass, custom_ui_removed),
848 NULL, NULL,
849 g_cclosure_marshal_VOID__POINTER,
850 G_TYPE_NONE, 1, G_TYPE_POINTER);
851 signals[CLIPBOARD_MODIFIED] = g_signal_new ("clipboard_modified",
852 GNM_APP_TYPE,
853 G_SIGNAL_RUN_LAST,
854 G_STRUCT_OFFSET (GnmAppClass, clipboard_modified),
855 NULL, NULL,
856 g_cclosure_marshal_VOID__VOID,
857 G_TYPE_NONE, 0);
858 signals[RECALC_FINISHED] = g_signal_new ("recalc_finished",
859 GNM_APP_TYPE,
860 G_SIGNAL_RUN_LAST,
861 G_STRUCT_OFFSET (GnmAppClass, recalc_finished),
862 NULL, NULL,
863 g_cclosure_marshal_VOID__VOID,
864 G_TYPE_NONE, 0);
865 signals[RECALC_FINISHED] = g_signal_new ("recalc_clear_caches",
866 GNM_APP_TYPE,
867 G_SIGNAL_RUN_LAST,
868 G_STRUCT_OFFSET (GnmAppClass, recalc_clear_caches),
869 NULL, NULL,
870 g_cclosure_marshal_VOID__VOID,
871 G_TYPE_NONE, 0);
874 static void
875 gnm_app_init (GObject *obj)
877 GnmApp *gnm_app = GNM_APP (obj);
879 gnm_app->clipboard_copied_contents = NULL;
880 gnm_app->clipboard_sheet_view = NULL;
882 gnm_app->workbook_list = NULL;
884 if (gdk_display_get_default ()) {
886 * Only allocate a GtkRecentManager if we have a gui.
887 * This is, in part, because it currently throws an error.
888 * deep inside gtk+.
890 gnm_app->recent = gtk_recent_manager_get_default ();
891 g_signal_connect_object (G_OBJECT (gnm_app->recent),
892 "changed",
893 G_CALLBACK (cb_recent_changed),
894 gnm_app, 0);
897 app = gnm_app;
900 GSF_CLASS (GnmApp, gnm_app,
901 gnm_app_class_init, gnm_app_init,
902 G_TYPE_OBJECT)
904 /**********************************************************************/
905 static GSList *extra_uis = NULL;
908 * gnm_action_new:
909 * @name: action ID.
910 * @label: label.
911 * @icon: icon name.
912 * @always_available: whether the action should always be available.
913 * @handler: (scope async): the handler.
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)
922 GnmAction *res = g_new0 (GnmAction, 1);
923 res->id = g_strdup (id);
924 res->label = g_strdup (label);
925 res->icon_name = g_strdup (icon_name);
926 res->always_available = always_available;
927 res->handler = handler;
928 return res;
931 void
932 gnm_action_free (GnmAction *action)
934 if (NULL != action) {
935 g_free (action->id);
936 g_free (action->label);
937 g_free (action->icon_name);
938 g_free (action);
942 static GnmAction *
943 gnm_action_copy (GnmAction const *action)
945 return gnm_action_new (action->id, action->label, action->icon_name,
946 action->always_available, action->handler);
949 GType
950 gnm_action_get_type (void)
952 static GType t = 0;
954 if (t == 0) {
955 t = g_boxed_type_register_static ("GnmAction",
956 (GBoxedCopyFunc)gnm_action_copy,
957 (GBoxedFreeFunc)gnm_action_free);
959 return t;
962 /***************
963 * making GnmAppExtraUI a boxed type for introspection. copy and free don't do
964 anything which might be critical, crossing fingers.*/
966 static GnmAppExtraUI *
967 gnm_app_extra_ui_ref (GnmAppExtraUI *ui)
969 // Nothing
970 return ui;
973 static GnmAppExtraUI *
974 gnm_app_extra_ui_unref (GnmAppExtraUI *ui)
976 // Nothing
977 return ui;
980 GType
981 gnm_app_extra_ui_get_type (void)
983 static GType t = 0;
985 if (t == 0) {
986 t = g_boxed_type_register_static ("GnmAppExtraUI",
987 (GBoxedCopyFunc)gnm_app_extra_ui_ref,
988 (GBoxedFreeFunc)gnm_app_extra_ui_unref);
990 return t;
994 * gnm_app_add_extra_ui:
995 * @group_name: action group name.
996 * @actions: (element-type GnmAction): list of actions.
997 * @layout: the xml string describing the menus and toolbars.
998 * @domain: localization domain.
999 * @user_data: user data
1001 * Returns: (transfer full): the newly allocated #GnmAppExtraUI.
1003 GnmAppExtraUI *
1004 gnm_app_add_extra_ui (char const *group_name,
1005 GSList *actions,
1006 const char *layout,
1007 char const *domain,
1008 gpointer user_data)
1010 GnmAppExtraUI *extra_ui = g_new0 (GnmAppExtraUI, 1);
1011 extra_uis = g_slist_prepend (extra_uis, extra_ui);
1012 extra_ui->group_name = g_strdup (group_name);
1013 extra_ui->actions = actions;
1014 extra_ui->layout = g_strdup (layout);
1015 extra_ui->user_data = user_data;
1016 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_ADDED], 0, extra_ui);
1017 if (gnm_debug_flag ("extra-ui"))
1018 g_printerr ("Adding extra ui [%s] %p\n", group_name, extra_ui);
1019 return extra_ui;
1022 void
1023 gnm_app_remove_extra_ui (GnmAppExtraUI *extra_ui)
1025 if (gnm_debug_flag ("extra-ui"))
1026 g_printerr ("Removing extra ui %p\n", extra_ui);
1027 extra_uis = g_slist_remove (extra_uis, extra_ui);
1028 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_REMOVED], 0, extra_ui);
1029 g_free (extra_ui->group_name);
1030 g_free (extra_ui->layout);
1031 g_free (extra_ui);
1035 * gnm_app_foreach_extra_ui:
1036 * @func: (scope call): #GFunc
1037 * @data: user data.
1039 * Applies @func to each #GnmAppExtraUI.
1041 void
1042 gnm_app_foreach_extra_ui (GFunc func, gpointer data)
1044 g_slist_foreach (extra_uis, func, data);
1047 /**********************************************************************/
1049 static gint windows_update_timer = 0;
1050 static gboolean
1051 cb_flag_windows_changed (void)
1053 if (app)
1054 g_signal_emit (G_OBJECT (app), signals[WINDOW_LIST_CHANGED], 0);
1055 windows_update_timer = 0;
1056 return FALSE;
1060 * _gnm_app_flag_windows_changed:
1062 * An internal utility routine to flag a regeneration of the window lists
1064 void
1065 _gnm_app_flag_windows_changed (void)
1067 if (windows_update_timer == 0)
1068 windows_update_timer = g_timeout_add (100,
1069 (GSourceFunc)cb_flag_windows_changed, NULL);
1072 /**********************************************************************/
1075 * gnm_app_recalc:
1077 * Recalculate everything dirty in all workbooks that have automatic
1078 * recalc turned on.
1080 void
1081 gnm_app_recalc (void)
1083 GList *l;
1085 g_return_if_fail (app != NULL);
1087 gnm_app_recalc_start ();
1089 for (l = app->workbook_list; l; l = l->next) {
1090 Workbook *wb = l->data;
1092 if (workbook_get_recalcmode (wb))
1093 workbook_recalc (wb);
1096 gnm_app_recalc_finish ();
1099 void
1100 gnm_app_recalc_start (void)
1102 g_return_if_fail (app->recalc_count >= 0);
1103 app->recalc_count++;
1106 void
1107 gnm_app_recalc_finish (void)
1109 g_return_if_fail (app->recalc_count > 0);
1110 app->recalc_count--;
1111 if (app->recalc_count == 0) {
1112 gnm_app_recalc_clear_caches ();
1113 g_signal_emit_by_name (gnm_app_get_app (), "recalc-finished");
1117 void
1118 gnm_app_recalc_clear_caches (void)
1120 g_signal_emit_by_name (gnm_app_get_app (), "recalc-clear-caches");
1123 gboolean
1124 gnm_app_shutting_down (void)
1126 return app->shutting_down;
1129 gboolean
1130 gnm_app_initial_open_complete (void)
1132 return app->initial_open_complete;