Code cleanup
[gnumeric.git] / src / application.c
blob1aed4a1b903b573a4acdb7d73e69ad7ce7e21678
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 APPLICATION_PROP_0,
42 APPLICATION_PROP_FILE_HISTORY_LIST
44 /* Signals */
45 enum {
46 WORKBOOK_ADDED,
47 WORKBOOK_REMOVED,
48 WINDOW_LIST_CHANGED,
49 CUSTOM_UI_ADDED,
50 CUSTOM_UI_REMOVED,
51 CLIPBOARD_MODIFIED,
52 RECALC_FINISHED,
53 RECALC_CLEAR_CACHES,
54 LAST_SIGNAL
57 static guint signals[LAST_SIGNAL] = { 0 };
59 struct _GnmApp {
60 GObject base;
62 /* Clipboard */
63 SheetView *clipboard_sheet_view;
64 GnmCellRegion *clipboard_copied_contents;
65 GnmRange *clipboard_cut_range;
67 GList *workbook_list;
69 /* Recalculation manager. */
70 int recalc_count;
72 GtkRecentManager *recent;
73 gulong recent_sig;
76 typedef struct {
77 GObjectClass parent;
79 void (*workbook_added) (GnmApp *gnm_app, Workbook *wb);
80 void (*workbook_removed) (GnmApp *gnm_app, Workbook *wb);
81 void (*window_list_changed) (GnmApp *gnm_app);
82 void (*custom_ui_added) (GnmApp *gnm_app, GnmAppExtraUI *ui);
83 void (*custom_ui_removed) (GnmApp *gnm_app, GnmAppExtraUI *ui);
84 void (*clipboard_modified) (GnmApp *gnm_app);
85 void (*recalc_finished) (GnmApp *gnm_app);
86 void (*recalc_clear_caches) (GnmApp *gnm_app);
87 } GnmAppClass;
89 static GObjectClass *parent_klass;
90 static GnmApp *app;
92 static Workbook *gnm_app_workbook_get_by_uri (char const *uri);
94 /**
95 * gnm_app_get_app:
97 * Returns: (transfer none): the #GnmApp instance.
98 **/
99 GObject *
100 gnm_app_get_app (void)
102 return G_OBJECT (app);
106 * gnm_app_workbook_list_add:
107 * @wb:
109 * Add @wb to the application's list of workbooks.
111 void
112 gnm_app_workbook_list_add (Workbook *wb)
114 g_return_if_fail (GNM_IS_WORKBOOK (wb));
115 g_return_if_fail (app != NULL);
117 app->workbook_list = g_list_prepend (app->workbook_list, wb);
118 g_signal_connect (G_OBJECT (wb),
119 "notify::uri",
120 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
121 _gnm_app_flag_windows_changed ();
122 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_ADDED], 0, wb);
126 * gnm_app_workbook_list_remove:
127 * @wb:
129 * Remove @wb from the application's list of workbooks.
131 void
132 gnm_app_workbook_list_remove (Workbook *wb)
134 g_return_if_fail (wb != NULL);
135 g_return_if_fail (app != NULL);
137 app->workbook_list = g_list_remove (app->workbook_list, wb);
138 g_signal_handlers_disconnect_by_func (G_OBJECT (wb),
139 G_CALLBACK (_gnm_app_flag_windows_changed), NULL);
140 _gnm_app_flag_windows_changed ();
141 g_signal_emit (G_OBJECT (app), signals[WORKBOOK_REMOVED], 0, wb);
145 * gnm_app_workbook_list:
147 * Returns: (element-type Workbook) (transfer none): the workbook list.
149 GList *
150 gnm_app_workbook_list (void)
152 g_return_val_if_fail (app != NULL, NULL);
154 return app->workbook_list;
157 void
158 gnm_app_sanity_check (void)
160 GList *l;
161 gboolean err = FALSE;
162 for (l = gnm_app_workbook_list (); l; l = l->next) {
163 Workbook *wb = l->data;
164 if (gnm_named_expr_collection_sanity_check (wb->names, "workbook"))
165 err = TRUE;
167 if (err)
168 g_error ("Sanity check failed\n");
174 * gnm_app_clipboard_clear:
176 * Clear and free the contents of the clipboard if it is
177 * not empty.
179 void
180 gnm_app_clipboard_clear (gboolean drop_selection)
182 g_return_if_fail (app != NULL);
184 if (app->clipboard_copied_contents) {
185 cellregion_unref (app->clipboard_copied_contents);
186 app->clipboard_copied_contents = NULL;
188 if (app->clipboard_sheet_view != NULL) {
189 gnm_sheet_view_unant (app->clipboard_sheet_view);
191 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
193 gnm_sheet_view_weak_unref (&(app->clipboard_sheet_view));
195 /* Release the selection */
196 if (drop_selection)
197 gnm_x_disown_clipboard ();
201 void
202 gnm_app_clipboard_invalidate_sheet (Sheet *sheet)
204 /* Clear the cliboard to avoid dangling references to the deleted sheet */
205 if (sheet == gnm_app_clipboard_sheet_get ())
206 gnm_app_clipboard_clear (TRUE);
207 else if (app->clipboard_copied_contents)
208 cellregion_invalidate_sheet (app->clipboard_copied_contents, sheet);
211 void
212 gnm_app_clipboard_unant (void)
214 g_return_if_fail (app != NULL);
216 if (app->clipboard_sheet_view != NULL)
217 gnm_sheet_view_unant (app->clipboard_sheet_view);
221 * gnm_app_clipboard_cut_copy:
222 * @wbc: the workbook control that requested the operation.
223 * @is_cut: is this a cut or a copy.
224 * @sv: The source sheet for the copy.
225 * @area: A single rectangular range to be copied.
226 * @animate_range: Do we want ot add an animated cursor around things.
228 * When Cutting we
229 * Clear and free the contents of the clipboard and save the sheet and area
230 * to be cut. DO NOT ACTUALLY CUT! Paste will move the region if this was a
231 * cut operation.
233 * When Copying we
234 * Clear and free the contents of the clipboard and COPY the designated region
235 * into the clipboard.
237 * we need to pass @wbc as a control rather than a simple command-context so
238 * that the control can claim the selection.
240 void
241 gnm_app_clipboard_cut_copy (WorkbookControl *wbc, gboolean is_cut,
242 SheetView *sv, GnmRange const *area,
243 gboolean animate_cursor)
245 Sheet *sheet;
247 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
248 g_return_if_fail (area != NULL);
249 g_return_if_fail (app != NULL);
251 gnm_app_clipboard_clear (FALSE);
252 sheet = sv_sheet (sv);
253 g_free (app->clipboard_cut_range);
254 app->clipboard_cut_range = gnm_range_dup (area);
255 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
257 if (!is_cut)
258 app->clipboard_copied_contents =
259 clipboard_copy_range (sheet, area);
260 if (animate_cursor) {
261 GList *l = g_list_append (NULL, (gpointer)area);
262 gnm_sheet_view_ant (sv, l);
263 g_list_free (l);
266 if (wb_control_claim_selection (wbc)) {
267 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
268 } else {
269 gnm_app_clipboard_clear (FALSE);
270 g_warning ("Unable to set selection ?");
275 * gnm_app_clipboard_cut_copy_obj:
276 * @wbc: #WorkbookControl
277 * @is_cut:
278 * @sv: #SheetView
279 * @objects: (element-type SheetObject): a list of #SheetObject which is freed
281 * Different than copying/cutting a region, this can actually cuts an object
283 void
284 gnm_app_clipboard_cut_copy_obj (WorkbookControl *wbc, gboolean is_cut,
285 SheetView *sv, GSList *objects)
287 g_return_if_fail (GNM_IS_SHEET_VIEW (sv));
288 g_return_if_fail (objects != NULL);
289 g_return_if_fail (app != NULL);
291 gnm_app_clipboard_clear (FALSE);
292 g_free (app->clipboard_cut_range);
293 app->clipboard_cut_range = NULL;
294 gnm_sheet_view_weak_ref (sv, &(app->clipboard_sheet_view));
295 app->clipboard_copied_contents
296 = clipboard_copy_obj (sv_sheet (sv), objects);
297 if (is_cut) {
298 cmd_objects_delete (wbc, objects, _("Cut Object"));
299 objects = NULL;
301 if (wb_control_claim_selection (wbc)) {
302 g_signal_emit (G_OBJECT (app), signals[CLIPBOARD_MODIFIED], 0);
303 } else {
304 gnm_app_clipboard_clear (FALSE);
305 g_warning ("Unable to set selection ?");
307 g_slist_free (objects);
310 gboolean
311 gnm_app_clipboard_is_empty (void)
313 g_return_val_if_fail (app != NULL, TRUE);
315 return app->clipboard_sheet_view == NULL;
318 gboolean
319 gnm_app_clipboard_is_cut (void)
321 g_return_val_if_fail (app != NULL, FALSE);
323 if (app->clipboard_sheet_view != NULL)
324 return app->clipboard_copied_contents ? FALSE : TRUE;
325 return FALSE;
329 * gnm_app_clipboard_sheet_get:
331 * Returns: (transfer none): the current clipboard #Sheet.
333 Sheet *
334 gnm_app_clipboard_sheet_get (void)
336 g_return_val_if_fail (app != NULL, NULL);
338 if (app->clipboard_sheet_view == NULL)
339 return NULL;
340 return sv_sheet (app->clipboard_sheet_view);
344 * gnm_app_clipboard_sheet_view_get:
346 * Returns: (transfer none): the current clipboard #SheetView.
348 SheetView *
349 gnm_app_clipboard_sheet_view_get (void)
351 g_return_val_if_fail (app != NULL, NULL);
352 return app->clipboard_sheet_view;
355 GnmCellRegion *
356 gnm_app_clipboard_contents_get (void)
358 g_return_val_if_fail (app != NULL, NULL);
359 return app->clipboard_copied_contents;
362 GnmRange const *
363 gnm_app_clipboard_area_get (void)
365 g_return_val_if_fail (app != NULL, NULL);
367 * Only return the range if the sheet has been set.
368 * The range will still contain data even after
369 * the clipboard has been cleared so we need to be extra
370 * safe and only return a range if there is a valid selection
372 if (app->clipboard_sheet_view != NULL)
373 return app->clipboard_cut_range;
374 return NULL;
378 * gnm_app_workbook_get_by_name:
379 * @name: the workbook name.
380 * @ref_uri:
382 * Returns: (transfer none): the #Workbook or %NULL.
384 Workbook *
385 gnm_app_workbook_get_by_name (char const *name,
386 char const *ref_uri)
388 Workbook *wb;
389 char *filename = NULL;
391 if (name == NULL || *name == 0)
392 return NULL;
394 /* Try as URI. */
395 wb = gnm_app_workbook_get_by_uri (name);
396 if (wb)
397 goto out;
399 filename = g_filename_from_utf8 (name, -1, NULL, NULL, NULL);
401 /* Try as absolute filename. */
402 if (filename && g_path_is_absolute (filename)) {
403 char *uri = go_filename_to_uri (filename);
404 if (uri) {
405 wb = gnm_app_workbook_get_by_uri (uri);
406 g_free (uri);
408 if (wb)
409 goto out;
412 if (filename && ref_uri) {
413 char *rel_uri = go_url_encode (filename, 1);
414 char *uri = go_url_resolve_relative (ref_uri, rel_uri);
415 g_free (rel_uri);
416 if (uri) {
417 wb = gnm_app_workbook_get_by_uri (uri);
418 g_free (uri);
420 if (wb)
421 goto out;
424 out:
425 g_free (filename);
426 return wb;
429 struct wb_uri_closure {
430 Workbook *wb;
431 char const *uri;
434 static gboolean
435 cb_workbook_uri (Workbook * wb, gpointer closure)
437 struct wb_uri_closure *dat = closure;
438 char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
440 if (wb_uri && strcmp (wb_uri, dat->uri) == 0) {
441 dat->wb = wb;
442 return FALSE;
444 return TRUE;
447 static gboolean
448 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data);
450 static Workbook *
451 gnm_app_workbook_get_by_uri (char const *uri)
453 struct wb_uri_closure closure;
455 g_return_val_if_fail (uri != NULL, NULL);
457 closure.wb = NULL;
458 closure.uri = uri;
459 gnm_app_workbook_foreach (&cb_workbook_uri, &closure);
461 return closure.wb;
464 static gboolean
465 gnm_app_workbook_foreach (GnmWbIterFunc cback, gpointer data)
467 GList *l;
469 g_return_val_if_fail (app != NULL, FALSE);
471 for (l = app->workbook_list; l; l = l->next){
472 Workbook *wb = l->data;
474 if (!(*cback)(wb, data))
475 return FALSE;
477 return TRUE;
481 * gnm_app_workbook_get_by_index:
482 * @i: index
484 * Get nth workbook. Index is zero-based.
485 * Return value: (transfer none): the nth workbook if any.
487 Workbook *
488 gnm_app_workbook_get_by_index (int i)
490 return g_list_nth_data (app->workbook_list, i);
493 double
494 gnm_app_display_dpi_get (gboolean horizontal)
496 return horizontal
497 ? gnm_conf_get_core_gui_screen_horizontaldpi ()
498 : gnm_conf_get_core_gui_screen_verticaldpi ();
501 double
502 gnm_app_dpi_to_pixels (void)
504 return MIN (gnm_app_display_dpi_get (TRUE),
505 gnm_app_display_dpi_get (FALSE)) / 72.;
508 /* GtkFileFilter */
510 * gnm_app_create_opener_filter:
511 * @openers: (element-type GOFileOpener): a list of file openers.
513 * Creates a #GtkFileFilter from the list of file types supported by the
514 * openers in the list.
515 * Returns: (transfer full): the newly allocated #GtkFileFilter.
517 void *
518 gnm_app_create_opener_filter (GList *openers)
520 /* See below. */
521 static const char *const bad_suffixes[] = {
522 "txt",
523 "html", "htm",
524 "xml",
525 NULL
528 GtkFileFilter *filter = gtk_file_filter_new ();
529 gboolean for_history = (openers == NULL);
531 if (openers == NULL)
532 openers = go_get_file_openers ();
534 for (; openers; openers = openers->next) {
535 GOFileOpener *opener = openers->data;
536 if (opener != NULL) {
537 const GSList *mimes = go_file_opener_get_mimes (opener);
538 const GSList *suffixes = go_file_opener_get_suffixes (opener);
540 if (!for_history)
541 while (mimes) {
542 const char *mime = mimes->data;
544 * See 438918. Too many things
545 * like *.xml and *.txt get added
546 * to be useful for the file history
548 gtk_file_filter_add_mime_type (filter, mime);
549 if (0)
550 g_print ("%s: Adding mime %s\n", go_file_opener_get_description (opener), mime);
551 mimes = mimes->next;
554 while (suffixes) {
555 const char *suffix = suffixes->data;
556 GString *pattern;
557 int i;
559 if (for_history)
560 for (i = 0; bad_suffixes[i]; i++)
561 if (strcmp (suffix, bad_suffixes[i]) == 0)
562 goto bad_suffix;
564 /* Create "*.[xX][lL][sS]" */
565 pattern = g_string_new ("*.");
566 while (*suffix) {
567 gunichar uc = g_utf8_get_char (suffix);
568 suffix = g_utf8_next_char (suffix);
569 if (g_unichar_islower (uc)) {
570 g_string_append_c (pattern, '[');
571 g_string_append_unichar (pattern, uc);
572 uc = g_unichar_toupper (uc);
573 g_string_append_unichar (pattern, uc);
574 g_string_append_c (pattern, ']');
575 } else
576 g_string_append_unichar (pattern, uc);
579 gtk_file_filter_add_pattern (filter, pattern->str);
580 if (0)
581 g_print ("%s: Adding %s\n", go_file_opener_get_description (opener), pattern->str);
582 g_string_free (pattern, TRUE);
584 bad_suffix:
585 suffixes = suffixes->next;
589 return filter;
592 static gint
593 compare_mru (GtkRecentInfo *a, GtkRecentInfo *b)
595 time_t ta = MAX (gtk_recent_info_get_visited (a), gtk_recent_info_get_modified (a));
596 time_t tb = MAX (gtk_recent_info_get_visited (b), gtk_recent_info_get_modified (b));
598 return ta < tb;
602 * gnm_app_history_get_list:
604 * creating it if necessary.
606 * Return value: (element-type utf8) (transfer full): the list, which must be
607 * freed along with the strings in it.
609 GSList *
610 gnm_app_history_get_list (int max_elements)
612 GSList *res = NULL;
613 GList *items, *l;
614 GtkFileFilter *filter;
615 int n_elements = 0;
617 if (app->recent == NULL)
618 return NULL;
620 items = gtk_recent_manager_get_items (app->recent);
621 items = g_list_sort (items, (GCompareFunc)compare_mru);
623 filter = gnm_app_create_opener_filter (NULL);
625 for (l = items; l && n_elements < max_elements; l = l->next) {
626 GtkRecentInfo *ri = l->data;
627 const char *uri = gtk_recent_info_get_uri (ri);
628 gboolean want_it;
630 if (gtk_recent_info_has_application (ri, g_get_application_name ())) {
631 want_it = TRUE;
632 } else {
633 GtkFileFilterInfo fi;
634 char *display_name = g_filename_display_basename (uri);
636 memset (&fi, 0, sizeof (fi));
637 fi.contains = (GTK_FILE_FILTER_MIME_TYPE |
638 GTK_FILE_FILTER_URI |
639 GTK_FILE_FILTER_DISPLAY_NAME);
640 fi.uri = uri;
641 fi.mime_type = gtk_recent_info_get_mime_type (ri);
642 fi.display_name = display_name;
643 want_it = gtk_file_filter_filter (filter, &fi);
644 g_free (display_name);
647 if (want_it) {
648 char *filename = go_filename_from_uri (uri);
649 if (filename && !g_file_test (filename, G_FILE_TEST_EXISTS))
650 want_it = FALSE;
651 g_free (filename);
654 if (want_it) {
655 res = g_slist_prepend (res, g_strdup (uri));
656 n_elements++;
660 g_list_free_full (items, (GDestroyNotify)gtk_recent_info_unref);
661 g_object_ref_sink (filter);
662 g_object_unref (filter);
664 return g_slist_reverse (res);
668 * application_history_update_list:
669 * @uri:
671 * Adds @uri to the application's history of files.
673 void
674 gnm_app_history_add (char const *uri, const char *mimetype)
676 GtkRecentData rd;
678 if (app->recent == NULL)
679 return;
681 memset (&rd, 0, sizeof (rd));
683 #if 0
684 g_print ("uri: %s\nmime: %s\n\n", uri, mimetype ? mimetype : "-");
685 #endif
687 rd.mime_type =
688 g_strdup (mimetype ? mimetype : "application/octet-stream");
690 rd.app_name = g_strdup (g_get_application_name ());
691 rd.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL);
692 rd.groups = NULL;
693 rd.is_private = FALSE;
695 if (!gtk_recent_manager_add_full (app->recent, uri, &rd)) {
696 /* Now what? */
697 g_printerr ("Warning: failed to update recent document.\n");
700 g_free (rd.mime_type);
701 g_free (rd.app_name);
702 g_free (rd.app_exec);
704 g_object_notify (G_OBJECT (app), "file-history-list");
707 static void
708 cb_recent_changed (G_GNUC_UNUSED GtkRecentManager *recent, GnmApp *app)
710 g_object_notify (G_OBJECT (app), "file-history-list");
713 static void
714 gnumeric_application_finalize (GObject *obj)
716 GnmApp *application = GNM_APP (obj);
718 g_free (application->clipboard_cut_range);
719 application->clipboard_cut_range = NULL;
721 application->recent = NULL;
723 if (app == application)
724 app = NULL;
726 G_OBJECT_CLASS (parent_klass)->finalize (obj);
729 static void
730 gnumeric_application_get_property (GObject *obj, guint param_id,
731 GValue *value, GParamSpec *pspec)
733 #if 0
734 GnmApp *application = GNM_APP (obj);
735 #endif
736 switch (param_id) {
737 case APPLICATION_PROP_FILE_HISTORY_LIST:
738 g_value_set_pointer (value, gnm_app_history_get_list (G_MAXINT));
739 break;
740 default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
741 break;
745 static void
746 gnm_app_class_init (GObjectClass *gobject_klass)
748 parent_klass = g_type_class_peek_parent (gobject_klass);
750 /* Object class method overrides */
751 gobject_klass->finalize = gnumeric_application_finalize;
752 gobject_klass->get_property = gnumeric_application_get_property;
753 g_object_class_install_property (gobject_klass, APPLICATION_PROP_FILE_HISTORY_LIST,
754 g_param_spec_pointer ("file-history-list",
755 P_("File History List"),
756 P_("A list of filenames that have been read recently"),
757 GSF_PARAM_STATIC | G_PARAM_READABLE));
759 signals[WORKBOOK_ADDED] = g_signal_new ("workbook_added",
760 GNM_APP_TYPE,
761 G_SIGNAL_RUN_LAST,
762 G_STRUCT_OFFSET (GnmAppClass, workbook_added),
763 NULL, NULL,
764 g_cclosure_marshal_VOID__OBJECT,
765 G_TYPE_NONE, 1, GNM_WORKBOOK_TYPE);
766 signals[WORKBOOK_REMOVED] = g_signal_new ("workbook_removed",
767 GNM_APP_TYPE,
768 G_SIGNAL_RUN_LAST,
769 G_STRUCT_OFFSET (GnmAppClass, workbook_removed),
770 NULL, NULL,
771 g_cclosure_marshal_VOID__POINTER,
772 G_TYPE_NONE, 1, G_TYPE_POINTER);
773 signals[WINDOW_LIST_CHANGED] = g_signal_new ("window-list-changed",
774 GNM_APP_TYPE,
775 G_SIGNAL_RUN_LAST,
776 G_STRUCT_OFFSET (GnmAppClass, window_list_changed),
777 NULL, NULL,
778 g_cclosure_marshal_VOID__VOID,
779 G_TYPE_NONE, 0);
780 signals[CUSTOM_UI_ADDED] = g_signal_new ("custom-ui-added",
781 GNM_APP_TYPE,
782 G_SIGNAL_RUN_LAST,
783 G_STRUCT_OFFSET (GnmAppClass, custom_ui_added),
784 NULL, NULL,
785 g_cclosure_marshal_VOID__POINTER,
786 G_TYPE_NONE, 1, G_TYPE_POINTER);
787 signals[CUSTOM_UI_REMOVED] = g_signal_new ("custom-ui-removed",
788 GNM_APP_TYPE,
789 G_SIGNAL_RUN_LAST,
790 G_STRUCT_OFFSET (GnmAppClass, custom_ui_removed),
791 NULL, NULL,
792 g_cclosure_marshal_VOID__POINTER,
793 G_TYPE_NONE, 1, G_TYPE_POINTER);
794 signals[CLIPBOARD_MODIFIED] = g_signal_new ("clipboard_modified",
795 GNM_APP_TYPE,
796 G_SIGNAL_RUN_LAST,
797 G_STRUCT_OFFSET (GnmAppClass, clipboard_modified),
798 NULL, NULL,
799 g_cclosure_marshal_VOID__VOID,
800 G_TYPE_NONE, 0);
801 signals[RECALC_FINISHED] = g_signal_new ("recalc_finished",
802 GNM_APP_TYPE,
803 G_SIGNAL_RUN_LAST,
804 G_STRUCT_OFFSET (GnmAppClass, recalc_finished),
805 NULL, NULL,
806 g_cclosure_marshal_VOID__VOID,
807 G_TYPE_NONE, 0);
808 signals[RECALC_FINISHED] = g_signal_new ("recalc_clear_caches",
809 GNM_APP_TYPE,
810 G_SIGNAL_RUN_LAST,
811 G_STRUCT_OFFSET (GnmAppClass, recalc_clear_caches),
812 NULL, NULL,
813 g_cclosure_marshal_VOID__VOID,
814 G_TYPE_NONE, 0);
817 static void
818 gnm_app_init (GObject *obj)
820 GnmApp *gnm_app = GNM_APP (obj);
822 gnm_app->clipboard_copied_contents = NULL;
823 gnm_app->clipboard_sheet_view = NULL;
825 gnm_app->workbook_list = NULL;
827 if (gdk_display_get_default ()) {
829 * Only allocate a GtkRecentManager if we have a gui.
830 * This is, in part, because it currently throws an error.
831 * deep inside gtk+.
833 gnm_app->recent = gtk_recent_manager_get_default ();
834 g_signal_connect_object (G_OBJECT (gnm_app->recent),
835 "changed",
836 G_CALLBACK (cb_recent_changed),
837 gnm_app, 0);
840 app = gnm_app;
843 GSF_CLASS (GnmApp, gnm_app,
844 gnm_app_class_init, gnm_app_init,
845 G_TYPE_OBJECT)
847 /**********************************************************************/
848 static GSList *extra_uis = NULL;
851 * gnm_action_new:
852 * @name: action ID.
853 * @label: label.
854 * @icon: icon name.
855 * @always_available: whether the action should always be available.
856 * @handler: (scope async): the handler.
858 * Returns: (transfer full): the newly allocated #GnmAction.
860 GnmAction *
861 gnm_action_new (char const *id, char const *label,
862 char const *icon_name, gboolean always_available,
863 GnmActionHandler handler)
865 GnmAction *res = g_new0 (GnmAction, 1);
866 res->id = g_strdup (id);
867 res->label = g_strdup (label);
868 res->icon_name = g_strdup (icon_name);
869 res->always_available = always_available;
870 res->handler = handler;
871 return res;
874 void
875 gnm_action_free (GnmAction *action)
877 if (NULL != action) {
878 g_free (action->id);
879 g_free (action->label);
880 g_free (action->icon_name);
881 g_free (action);
885 static GnmAction *
886 gnm_action_copy (GnmAction const *action)
888 return gnm_action_new (action->id, action->label, action->icon_name,
889 action->always_available, action->handler);
892 GType
893 gnm_action_get_type (void)
895 static GType t = 0;
897 if (t == 0) {
898 t = g_boxed_type_register_static ("GnmAction",
899 (GBoxedCopyFunc)gnm_action_copy,
900 (GBoxedFreeFunc)gnm_action_free);
902 return t;
905 /***************
906 * making GnmAppExtraUI a boxed type for introspection. copy and free don't do
907 anything which might be critical, crossing fingers.*/
909 static GnmAppExtraUI *
910 gnm_app_extra_ui_ref (GnmAppExtraUI *ui)
912 // Nothing
913 return ui;
916 static GnmAppExtraUI *
917 gnm_app_extra_ui_unref (GnmAppExtraUI *ui)
919 // Nothing
920 return ui;
923 GType
924 gnm_app_extra_ui_get_type (void)
926 static GType t = 0;
928 if (t == 0) {
929 t = g_boxed_type_register_static ("GnmAppExtraUI",
930 (GBoxedCopyFunc)gnm_app_extra_ui_ref,
931 (GBoxedFreeFunc)gnm_app_extra_ui_unref);
933 return t;
937 * gnm_app_add_extra_ui:
938 * @group_name: action group name.
939 * @actions: (element-type GnmAction): list of actions.
940 * @layout: the xml string describing the menus and toolbars.
941 * @domain: localization domain.
942 * @user_data: user data
944 * Returns: (transfer full): the newly allocated #GnmAppExtraUI.
946 GnmAppExtraUI *
947 gnm_app_add_extra_ui (char const *group_name,
948 GSList *actions,
949 const char *layout,
950 char const *domain,
951 gpointer user_data)
953 GnmAppExtraUI *extra_ui = g_new0 (GnmAppExtraUI, 1);
954 extra_uis = g_slist_prepend (extra_uis, extra_ui);
955 extra_ui->group_name = g_strdup (group_name);
956 extra_ui->actions = actions;
957 extra_ui->layout = g_strdup (layout);
958 extra_ui->user_data = user_data;
959 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_ADDED], 0, extra_ui);
960 if (gnm_debug_flag ("extra-ui"))
961 g_printerr ("Adding extra ui [%s] %p\n", group_name, extra_ui);
962 return extra_ui;
965 void
966 gnm_app_remove_extra_ui (GnmAppExtraUI *extra_ui)
968 if (gnm_debug_flag ("extra-ui"))
969 g_printerr ("Removing extra ui %p\n", extra_ui);
970 extra_uis = g_slist_remove (extra_uis, extra_ui);
971 g_signal_emit (G_OBJECT (app), signals[CUSTOM_UI_REMOVED], 0, extra_ui);
972 g_free (extra_ui->group_name);
973 g_free (extra_ui->layout);
974 g_free (extra_ui);
978 * gnm_app_foreach_extra_ui:
979 * @func: (scope call): #GFunc
980 * @data: user data.
982 * Applies @func to each #GnmAppExtraUI.
984 void
985 gnm_app_foreach_extra_ui (GFunc func, gpointer data)
987 g_slist_foreach (extra_uis, func, data);
990 /**********************************************************************/
992 static gint windows_update_timer = 0;
993 static gboolean
994 cb_flag_windows_changed (void)
996 if (app)
997 g_signal_emit (G_OBJECT (app), signals[WINDOW_LIST_CHANGED], 0);
998 windows_update_timer = 0;
999 return FALSE;
1003 * _gnm_app_flag_windows_changed:
1005 * An internal utility routine to flag a regeneration of the window lists
1007 void
1008 _gnm_app_flag_windows_changed (void)
1010 if (windows_update_timer == 0)
1011 windows_update_timer = g_timeout_add (100,
1012 (GSourceFunc)cb_flag_windows_changed, NULL);
1015 /**********************************************************************/
1018 * gnm_app_recalc:
1020 * Recalculate everything dirty in all workbooks that have automatic
1021 * recalc turned on.
1023 void
1024 gnm_app_recalc (void)
1026 GList *l;
1028 g_return_if_fail (app != NULL);
1030 gnm_app_recalc_start ();
1032 for (l = app->workbook_list; l; l = l->next) {
1033 Workbook *wb = l->data;
1035 if (workbook_get_recalcmode (wb))
1036 workbook_recalc (wb);
1039 gnm_app_recalc_finish ();
1042 void
1043 gnm_app_recalc_start (void)
1045 g_return_if_fail (app->recalc_count >= 0);
1046 app->recalc_count++;
1049 void
1050 gnm_app_recalc_finish (void)
1052 g_return_if_fail (app->recalc_count > 0);
1053 app->recalc_count--;
1054 if (app->recalc_count == 0) {
1055 gnm_app_recalc_clear_caches ();
1056 g_signal_emit_by_name (gnm_app_get_app (), "recalc-finished");
1060 void
1061 gnm_app_recalc_clear_caches (void)
1063 g_signal_emit_by_name (gnm_app_get_app (), "recalc-clear-caches");