GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / wbc-gtk.c
blob24a9ad23fd551cc5cc79ef176b186502648a0c16
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * wbc-gtk.c: A gtk based WorkbookControl
6 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
7 * Copyright (C) 2006-2012 Morten Welinder (terra@gnome.org)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) version 3.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
22 * USA
24 #include <gnumeric-config.h>
25 #include "gnumeric.h"
26 #include "wbc-gtk-impl.h"
27 #include "workbook-view.h"
28 #include "workbook-priv.h"
29 #include "gui-util.h"
30 #include "gutils.h"
31 #include "gui-file.h"
32 #include "sheet-control-gui-priv.h"
33 #include "sheet.h"
34 #include "sheet-private.h"
35 #include "sheet-view.h"
36 #include "sheet-style.h"
37 #include "sheet-filter.h"
38 #include "commands.h"
39 #include "dependent.h"
40 #include "application.h"
41 #include "history.h"
42 #include "func.h"
43 #include "value.h"
44 #include "style-font.h"
45 #include "gnm-format.h"
46 #include "expr.h"
47 #include "style-color.h"
48 #include "style-border.h"
49 #include "gnumeric-conf.h"
50 #include "dialogs/dialogs.h"
51 #include "gui-clipboard.h"
52 #include "libgnumeric.h"
53 #include "gnm-pane-impl.h"
54 #include "graph.h"
55 #include "selection.h"
56 #include "file-autoft.h"
57 #include "ranges.h"
58 #include "tools/analysis-auto-expression.h"
59 #include "sheet-object-cell-comment.h"
60 #include "print-info.h"
61 #include "expr-name.h"
63 #include <goffice/goffice.h>
64 #include <gsf/gsf-impl-utils.h>
65 #include <gsf/gsf-doc-meta-data.h>
66 #include <gtk/gtk.h>
67 #include "gdk/gdkkeysyms-compat.h"
68 #include "gnm-i18n.h"
69 #include <string.h>
71 #define GET_GUI_ITEM(i_) (gpointer)(gtk_builder_get_object(wbcg->gui, (i_)))
73 #define SHEET_CONTROL_KEY "SheetControl"
75 #define AUTO_EXPR_SAMPLE "Sumerage = -012345678901234"
78 enum {
79 WBG_GTK_PROP_0,
80 WBG_GTK_PROP_AUTOSAVE_PROMPT,
81 WBG_GTK_PROP_AUTOSAVE_TIME
84 enum {
85 WBC_GTK_MARKUP_CHANGED,
86 WBC_GTK_LAST_SIGNAL
89 enum {
90 TARGET_URI_LIST,
91 TARGET_SHEET
95 static gboolean debug_tab_order;
96 static char const *uifilename = NULL;
97 static GtkActionEntry const *extra_actions = NULL;
98 static int extra_actions_nb;
99 static guint wbc_gtk_signals[WBC_GTK_LAST_SIGNAL];
100 static GObjectClass *parent_class = NULL;
102 static gboolean
103 wbcg_ui_update_begin (WBCGtk *wbcg)
105 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
106 g_return_val_if_fail (!wbcg->updating_ui, FALSE);
108 return (wbcg->updating_ui = TRUE);
111 static void
112 wbcg_ui_update_end (WBCGtk *wbcg)
114 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
115 g_return_if_fail (wbcg->updating_ui);
117 wbcg->updating_ui = FALSE;
120 /****************************************************************************/
122 G_MODULE_EXPORT void
123 set_uifilename (char const *name, GtkActionEntry const *actions, int nb)
125 uifilename = name;
126 extra_actions = actions;
127 extra_actions_nb = nb;
131 * wbcg_find_action:
132 * @wbcg: the workbook control gui
133 * @name: name of action
135 * Returns: (transfer none): The action with the given name
137 GtkAction *
138 wbcg_find_action (WBCGtk *wbcg, const char *name)
140 GtkAction *a;
142 a = gtk_action_group_get_action (wbcg->actions, name);
143 if (a == NULL)
144 a = gtk_action_group_get_action (wbcg->permanent_actions, name);
145 if (a == NULL)
146 a = gtk_action_group_get_action (wbcg->semi_permanent_actions, name);
147 if (a == NULL)
148 a = gtk_action_group_get_action (wbcg->data_only_actions, name);
149 if (a == NULL)
150 a = gtk_action_group_get_action (wbcg->font_actions, name);
151 if (a == NULL)
152 a = gtk_action_group_get_action (wbcg->toolbar.actions, name);
154 return a;
157 static void
158 wbc_gtk_set_action_sensitivity (WBCGtk *wbcg,
159 char const *action, gboolean sensitive)
161 GtkAction *a = wbcg_find_action (wbcg, action);
162 g_object_set (G_OBJECT (a), "sensitive", sensitive, NULL);
165 /* NOTE : The semantics of prefix and suffix seem contrived. Why are we
166 * handling it at this end ? That stuff should be done in the undo/redo code
168 static void
169 wbc_gtk_set_action_label (WBCGtk *wbcg,
170 char const *action,
171 char const *prefix,
172 char const *suffix,
173 char const *new_tip)
175 GtkAction *a = wbcg_find_action (wbcg, action);
177 if (prefix != NULL) {
178 char *text;
179 gboolean is_suffix = (suffix != NULL);
181 text = is_suffix ? g_strdup_printf ("%s: %s", prefix, suffix) : (char *) prefix;
182 g_object_set (G_OBJECT (a),
183 "label", text,
184 "sensitive", is_suffix,
185 NULL);
186 if (is_suffix)
187 g_free (text);
188 } else
189 g_object_set (G_OBJECT (a), "label", suffix, NULL);
191 if (new_tip != NULL)
192 g_object_set (G_OBJECT (a), "tooltip", new_tip, NULL);
195 static void
196 wbc_gtk_set_toggle_action_state (WBCGtk *wbcg,
197 char const *action, gboolean state)
199 GtkAction *a = wbcg_find_action (wbcg, action);
200 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (a), state);
203 /****************************************************************************/
205 static SheetControlGUI *
206 wbcg_get_scg (WBCGtk *wbcg, Sheet *sheet)
208 SheetControlGUI *scg;
209 int i, npages;
211 if (sheet == NULL || wbcg->snotebook == NULL)
212 return NULL;
214 npages = wbcg_get_n_scg (wbcg);
215 if (npages == 0) {
217 * This can happen during construction when the clipboard is
218 * being cleared. Ctrl-C Ctrl-Q.
220 return NULL;
223 g_return_val_if_fail (IS_SHEET (sheet), NULL);
224 g_return_val_if_fail (sheet->index_in_wb >= 0, NULL);
226 scg = wbcg_get_nth_scg (wbcg, sheet->index_in_wb);
227 if (NULL != scg && scg_sheet (scg) == sheet)
228 return scg;
231 * index_in_wb is probably not accurate because we are in the
232 * middle of removing or adding a sheet.
234 for (i = 0; i < npages; i++) {
235 scg = wbcg_get_nth_scg (wbcg, i);
236 if (NULL != scg && scg_sheet (scg) == sheet)
237 return scg;
240 g_warning ("Failed to find scg for sheet %s", sheet->name_quoted);
241 return NULL;
244 static SheetControlGUI *
245 get_scg (const GtkWidget *w)
247 return g_object_get_data (G_OBJECT (w), SHEET_CONTROL_KEY);
250 static GSList *
251 get_all_scgs (WBCGtk *wbcg)
253 int i, n = gtk_notebook_get_n_pages (wbcg->snotebook);
254 GSList *l = NULL;
256 for (i = 0; i < n; i++) {
257 GtkWidget *w = gtk_notebook_get_nth_page (wbcg->snotebook, i);
258 SheetControlGUI *scg = get_scg (w);
259 l = g_slist_prepend (l, scg);
262 return g_slist_reverse (l);
265 /* Autosave */
267 static gboolean
268 cb_autosave (WBCGtk *wbcg)
270 WorkbookView *wb_view;
272 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
274 wb_view = wb_control_view (GNM_WBC (wbcg));
276 if (wb_view == NULL)
277 return FALSE;
279 if (wbcg->autosave_time > 0 &&
280 go_doc_is_dirty (wb_view_get_doc (wb_view))) {
281 if (wbcg->autosave_prompt && !dialog_autosave_prompt (wbcg))
282 return TRUE;
283 gui_file_save (wbcg, wb_view);
285 return TRUE;
289 * wbcg_rangesel_possible:
290 * @wbcg: the workbook control gui
292 * Returns true if the cursor keys should be used to select
293 * a cell range (if the cursor is in a spot in the expression
294 * where it makes sense to have a cell reference), false if not.
296 gboolean
297 wbcg_rangesel_possible (WBCGtk const *wbcg)
299 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
301 /* Already range selecting */
302 if (wbcg->rangesel != NULL)
303 return TRUE;
305 /* Rangesel requires that we be editing somthing */
306 if (!wbcg_is_editing (wbcg) && !wbcg_entry_has_logical (wbcg))
307 return FALSE;
309 return gnm_expr_entry_can_rangesel (wbcg_get_entry_logical (wbcg));
312 gboolean
313 wbcg_is_editing (WBCGtk const *wbcg)
315 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
316 return wbcg->editing;
319 static void
320 wbcg_autosave_cancel (WBCGtk *wbcg)
322 if (wbcg->autosave_timer != 0) {
323 g_source_remove (wbcg->autosave_timer);
324 wbcg->autosave_timer = 0;
328 static void
329 wbcg_autosave_activate (WBCGtk *wbcg)
331 wbcg_autosave_cancel (wbcg);
333 if (wbcg->autosave_time > 0) {
334 int secs = MIN (wbcg->autosave_time, G_MAXINT / 1000);
335 wbcg->autosave_timer =
336 g_timeout_add (secs * 1000,
337 (GSourceFunc) cb_autosave,
338 wbcg);
342 static void
343 wbcg_set_autosave_time (WBCGtk *wbcg, int secs)
345 if (secs == wbcg->autosave_time)
346 return;
348 wbcg->autosave_time = secs;
349 wbcg_autosave_activate (wbcg);
352 /****************************************************************************/
354 static void
355 wbcg_edit_line_set (WorkbookControl *wbc, char const *text)
357 GtkEntry *entry = wbcg_get_entry ((WBCGtk*)wbc);
358 gtk_entry_set_text (entry, text);
361 static void
362 wbcg_edit_selection_descr_set (WorkbookControl *wbc, char const *text)
364 WBCGtk *wbcg = (WBCGtk *)wbc;
365 gtk_entry_set_text (GTK_ENTRY (wbcg->selection_descriptor), text);
368 static void
369 wbcg_update_action_sensitivity (WorkbookControl *wbc)
371 WBCGtk *wbcg = WBC_GTK (wbc);
372 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
373 gboolean edit_object = scg != NULL &&
374 (scg->selected_objects != NULL || wbcg->new_object != NULL ||
375 scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT);
376 gboolean enable_actions = TRUE;
377 gboolean enable_edit_ok_cancel = FALSE;
379 if (edit_object || wbcg->edit_line.guru != NULL)
380 enable_actions = FALSE;
381 else if (wbcg_is_editing (wbcg)) {
382 enable_actions = FALSE;
383 enable_edit_ok_cancel = TRUE;
386 /* These are only sensitive while editing */
387 gtk_widget_set_sensitive (wbcg->ok_button, enable_edit_ok_cancel);
388 gtk_widget_set_sensitive (wbcg->cancel_button, enable_edit_ok_cancel);
389 gtk_widget_set_sensitive (wbcg->func_button, enable_actions);
391 if (wbcg->snotebook) {
392 gboolean tab_context_menu =
393 enable_actions ||
394 scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT;
395 int i, N = wbcg_get_n_scg (wbcg);
396 for (i = 0; i < N; i++) {
397 GtkWidget *label =
398 gnm_notebook_get_nth_label (wbcg->bnotebook, i);
399 g_object_set_data (G_OBJECT (label), "editable",
400 GINT_TO_POINTER (tab_context_menu));
404 g_object_set (G_OBJECT (wbcg->actions),
405 "sensitive", enable_actions,
406 NULL);
407 g_object_set (G_OBJECT (wbcg->font_actions),
408 "sensitive", enable_actions || enable_edit_ok_cancel,
409 NULL);
411 if (scg && scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT) {
412 g_object_set (G_OBJECT (wbcg->data_only_actions),
413 "sensitive", FALSE,
414 NULL);
415 g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
416 "sensitive",TRUE,
417 NULL);
418 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), FALSE);
419 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), FALSE);
420 } else {
421 g_object_set (G_OBJECT (wbcg->data_only_actions),
422 "sensitive", TRUE,
423 NULL);
424 g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
425 "sensitive", enable_actions,
426 NULL);
427 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), TRUE);
428 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), TRUE);
432 void
433 wbcg_insert_sheet (GtkWidget *unused, WBCGtk *wbcg)
435 WorkbookControl *wbc = GNM_WBC (wbcg);
436 Sheet *sheet = wb_control_cur_sheet (wbc);
437 Workbook *wb = sheet->workbook;
438 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
439 /* Use same size as current sheet. */
440 workbook_sheet_add (wb, sheet->index_in_wb,
441 gnm_sheet_get_max_cols (sheet),
442 gnm_sheet_get_max_rows (sheet));
443 cmd_reorganize_sheets (wbc, old_state, sheet);
446 void
447 wbcg_append_sheet (GtkWidget *unused, WBCGtk *wbcg)
449 WorkbookControl *wbc = GNM_WBC (wbcg);
450 Sheet *sheet = wb_control_cur_sheet (wbc);
451 Workbook *wb = sheet->workbook;
452 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
453 /* Use same size as current sheet. */
454 workbook_sheet_add (wb, -1,
455 gnm_sheet_get_max_cols (sheet),
456 gnm_sheet_get_max_rows (sheet));
457 cmd_reorganize_sheets (wbc, old_state, sheet);
460 void
461 wbcg_clone_sheet (GtkWidget *unused, WBCGtk *wbcg)
463 WorkbookControl *wbc = GNM_WBC (wbcg);
464 Sheet *sheet = wb_control_cur_sheet (wbc);
465 Workbook *wb = sheet->workbook;
466 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
467 Sheet *new_sheet = sheet_dup (sheet);
468 workbook_sheet_attach_at_pos (wb, new_sheet, sheet->index_in_wb + 1);
469 /* See workbook_sheet_add: */
470 g_signal_emit_by_name (G_OBJECT (wb), "sheet_added", 0);
471 cmd_reorganize_sheets (wbc, old_state, sheet);
472 g_object_unref (new_sheet);
475 static void
476 cb_show_sheet (SheetControlGUI *scg)
478 WBCGtk *wbcg = scg->wbcg;
479 int page_number = gtk_notebook_page_num (wbcg->snotebook,
480 GTK_WIDGET (scg->grid));
481 gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
486 static void cb_sheets_manage (SheetControlGUI *scg) { dialog_sheet_order (scg->wbcg); }
487 static void cb_sheets_insert (SheetControlGUI *scg) { wbcg_insert_sheet (NULL, scg->wbcg); }
488 static void cb_sheets_add (SheetControlGUI *scg) { wbcg_append_sheet (NULL, scg->wbcg); }
489 static void cb_sheets_clone (SheetControlGUI *scg) { wbcg_clone_sheet (NULL, scg->wbcg); }
490 static void cb_sheets_rename (SheetControlGUI *scg) { dialog_sheet_rename (scg->wbcg, scg_sheet (scg)); }
491 static void cb_sheets_resize (SheetControlGUI *scg) { dialog_sheet_resize (scg->wbcg); }
494 static gint
495 cb_by_scg_sheet_name (gconstpointer a_, gconstpointer b_)
497 const SheetControlGUI *a = a_;
498 const SheetControlGUI *b = b_;
499 Sheet *sa = scg_sheet (a);
500 Sheet *sb = scg_sheet (b);
502 return g_utf8_collate (sa->name_unquoted, sb->name_unquoted);
506 static void
507 sheet_menu_label_run (SheetControlGUI *scg, GdkEvent *event)
509 enum { CM_MULTIPLE = 1, CM_DATA_SHEET = 2 };
510 struct SheetTabMenu {
511 char const *text;
512 void (*function) (SheetControlGUI *scg);
513 int flags;
514 int submenu;
515 } const sheet_label_context_actions [] = {
516 { N_("Manage Sheets..."), &cb_sheets_manage, 0, 0},
517 { NULL, NULL, 0, 0 },
518 { N_("Insert"), &cb_sheets_insert, 0, 0 },
519 { N_("Append"), &cb_sheets_add, 0, 0 },
520 { N_("Duplicate"), &cb_sheets_clone, 0, 0 },
521 { N_("Remove"), &scg_delete_sheet_if_possible, CM_MULTIPLE, 0 },
522 { N_("Rename"), &cb_sheets_rename, 0, 0 },
523 { N_("Resize..."), &cb_sheets_resize, CM_DATA_SHEET, 0 },
524 { N_("Select"), NULL, 0, 1 },
525 { N_("Select (sorted)"), NULL, 0, 2 }
528 unsigned int ui;
529 GtkWidget *item, *menu = gtk_menu_new ();
530 GtkWidget *guru = wbc_gtk_get_guru (scg_wbcg (scg));
531 unsigned int N_visible, pass;
532 GtkWidget *submenus[2 + 1];
533 GSList *scgs = get_all_scgs (scg->wbcg);
535 for (pass = 1; pass <= 2; pass++) {
536 GSList *l;
538 submenus[pass] = gtk_menu_new ();
539 N_visible = 0;
540 for (l = scgs; l; l = l->next) {
541 SheetControlGUI *scg1 = l->data;
542 Sheet *sheet = scg_sheet (scg1);
543 if (!sheet_is_visible (sheet))
544 continue;
546 N_visible++;
548 item = gtk_menu_item_new_with_label (sheet->name_unquoted);
549 g_signal_connect_swapped (G_OBJECT (item), "activate",
550 G_CALLBACK (cb_show_sheet), scg1);
551 gtk_menu_shell_append (GTK_MENU_SHELL (submenus[pass]), item);
552 gtk_widget_show (item);
555 scgs = g_slist_sort (scgs, cb_by_scg_sheet_name);
557 g_slist_free (scgs);
559 for (ui = 0; ui < G_N_ELEMENTS (sheet_label_context_actions); ui++) {
560 const struct SheetTabMenu *it =
561 sheet_label_context_actions + ui;
562 gboolean inactive =
563 ((it->flags & CM_MULTIPLE) && N_visible <= 1) ||
564 ((it->flags & CM_DATA_SHEET) && scg_sheet (scg)->sheet_type != GNM_SHEET_DATA) ||
565 (!it->submenu && guru != NULL);
567 item = it->text
568 ? gtk_menu_item_new_with_label (_(it->text))
569 : gtk_separator_menu_item_new ();
570 if (it->function)
571 g_signal_connect_swapped (G_OBJECT (item), "activate",
572 G_CALLBACK (it->function), scg);
573 if (it->submenu)
574 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
575 submenus[it->submenu]);
577 gtk_widget_set_sensitive (item, !inactive);
578 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
579 gtk_widget_show (item);
582 gnumeric_popup_menu (GTK_MENU (menu), event);
586 * cb_sheet_label_button_press:
588 * Invoked when the user has clicked on the sheet name widget.
589 * This takes care of switching to the notebook that contains the label
591 static gboolean
592 cb_sheet_label_button_press (GtkWidget *widget, GdkEvent *event,
593 SheetControlGUI *scg)
595 WBCGtk *wbcg = scg->wbcg;
596 gint page_number;
598 if (event->type != GDK_BUTTON_PRESS)
599 return FALSE;
601 page_number = gtk_notebook_page_num (wbcg->snotebook,
602 GTK_WIDGET (scg->grid));
603 gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
605 if (event->button.button == 1 || NULL != wbcg->rangesel)
606 return FALSE;
608 if (event->button.button == 3) {
609 if ((scg_wbcg (scg))->edit_line.guru == NULL)
610 scg_object_unselect (scg, NULL);
611 if (g_object_get_data (G_OBJECT (widget), "editable")) {
612 sheet_menu_label_run (scg, event);
613 scg_take_focus (scg);
614 return TRUE;
618 return FALSE;
621 static void
622 cb_sheet_label_drag_data_get (GtkWidget *widget, GdkDragContext *context,
623 GtkSelectionData *selection_data,
624 guint info, guint time)
626 SheetControlGUI *scg = get_scg (widget);
627 g_return_if_fail (GNM_IS_SCG (scg));
629 scg_drag_data_get (scg, selection_data);
632 static void
633 cb_sheet_label_drag_data_received (GtkWidget *widget, GdkDragContext *context,
634 gint x, gint y, GtkSelectionData *data, guint info, guint time,
635 WBCGtk *wbcg)
637 GtkWidget *w_source;
638 SheetControlGUI *scg_src, *scg_dst;
639 Sheet *s_src, *s_dst;
641 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
642 g_return_if_fail (GTK_IS_WIDGET (widget));
644 w_source = gtk_drag_get_source_widget (context);
645 if (!w_source) {
646 g_warning ("Not yet implemented!"); /* Different process */
647 return;
650 scg_src = get_scg (w_source);
651 g_return_if_fail (scg_src != NULL);
652 s_src = scg_sheet (scg_src);
654 scg_dst = get_scg (widget);
655 g_return_if_fail (scg_dst != NULL);
656 s_dst = scg_sheet (scg_dst);
658 if (s_src == s_dst) {
659 /* Nothing */
660 } else if (s_src->workbook == s_dst->workbook) {
661 /* Move within workbook */
662 Workbook *wb = s_src->workbook;
663 int p_src = s_src->index_in_wb;
664 int p_dst = s_dst->index_in_wb;
665 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
666 workbook_sheet_move (s_src, p_dst - p_src);
667 cmd_reorganize_sheets (GNM_WBC (wbcg),
668 old_state,
669 s_src);
670 } else {
671 g_return_if_fail (GNM_IS_SCG (gtk_selection_data_get_data (data)));
673 /* Different workbook, same process */
674 g_warning ("Not yet implemented!");
679 * Not currently reachable, I believe. We use the notebook's dragging.
681 static void
682 cb_sheet_label_drag_begin (GtkWidget *widget, GdkDragContext *context,
683 WBCGtk *wbcg)
685 GtkWidget *arrow, *image;
687 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
689 /* Create the arrow. */
690 arrow = gtk_window_new (GTK_WINDOW_POPUP);
691 gtk_window_set_screen (GTK_WINDOW (arrow),
692 gtk_widget_get_screen (widget));
693 gtk_widget_realize (arrow);
694 image = gtk_image_new_from_resource ("/org/gnumeric/gnumeric/images/sheet_move_marker.png");
695 gtk_widget_show (image);
696 gtk_container_add (GTK_CONTAINER (arrow), image);
697 g_object_ref_sink (arrow);
698 g_object_set_data (G_OBJECT (widget), "arrow", arrow);
701 static void
702 cb_sheet_label_drag_end (GtkWidget *widget, GdkDragContext *context,
703 WBCGtk *wbcg)
705 GtkWidget *arrow;
707 g_return_if_fail (GNM_IS_WBC (wbcg));
709 /* Destroy the arrow. */
710 arrow = g_object_get_data (G_OBJECT (widget), "arrow");
711 gtk_widget_destroy (arrow);
712 g_object_unref (arrow);
713 g_object_set_data (G_OBJECT (widget), "arrow", NULL);
716 static void
717 cb_sheet_label_drag_leave (GtkWidget *widget, GdkDragContext *context,
718 guint time, WBCGtk *wbcg)
720 GtkWidget *w_source, *arrow;
722 /* Hide the arrow. */
723 w_source = gtk_drag_get_source_widget (context);
724 if (w_source) {
725 arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
726 gtk_widget_hide (arrow);
730 static gboolean
731 cb_sheet_label_drag_motion (GtkWidget *widget, GdkDragContext *context,
732 gint x, gint y, guint time, WBCGtk *wbcg)
734 SheetControlGUI *scg_src, *scg_dst;
735 GtkWidget *w_source, *arrow, *window;
736 gint root_x, root_y, pos_x, pos_y;
737 GtkAllocation wa, wsa;
739 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
740 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
742 /* Make sure we are really hovering over another label. */
743 w_source = gtk_drag_get_source_widget (context);
744 if (!w_source)
745 return FALSE;
747 arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
749 scg_src = get_scg (w_source);
750 scg_dst = get_scg (widget);
752 if (scg_src == scg_dst) {
753 gtk_widget_hide (arrow);
754 return (FALSE);
757 /* Move the arrow to the correct position and show it. */
758 window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
759 gtk_window_get_position (GTK_WINDOW (window), &root_x, &root_y);
760 gtk_widget_get_allocation (widget ,&wa);
761 pos_x = root_x + wa.x;
762 pos_y = root_y + wa.y;
763 gtk_widget_get_allocation (w_source ,&wsa);
764 if (wsa.x < wa.x)
765 pos_x += wa.width;
766 gtk_window_move (GTK_WINDOW (arrow), pos_x, pos_y);
767 gtk_widget_show (arrow);
769 return (TRUE);
772 static void
773 set_dir (GtkWidget *w, GtkTextDirection *dir)
775 gtk_widget_set_direction (w, *dir);
776 if (GTK_IS_CONTAINER (w))
777 gtk_container_foreach (GTK_CONTAINER (w),
778 (GtkCallback)&set_dir,
779 dir);
782 static void
783 wbcg_set_direction (SheetControlGUI const *scg)
785 GtkWidget *w = (GtkWidget *)scg->wbcg->snotebook;
786 gboolean text_is_rtl = scg_sheet (scg)->text_is_rtl;
787 GtkTextDirection dir = text_is_rtl
788 ? GTK_TEXT_DIR_RTL
789 : GTK_TEXT_DIR_LTR;
791 if (dir != gtk_widget_get_direction (w))
792 set_dir (w, &dir);
793 if (scg->hs)
794 g_object_set (scg->hs, "inverted", text_is_rtl, NULL);
797 static void
798 cb_direction_change (G_GNUC_UNUSED Sheet *null_sheet,
799 G_GNUC_UNUSED GParamSpec *null_pspec,
800 SheetControlGUI const *scg)
802 if (scg && scg == wbcg_cur_scg (scg->wbcg))
803 wbcg_set_direction (scg);
806 static void
807 wbcg_update_menu_feedback (WBCGtk *wbcg, Sheet const *sheet)
809 g_return_if_fail (IS_SHEET (sheet));
811 if (!wbcg_ui_update_begin (wbcg))
812 return;
814 wbc_gtk_set_toggle_action_state (wbcg,
815 "SheetDisplayFormulas", sheet->display_formulas);
816 wbc_gtk_set_toggle_action_state (wbcg,
817 "SheetHideZeros", sheet->hide_zero);
818 wbc_gtk_set_toggle_action_state (wbcg,
819 "SheetHideGridlines", sheet->hide_grid);
820 wbc_gtk_set_toggle_action_state (wbcg,
821 "SheetHideColHeader", sheet->hide_col_header);
822 wbc_gtk_set_toggle_action_state (wbcg,
823 "SheetHideRowHeader", sheet->hide_row_header);
824 wbc_gtk_set_toggle_action_state (wbcg,
825 "SheetDisplayOutlines", sheet->display_outlines);
826 wbc_gtk_set_toggle_action_state (wbcg,
827 "SheetOutlineBelow", sheet->outline_symbols_below);
828 wbc_gtk_set_toggle_action_state (wbcg,
829 "SheetOutlineRight", sheet->outline_symbols_right);
830 wbc_gtk_set_toggle_action_state (wbcg,
831 "SheetUseR1C1", sheet->convs->r1c1_addresses);
832 wbcg_ui_update_end (wbcg);
835 static void
836 cb_zoom_change (Sheet *sheet,
837 G_GNUC_UNUSED GParamSpec *null_pspec,
838 WBCGtk *wbcg)
840 if (wbcg_ui_update_begin (wbcg)) {
841 int pct = sheet->last_zoom_factor_used * 100 + .5;
842 char *label = g_strdup_printf ("%d%%", pct);
843 go_action_combo_text_set_entry (wbcg->zoom_haction, label,
844 GO_ACTION_COMBO_SEARCH_CURRENT);
845 g_free (label);
846 wbcg_ui_update_end (wbcg);
850 static void
851 cb_notebook_switch_page (G_GNUC_UNUSED GtkNotebook *notebook_,
852 G_GNUC_UNUSED GtkWidget *page_,
853 guint page_num, WBCGtk *wbcg)
855 Sheet *sheet;
856 SheetControlGUI *new_scg;
858 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
860 /* Ignore events during destruction */
861 if (wbcg->snotebook == NULL)
862 return;
864 if (debug_tab_order)
865 g_printerr ("Notebook page switch\n");
867 /* While initializing adding the sheets will trigger page changes, but
868 * we do not actually want to change the focus sheet for the view
870 if (wbcg->updating_ui)
871 return;
873 /* If we are not at a subexpression boundary then finish editing */
874 if (NULL != wbcg->rangesel)
875 scg_rangesel_stop (wbcg->rangesel, TRUE);
878 * Make snotebook follow bnotebook. This should be the only place
879 * that changes pages for snotebook.
881 gtk_notebook_set_current_page (wbcg->snotebook, page_num);
883 new_scg = wbcg_get_nth_scg (wbcg, page_num);
884 wbcg_set_direction (new_scg);
886 if (wbcg_is_editing (wbcg) && wbcg_rangesel_possible (wbcg)) {
888 * When we are editing, sheet changes are not done fully.
889 * We revert to the original sheet later.
891 * On the other hand, when we are selecting a range for a
892 * dialog, we do change sheet fully.
894 scg_take_focus (new_scg);
895 return;
898 gnm_expr_entry_set_scg (wbcg->edit_line.entry, new_scg);
901 * Make absolutely sure the expression doesn't get 'lost',
902 * if it's invalid then prompt the user and don't switch
903 * the notebook page.
905 if (wbcg_is_editing (wbcg)) {
906 guint prev = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (wbcg->snotebook),
907 "previous_page"));
909 if (prev == page_num)
910 return;
912 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
913 gnm_notebook_set_current_page (wbcg->bnotebook,
914 prev);
915 else
916 /* Looks silly, but is really neccesarry */
917 gnm_notebook_set_current_page (wbcg->bnotebook,
918 page_num);
920 return;
923 g_object_set_data (G_OBJECT (wbcg->snotebook), "previous_page",
924 GINT_TO_POINTER (gtk_notebook_get_current_page (wbcg->snotebook)));
926 /* if we are not selecting a range for an expression update */
927 sheet = wbcg_focus_cur_scg (wbcg);
928 if (sheet != wbcg_cur_sheet (wbcg)) {
929 wbcg_update_menu_feedback (wbcg, sheet);
930 sheet_flag_status_update_range (sheet, NULL);
931 sheet_update (sheet);
932 wb_view_sheet_focus (wb_control_view (GNM_WBC (wbcg)), sheet);
933 cb_zoom_change (sheet, NULL, wbcg);
937 static gboolean
938 cb_bnotebook_button_press (GtkWidget *widget, GdkEventButton *event)
940 if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
942 * Eat the click so cb_paned_button_press doesn't see it.
943 * see bug #607794.
945 return TRUE;
948 return FALSE;
951 static void
952 cb_bnotebook_page_reordered (GtkNotebook *notebook, GtkWidget *child,
953 int page_num, WBCGtk *wbcg)
955 GtkNotebook *snotebook = GTK_NOTEBOOK (wbcg->snotebook);
956 int old = gtk_notebook_get_current_page (snotebook);
958 if (wbcg->updating_ui)
959 return;
961 if (debug_tab_order)
962 g_printerr ("Reordered %d -> %d\n", old, page_num);
964 if (old != page_num) {
965 WorkbookControl * wbc = GNM_WBC (wbcg);
966 Workbook *wb = wb_control_get_workbook (wbc);
967 Sheet *sheet = workbook_sheet_by_index (wb, old);
968 WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
969 workbook_sheet_move (sheet, page_num - old);
970 cmd_reorganize_sheets (wbc, old_state, sheet);
975 static void
976 wbc_gtk_create_notebook_area (WBCGtk *wbcg)
978 GtkWidget *placeholder;
980 wbcg->bnotebook = g_object_new (GNM_NOTEBOOK_TYPE,
981 "can-focus", FALSE,
982 NULL);
983 g_object_ref (wbcg->bnotebook);
985 g_signal_connect_after (G_OBJECT (wbcg->bnotebook),
986 "switch_page",
987 G_CALLBACK (cb_notebook_switch_page), wbcg);
988 g_signal_connect (G_OBJECT (wbcg->bnotebook),
989 "button-press-event",
990 G_CALLBACK (cb_bnotebook_button_press),
991 NULL);
992 g_signal_connect (G_OBJECT (wbcg->bnotebook),
993 "page-reordered",
994 G_CALLBACK (cb_bnotebook_page_reordered),
995 wbcg);
996 placeholder = gtk_paned_get_child1 (wbcg->tabs_paned);
997 if (placeholder)
998 gtk_widget_destroy (placeholder);
999 gtk_paned_pack1 (wbcg->tabs_paned, GTK_WIDGET (wbcg->bnotebook), FALSE, TRUE);
1001 gtk_widget_show_all (GTK_WIDGET (wbcg->tabs_paned));
1005 static void
1006 wbcg_menu_state_sheet_count (WBCGtk *wbcg)
1008 int const sheet_count = gnm_notebook_get_n_visible (wbcg->bnotebook);
1009 /* Should we enable commands requiring multiple sheets */
1010 gboolean const multi_sheet = (sheet_count > 1);
1012 wbc_gtk_set_action_sensitivity (wbcg, "SheetRemove", multi_sheet);
1015 static void
1016 cb_sheet_direction_change (Sheet *sheet,
1017 G_GNUC_UNUSED GParamSpec *pspec,
1018 GtkAction *a)
1020 g_object_set (a,
1021 "icon-name", (sheet->text_is_rtl
1022 ? "format-text-direction-rtl"
1023 : "format-text-direction-ltr"),
1024 NULL);
1027 static void
1028 cb_sheet_tab_change (Sheet *sheet,
1029 G_GNUC_UNUSED GParamSpec *pspec,
1030 GtkWidget *widget)
1032 GdkRGBA cfore, cback;
1033 SheetControlGUI *scg = get_scg (widget);
1035 g_return_if_fail (GNM_IS_SCG (scg));
1037 /* We're lazy and just set all relevant attributes. */
1038 g_object_set (widget,
1039 "label", sheet->name_unquoted,
1040 "background-color",
1041 (sheet->tab_color
1042 ? go_color_to_gdk_rgba (sheet->tab_color->go_color,
1043 &cback)
1044 : NULL),
1045 "text-color",
1046 (sheet->tab_text_color
1047 ? go_color_to_gdk_rgba (sheet->tab_text_color->go_color,
1048 &cfore)
1049 : NULL),
1050 NULL);
1053 static void
1054 cb_toggle_menu_item_changed (Sheet *sheet,
1055 G_GNUC_UNUSED GParamSpec *pspec,
1056 WBCGtk *wbcg)
1058 /* We're lazy and just update all. */
1059 wbcg_update_menu_feedback (wbcg, sheet);
1062 static void
1063 cb_sheet_visibility_change (Sheet *sheet,
1064 G_GNUC_UNUSED GParamSpec *pspec,
1065 SheetControlGUI *scg)
1067 gboolean viz;
1069 g_return_if_fail (GNM_IS_SCG (scg));
1071 viz = sheet_is_visible (sheet);
1072 gtk_widget_set_visible (GTK_WIDGET (scg->grid), viz);
1073 gtk_widget_set_visible (GTK_WIDGET (scg->label), viz);
1075 wbcg_menu_state_sheet_count (scg->wbcg);
1078 static void
1079 disconnect_sheet_focus_signals (WBCGtk *wbcg)
1081 SheetControlGUI *scg = wbcg->active_scg;
1082 Sheet *sheet;
1084 if (!scg)
1085 return;
1087 sheet = scg_sheet (scg);
1089 #if 0
1090 g_printerr ("Disconnecting focus for %s with scg=%p\n", sheet->name_unquoted, scg);
1091 #endif
1093 g_signal_handlers_disconnect_by_func (sheet, cb_toggle_menu_item_changed, wbcg);
1094 g_signal_handlers_disconnect_by_func (sheet, cb_direction_change, scg);
1095 g_signal_handlers_disconnect_by_func (sheet, cb_zoom_change, wbcg);
1097 wbcg->active_scg = NULL;
1100 static void
1101 disconnect_sheet_signals (SheetControlGUI *scg)
1103 WBCGtk *wbcg = scg->wbcg;
1104 Sheet *sheet = scg_sheet (scg);
1106 if (scg == wbcg->active_scg)
1107 disconnect_sheet_focus_signals (wbcg);
1109 #if 0
1110 g_printerr ("Disconnecting all for %s with scg=%p\n", sheet->name_unquoted, scg);
1111 #endif
1113 g_signal_handlers_disconnect_by_func (sheet, cb_sheet_direction_change,
1114 wbcg_find_action (wbcg, "SheetDirection"));
1115 g_signal_handlers_disconnect_by_func (sheet, cb_sheet_tab_change, scg->label);
1116 g_signal_handlers_disconnect_by_func (sheet, cb_sheet_visibility_change, scg);
1119 static void
1120 wbcg_sheet_add (WorkbookControl *wbc, SheetView *sv)
1122 static GtkTargetEntry const drag_types[] = {
1123 { (char *)"GNUMERIC_SHEET", GTK_TARGET_SAME_APP, TARGET_SHEET },
1124 { (char *)"UTF8_STRING", 0, 0 },
1125 { (char *)"image/svg+xml", 0, 0 },
1126 { (char *)"image/x-wmf", 0, 0 },
1127 { (char *)"image/x-emf", 0, 0 },
1128 { (char *)"image/png", 0, 0 },
1129 { (char *)"image/jpeg", 0, 0 },
1130 { (char *)"image/bmp", 0, 0 }
1133 WBCGtk *wbcg = (WBCGtk *)wbc;
1134 SheetControlGUI *scg;
1135 Sheet *sheet = sv_sheet (sv);
1136 gboolean visible = sheet_is_visible (sheet);
1138 g_return_if_fail (wbcg != NULL);
1140 scg = sheet_control_gui_new (sv, wbcg);
1142 g_object_set_data (G_OBJECT (scg->grid), SHEET_CONTROL_KEY, scg);
1144 g_object_set_data (G_OBJECT (scg->label), SHEET_CONTROL_KEY, scg);
1146 /* do not preempt the editable label handler */
1147 g_signal_connect_after (G_OBJECT (scg->label),
1148 "button_press_event",
1149 G_CALLBACK (cb_sheet_label_button_press), scg);
1151 /* Drag & Drop */
1152 gtk_drag_source_set (scg->label, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
1153 drag_types, G_N_ELEMENTS (drag_types),
1154 GDK_ACTION_MOVE);
1155 gtk_drag_dest_set (scg->label, GTK_DEST_DEFAULT_ALL,
1156 drag_types, G_N_ELEMENTS (drag_types),
1157 GDK_ACTION_MOVE);
1158 g_object_connect (G_OBJECT (scg->label),
1159 "signal::drag_begin", G_CALLBACK (cb_sheet_label_drag_begin), wbcg,
1160 "signal::drag_end", G_CALLBACK (cb_sheet_label_drag_end), wbcg,
1161 "signal::drag_leave", G_CALLBACK (cb_sheet_label_drag_leave), wbcg,
1162 "signal::drag_data_get", G_CALLBACK (cb_sheet_label_drag_data_get), NULL,
1163 "signal::drag_data_received", G_CALLBACK (cb_sheet_label_drag_data_received), wbcg,
1164 "signal::drag_motion", G_CALLBACK (cb_sheet_label_drag_motion), wbcg,
1165 NULL);
1167 gtk_widget_show (scg->label);
1168 gtk_widget_show_all (GTK_WIDGET (scg->grid));
1169 if (!visible) {
1170 gtk_widget_hide (GTK_WIDGET (scg->grid));
1171 gtk_widget_hide (GTK_WIDGET (scg->label));
1173 g_object_connect (G_OBJECT (sheet),
1174 "signal::notify::visibility", cb_sheet_visibility_change, scg,
1175 "signal::notify::name", cb_sheet_tab_change, scg->label,
1176 "signal::notify::tab-foreground", cb_sheet_tab_change, scg->label,
1177 "signal::notify::tab-background", cb_sheet_tab_change, scg->label,
1178 "signal::notify::text-is-rtl", cb_sheet_direction_change, wbcg_find_action (wbcg, "SheetDirection"),
1179 NULL);
1181 if (wbcg_ui_update_begin (wbcg)) {
1183 * Just let wbcg_sheet_order_changed deal with where to put
1184 * it.
1186 int pos = -1;
1187 gtk_notebook_insert_page (wbcg->snotebook,
1188 GTK_WIDGET (scg->grid), NULL,
1189 pos);
1190 gnm_notebook_insert_tab (wbcg->bnotebook,
1191 GTK_WIDGET (scg->label),
1192 pos);
1193 wbcg_menu_state_sheet_count (wbcg);
1194 wbcg_ui_update_end (wbcg);
1197 scg_adjust_preferences (scg);
1198 if (sheet == wb_control_cur_sheet (wbc)) {
1199 scg_take_focus (scg);
1200 wbcg_set_direction (scg);
1201 cb_zoom_change (sheet, NULL, wbcg);
1202 cb_toggle_menu_item_changed (sheet, NULL, wbcg);
1206 static void
1207 wbcg_sheet_remove (WorkbookControl *wbc, Sheet *sheet)
1209 WBCGtk *wbcg = (WBCGtk *)wbc;
1210 SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1212 /* During destruction we may have already removed the notebook */
1213 if (scg == NULL)
1214 return;
1216 disconnect_sheet_signals (scg);
1218 gtk_widget_destroy (GTK_WIDGET (scg->label));
1219 gtk_widget_destroy (GTK_WIDGET (scg->grid));
1221 wbcg_menu_state_sheet_count (wbcg);
1224 static void
1225 wbcg_sheet_focus (WorkbookControl *wbc, Sheet *sheet)
1227 WBCGtk *wbcg = (WBCGtk *)wbc;
1228 SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1230 if (scg) {
1231 int n = gtk_notebook_page_num (wbcg->snotebook,
1232 GTK_WIDGET (scg->grid));
1233 gnm_notebook_set_current_page (wbcg->bnotebook, n);
1235 if (wbcg->rangesel == NULL)
1236 gnm_expr_entry_set_scg (wbcg->edit_line.entry, scg);
1239 disconnect_sheet_focus_signals (wbcg);
1241 if (sheet) {
1242 wbcg_update_menu_feedback (wbcg, sheet);
1244 if (scg)
1245 wbcg_set_direction (scg);
1247 #if 0
1248 g_printerr ("Connecting for %s with scg=%p\n", sheet->name_unquoted, scg);
1249 #endif
1251 g_object_connect
1252 (G_OBJECT (sheet),
1253 "signal::notify::display-formulas", cb_toggle_menu_item_changed, wbcg,
1254 "signal::notify::display-zeros", cb_toggle_menu_item_changed, wbcg,
1255 "signal::notify::display-grid", cb_toggle_menu_item_changed, wbcg,
1256 "signal::notify::display-column-header", cb_toggle_menu_item_changed, wbcg,
1257 "signal::notify::display-row-header", cb_toggle_menu_item_changed, wbcg,
1258 "signal::notify::display-outlines", cb_toggle_menu_item_changed, wbcg,
1259 "signal::notify::display-outlines-below", cb_toggle_menu_item_changed, wbcg,
1260 "signal::notify::display-outlines-right", cb_toggle_menu_item_changed, wbcg,
1261 "signal::notify::text-is-rtl", cb_direction_change, scg,
1262 "signal::notify::zoom-factor", cb_zoom_change, wbcg,
1263 NULL);
1265 wbcg->active_scg = scg;
1269 static gint
1270 by_sheet_index (gconstpointer a, gconstpointer b)
1272 SheetControlGUI *scga = (SheetControlGUI *)a;
1273 SheetControlGUI *scgb = (SheetControlGUI *)b;
1274 return scg_sheet (scga)->index_in_wb - scg_sheet (scgb)->index_in_wb;
1277 static void
1278 wbcg_sheet_order_changed (WBCGtk *wbcg)
1280 if (wbcg_ui_update_begin (wbcg)) {
1281 GSList *l, *scgs;
1282 int i;
1284 /* Reorder all tabs so they end up in index_in_wb order. */
1285 scgs = g_slist_sort (get_all_scgs (wbcg), by_sheet_index);
1287 for (i = 0, l = scgs; l; l = l->next, i++) {
1288 SheetControlGUI *scg = l->data;
1289 gtk_notebook_reorder_child (wbcg->snotebook,
1290 GTK_WIDGET (scg->grid),
1292 gnm_notebook_move_tab (wbcg->bnotebook,
1293 GTK_WIDGET (scg->label),
1296 g_slist_free (scgs);
1298 wbcg_ui_update_end (wbcg);
1302 static void
1303 wbcg_update_title (WBCGtk *wbcg)
1305 GODoc *doc = wb_control_get_doc (GNM_WBC (wbcg));
1306 char *basename = doc->uri ? go_basename_from_uri (doc->uri) : NULL;
1307 char *title = g_strconcat
1308 (go_doc_is_dirty (doc) ? "*" : "",
1309 basename ? basename : doc->uri,
1310 _(" - Gnumeric"),
1311 NULL);
1312 gtk_window_set_title (wbcg_toplevel (wbcg), title);
1313 g_free (title);
1314 g_free (basename);
1317 static void
1318 wbcg_sheet_remove_all (WorkbookControl *wbc)
1320 WBCGtk *wbcg = (WBCGtk *)wbc;
1322 if (wbcg->snotebook != NULL) {
1323 GtkNotebook *tmp = wbcg->snotebook;
1324 GSList *l, *all = get_all_scgs (wbcg);
1325 SheetControlGUI *current = wbcg_cur_scg (wbcg);
1327 /* Clear notebook to disable updates as focus changes for pages
1328 * during destruction */
1329 wbcg->snotebook = NULL;
1331 /* Be sure we are no longer editing */
1332 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1334 for (l = all; l; l = l->next) {
1335 SheetControlGUI *scg = l->data;
1336 disconnect_sheet_signals (scg);
1337 if (scg != current) {
1338 gtk_widget_destroy (GTK_WIDGET (scg->label));
1339 gtk_widget_destroy (GTK_WIDGET (scg->grid));
1343 g_slist_free (all);
1345 /* Do current scg last. */
1346 if (current) {
1347 gtk_widget_destroy (GTK_WIDGET (current->label));
1348 gtk_widget_destroy (GTK_WIDGET (current->grid));
1351 wbcg->snotebook = tmp;
1355 static double
1356 color_diff (const GdkRGBA *a, const GdkRGBA *b)
1358 /* Ignoring alpha. */
1359 return ((a->red - b->red) * (a->red - b->red) +
1360 (a->green - b->green) * (a->green - b->green) +
1361 (a->blue - b->blue) * (a->blue - b->blue));
1365 static gboolean
1366 cb_adjust_foreground_attributes (PangoAttribute *attribute,
1367 gpointer data)
1369 const GdkRGBA *back = data;
1371 if (attribute->klass->type == PANGO_ATTR_FOREGROUND) {
1372 PangoColor *pfore = &((PangoAttrColor *)attribute)->color;
1373 GdkRGBA fore;
1374 const double threshold = 0.01;
1376 fore.red = pfore->red / 65535.0;
1377 fore.green = pfore->green / 65535.0;
1378 fore.blue = pfore->blue / 65535.0;
1380 if (color_diff (&fore, back) < threshold) {
1381 static const GdkRGBA black = { 0, 0, 0, 1 };
1382 static const GdkRGBA white = { 1, 1, 1, 1 };
1383 double back_norm = color_diff (back, &black);
1384 double f = 0.2;
1385 const GdkRGBA *ref =
1386 back_norm > 0.75 ? &black : &white;
1388 #define DO_CHANNEL(channel) \
1389 do { \
1390 double val = fore.channel * (1 - f) + ref->channel * f; \
1391 pfore->channel = CLAMP (val, 0, 1) * 65535; \
1392 } while (0)
1393 DO_CHANNEL(red);
1394 DO_CHANNEL(green);
1395 DO_CHANNEL(blue);
1396 #undef DO_CHANNEL
1399 return FALSE;
1402 static void
1403 adjust_foreground_attributes (PangoAttrList *attrs, GtkWidget *w)
1405 GdkRGBA c;
1406 GtkStyleContext *ctxt = gtk_widget_get_style_context (w);
1408 gtk_style_context_get_background_color (ctxt, GTK_STATE_FLAG_NORMAL,
1409 &c);
1411 if (0)
1412 g_printerr ("back=%s\n", gdk_rgba_to_string (&c));
1414 pango_attr_list_unref
1415 (pango_attr_list_filter
1416 (attrs,
1417 cb_adjust_foreground_attributes,
1418 &c));
1422 static void
1423 wbcg_auto_expr_value_changed (WorkbookView *wbv,
1424 G_GNUC_UNUSED GParamSpec *pspec,
1425 WBCGtk *wbcg)
1427 GtkLabel *lbl = GTK_LABEL (wbcg->auto_expr_label);
1428 GnmValue const *v = wbv->auto_expr.value;
1430 if (v) {
1431 GOFormat const *format = VALUE_FMT (v);
1432 GString *str = g_string_new (wbv->auto_expr.descr);
1433 PangoAttrList *attrs = NULL;
1435 g_string_append (str, " = ");
1437 if (format) {
1438 PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (wbcg->toplevel), NULL);
1439 gsize old_len = str->len;
1440 GODateConventions const *date_conv = workbook_date_conv (wb_view_get_workbook (wbv));
1441 int max_width = go_format_is_general (format) && VALUE_IS_NUMBER (v)
1442 ? 14
1443 : strlen (AUTO_EXPR_SAMPLE) - g_utf8_strlen (str->str, -1);
1444 GOFormatNumberError err =
1445 format_value_layout (layout, format, v,
1446 max_width, date_conv);
1447 switch (err) {
1448 case GO_FORMAT_NUMBER_OK:
1449 case GO_FORMAT_NUMBER_DATE_ERROR: {
1450 PangoAttrList *atl;
1452 go_pango_translate_layout (layout); /* translating custom attributes */
1453 g_string_append (str, pango_layout_get_text (layout));
1454 /* We need to shift the attribute list */
1455 atl = pango_attr_list_ref (pango_layout_get_attributes (layout));
1456 if (atl != NULL) {
1457 attrs = pango_attr_list_new ();
1458 pango_attr_list_splice
1459 (attrs, atl, old_len,
1460 str->len - old_len);
1461 pango_attr_list_unref (atl);
1462 /* Adjust colours to make text visible. */
1463 adjust_foreground_attributes
1464 (attrs,
1465 gtk_widget_get_parent (GTK_WIDGET (lbl)));
1467 break;
1469 default:
1470 case GO_FORMAT_NUMBER_INVALID_FORMAT:
1471 g_string_append (str, _("Invalid format"));
1472 break;
1474 g_object_unref (layout);
1475 } else
1476 g_string_append (str, value_peek_string (v));
1478 gtk_label_set_text (lbl, str->str);
1479 gtk_label_set_attributes (lbl, attrs);
1481 pango_attr_list_unref (attrs);
1482 g_string_free (str, TRUE);
1483 } else {
1484 gtk_label_set_text (lbl, "");
1485 gtk_label_set_attributes (lbl, NULL);
1489 static void
1490 wbcg_scrollbar_visibility (WorkbookView *wbv,
1491 G_GNUC_UNUSED GParamSpec *pspec,
1492 WBCGtk *wbcg)
1494 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
1495 scg_adjust_preferences (scg);
1498 static void
1499 wbcg_notebook_tabs_visibility (WorkbookView *wbv,
1500 G_GNUC_UNUSED GParamSpec *pspec,
1501 WBCGtk *wbcg)
1503 gtk_widget_set_visible (GTK_WIDGET (wbcg->bnotebook),
1504 wbv->show_notebook_tabs);
1508 static void
1509 wbcg_menu_state_update (WorkbookControl *wbc, int flags)
1511 WBCGtk *wbcg = (WBCGtk *)wbc;
1512 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
1513 SheetView const *sv = wb_control_cur_sheet_view (wbc);
1514 Sheet const *sheet = wb_control_cur_sheet (wbc);
1515 gboolean const has_guru = wbc_gtk_get_guru (wbcg) != NULL;
1516 gboolean edit_object = scg != NULL &&
1517 (scg->selected_objects != NULL || wbcg->new_object != NULL);
1518 gboolean has_print_area;
1520 if (MS_INSERT_COLS & flags)
1521 wbc_gtk_set_action_sensitivity (wbcg, "InsertColumns",
1522 sv->enable_insert_cols);
1523 if (MS_INSERT_ROWS & flags)
1524 wbc_gtk_set_action_sensitivity (wbcg, "InsertRows",
1525 sv->enable_insert_rows);
1526 if (MS_INSERT_CELLS & flags)
1527 wbc_gtk_set_action_sensitivity (wbcg, "InsertCells",
1528 sv->enable_insert_cells);
1529 if (MS_SHOWHIDE_DETAIL & flags) {
1530 wbc_gtk_set_action_sensitivity (wbcg, "DataOutlineShowDetail",
1531 sheet->priv->enable_showhide_detail);
1532 wbc_gtk_set_action_sensitivity (wbcg, "DataOutlineHideDetail",
1533 sheet->priv->enable_showhide_detail);
1535 if (MS_PASTE_SPECIAL & flags)
1536 wbc_gtk_set_action_sensitivity (wbcg, "EditPasteSpecial",
1537 // Inter-process paste special is now allowed
1538 !gnm_app_clipboard_is_cut () &&
1539 !edit_object);
1540 if (MS_PRINT_SETUP & flags)
1541 wbc_gtk_set_action_sensitivity (wbcg, "FilePageSetup", !has_guru);
1542 if (MS_SEARCH_REPLACE & flags)
1543 wbc_gtk_set_action_sensitivity (wbcg, "EditReplace", !has_guru);
1544 if (MS_DEFINE_NAME & flags) {
1545 wbc_gtk_set_action_sensitivity (wbcg, "EditNames", !has_guru);
1546 wbc_gtk_set_action_sensitivity (wbcg, "InsertNames", !has_guru);
1548 if (MS_CONSOLIDATE & flags)
1549 wbc_gtk_set_action_sensitivity (wbcg, "DataConsolidate", !has_guru);
1550 if (MS_FILTER_STATE_CHANGED & flags)
1551 wbc_gtk_set_action_sensitivity (wbcg, "DataFilterShowAll", sheet->has_filtered_rows);
1552 if (MS_SHOW_PRINTAREA & flags) {
1553 GnmRange *print_area = sheet_get_nominal_printarea (sheet);
1554 has_print_area = (print_area != NULL);
1555 g_free (print_area);
1556 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaClear", has_print_area);
1557 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaShow", has_print_area);
1559 if (MS_PAGE_BREAKS & flags) {
1560 gint col = sv->edit_pos.col;
1561 gint row = sv->edit_pos.row;
1562 GnmPrintInformation *pi = sheet->print_info;
1563 char const* new_label = NULL;
1564 char const *new_tip = NULL;
1566 if (pi->page_breaks.v != NULL &&
1567 gnm_page_breaks_get_break (pi->page_breaks.v, col) == GNM_PAGE_BREAK_MANUAL) {
1568 new_label = _("Remove Column Page Break");
1569 new_tip = _("Remove the page break to the left of the current column");
1570 } else {
1571 new_label = _("Add Column Page Break");
1572 new_tip = _("Add a page break to the left of the current column");
1574 wbc_gtk_set_action_label (wbcg, "FilePrintAreaToggleColPageBreak",
1575 NULL, new_label, new_tip);
1576 if (pi->page_breaks.h != NULL &&
1577 gnm_page_breaks_get_break (pi->page_breaks.h, col) == GNM_PAGE_BREAK_MANUAL) {
1578 new_label = _("Remove Row Page Break");
1579 new_tip = _("Remove the page break above the current row");
1580 } else {
1581 new_label = _("Add Row Page Break");
1582 new_tip = _("Add a page break above current row");
1584 wbc_gtk_set_action_label (wbcg, "FilePrintAreaToggleRowPageBreak",
1585 NULL, new_label, new_tip);
1586 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaToggleRowPageBreak",
1587 row != 0);
1588 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaToggleColPageBreak",
1589 col != 0);
1590 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaClearAllPageBreak",
1591 print_info_has_manual_breaks (sheet->print_info));
1593 if (MS_SELECT_OBJECT & flags) {
1594 wbc_gtk_set_action_sensitivity (wbcg, "EditSelectObject",
1595 sheet->sheet_objects != NULL);
1598 if (MS_FREEZE_VS_THAW & flags) {
1599 /* Cheat and use the same accelerator for both states because
1600 * we don't reset it when the label changes */
1601 char const* label = gnm_sheet_view_is_frozen (sv)
1602 ? _("Un_freeze Panes")
1603 : _("_Freeze Panes");
1604 char const *new_tip = gnm_sheet_view_is_frozen (sv)
1605 ? _("Unfreeze the top left of the sheet")
1606 : _("Freeze the top left of the sheet");
1607 wbc_gtk_set_action_label (wbcg, "ViewFreezeThawPanes", NULL, label, new_tip);
1610 if (MS_ADD_VS_REMOVE_FILTER & flags) {
1611 gboolean const has_filter = (NULL != gnm_sheet_view_editpos_in_filter (sv));
1612 GnmFilter *f = gnm_sheet_view_selection_intersects_filter_rows (sv);
1613 char const* label;
1614 char const *new_tip;
1615 gboolean active = TRUE;
1616 GnmRange *r = NULL;
1618 if ((!has_filter) && (NULL != f)) {
1619 gchar *nlabel = NULL;
1620 if (NULL != (r = gnm_sheet_view_selection_extends_filter (sv, f))) {
1621 active = TRUE;
1622 nlabel = g_strdup_printf
1623 (_("Extend _Auto Filter to %s"),
1624 range_as_string (r));
1625 new_tip = _("Extend the existing filter.");
1626 wbc_gtk_set_action_label
1627 (wbcg, "DataAutoFilter", NULL,
1628 nlabel, new_tip);
1629 g_free (r);
1630 } else {
1631 active = FALSE;
1632 nlabel = g_strdup_printf
1633 (_("Auto Filter blocked by %s"),
1634 range_as_string (&f->r));
1635 new_tip = _("The selection intersects an "
1636 "existing auto filter.");
1637 wbc_gtk_set_action_label
1638 (wbcg, "DataAutoFilter", NULL,
1639 nlabel, new_tip);
1641 g_free (nlabel);
1642 } else {
1643 label = has_filter
1644 ? _("Remove _Auto Filter")
1645 : _("Add _Auto Filter");
1646 new_tip = has_filter
1647 ? _("Remove a filter")
1648 : _("Add a filter");
1649 wbc_gtk_set_action_label (wbcg, "DataAutoFilter", NULL, label, new_tip);
1652 wbc_gtk_set_action_sensitivity (wbcg, "DataAutoFilter", active);
1654 if (MS_COMMENT_LINKS & flags) {
1655 gboolean has_comment
1656 = (sheet_get_comment (sheet, &sv->edit_pos) != NULL);
1657 gboolean has_link;
1658 GnmRange rge;
1659 range_init_cellpos (&rge, &sv->edit_pos);
1660 has_link = (NULL !=
1661 sheet_style_region_contains_link (sheet, &rge));
1662 wbc_gtk_set_action_sensitivity
1663 (wbcg, "EditComment", has_comment);
1664 wbc_gtk_set_action_sensitivity
1665 (wbcg, "EditHyperlink", has_link);
1668 if (MS_COMMENT_LINKS_RANGE & flags) {
1669 GSList *l;
1670 int count = 0;
1671 gboolean has_links = FALSE, has_comments = FALSE;
1672 gboolean sel_is_vector = FALSE;
1673 SheetView *sv = scg_view (scg);
1674 for (l = sv->selections;
1675 l != NULL; l = l->next) {
1676 GnmRange const *r = l->data;
1677 GSList *objs;
1678 GnmStyleList *styles;
1679 if (!has_links) {
1680 styles = sheet_style_collect_hlinks
1681 (sheet, r);
1682 has_links = (styles != NULL);
1683 style_list_free (styles);
1685 if (!has_comments) {
1686 objs = sheet_objects_get
1687 (sheet, r, GNM_CELL_COMMENT_TYPE);
1688 has_comments = (objs != NULL);
1689 g_slist_free (objs);
1691 if((count++ > 1) && has_comments && has_links)
1692 break;
1694 wbc_gtk_set_action_sensitivity
1695 (wbcg, "EditClearHyperlinks", has_links);
1696 wbc_gtk_set_action_sensitivity
1697 (wbcg, "EditClearComments", has_comments);
1698 if (count == 1) {
1699 GnmRange const *r = sv->selections->data;
1700 sel_is_vector = (range_width (r) == 1 ||
1701 range_height (r) == 1) &&
1702 !range_is_singleton (r);
1704 wbc_gtk_set_action_sensitivity
1705 (wbcg, "InsertSortDecreasing", sel_is_vector);
1706 wbc_gtk_set_action_sensitivity
1707 (wbcg, "InsertSortIncreasing", sel_is_vector);
1709 if (MS_FILE_EXPORT_IMPORT & flags) {
1710 Workbook *wb = wb_control_get_workbook (wbc);
1711 gboolean has_export_info = workbook_get_file_exporter (wb) &&
1712 workbook_get_last_export_uri (wb);
1713 wbc_gtk_set_action_sensitivity (wbcg, "DataExportRepeat", has_export_info);
1714 if (has_export_info) {
1715 gchar *base = go_basename_from_uri (workbook_get_last_export_uri (wb));
1716 gchar *new_label = g_strdup_printf (_("Repeat Export to %s"),
1717 base);
1718 g_free (base);
1719 wbc_gtk_set_action_label (wbcg, "DataExportRepeat", NULL,
1720 new_label, N_("Repeat the last data export"));
1721 g_free (new_label);
1722 } else
1723 wbc_gtk_set_action_label (wbcg, "DataExportRepeat", NULL,
1724 N_("Repeat Export"), N_("Repeat the last data export"));
1727 gboolean const has_slicer = (NULL != gnm_sheet_view_editpos_in_slicer (sv));
1728 char const* label = has_slicer
1729 ? _("Remove _Data Slicer")
1730 : _("Create _Data Slicer");
1731 char const *new_tip = has_slicer
1732 ? _("Remove a Data Slicer")
1733 : _("Create a Data Slicer");
1734 wbc_gtk_set_action_label (wbcg, "DataSlicer", NULL, label, new_tip);
1735 wbc_gtk_set_action_sensitivity (wbcg, "DataSlicerRefresh", has_slicer);
1736 wbc_gtk_set_action_sensitivity (wbcg, "DataSlicerEdit", has_slicer);
1740 static void
1741 wbcg_undo_redo_labels (WorkbookControl *wbc, char const *undo, char const *redo)
1743 WBCGtk *wbcg = (WBCGtk *)wbc;
1744 g_return_if_fail (wbcg != NULL);
1746 wbc_gtk_set_action_label (wbcg, "Redo", _("_Redo"), redo, NULL);
1747 wbc_gtk_set_action_label (wbcg, "Undo", _("_Undo"), undo, NULL);
1748 wbc_gtk_set_action_sensitivity (wbcg, "Repeat", undo != NULL);
1751 static void
1752 wbcg_paste_from_selection (WorkbookControl *wbc, GnmPasteTarget const *pt)
1754 gnm_x_request_clipboard ((WBCGtk *)wbc, pt);
1757 static gboolean
1758 wbcg_claim_selection (WorkbookControl *wbc)
1760 WBCGtk *wbcg = (WBCGtk *)wbc;
1761 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (wbcg_toplevel (wbcg)));
1762 return gnm_x_claim_clipboard (display);
1765 static int
1766 wbcg_show_save_dialog (WBCGtk *wbcg, Workbook *wb)
1768 GtkWidget *d;
1769 char *msg;
1770 char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
1771 int ret = 0;
1773 if (wb_uri) {
1774 char *base = go_basename_from_uri (wb_uri);
1775 char *display = g_markup_escape_text (base, -1);
1776 msg = g_strdup_printf (
1777 _("Save changes to workbook '%s' before closing?"),
1778 display);
1779 g_free (base);
1780 g_free (display);
1781 } else {
1782 msg = g_strdup (_("Save changes to workbook before closing?"));
1785 d = gnm_message_dialog_create (wbcg_toplevel (wbcg),
1786 GTK_DIALOG_DESTROY_WITH_PARENT,
1787 GTK_MESSAGE_WARNING,
1788 msg,
1789 _("If you close without saving, changes will be discarded."));
1790 atk_object_set_role (gtk_widget_get_accessible (d), ATK_ROLE_ALERT);
1792 go_gtk_dialog_add_button (GTK_DIALOG(d), _("Discard"),
1793 GTK_STOCK_DELETE, GTK_RESPONSE_NO);
1794 go_gtk_dialog_add_button (GTK_DIALOG(d), _("Don't close"),
1795 GNM_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1797 gtk_dialog_add_button (GTK_DIALOG(d), GNM_STOCK_SAVE, GTK_RESPONSE_YES);
1798 gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
1799 ret = go_gtk_dialog_run (GTK_DIALOG (d), wbcg_toplevel (wbcg));
1800 g_free (msg);
1802 return ret;
1806 * wbcg_close_if_user_permits : If the workbook is dirty the user is
1807 * prompted to see if they should exit.
1809 * Returns:
1810 * 0) canceled
1811 * 1) closed
1812 * 2) -
1813 * 3) save any future dirty
1814 * 4) do not save any future dirty
1816 static int
1817 wbcg_close_if_user_permits (WBCGtk *wbcg, WorkbookView *wb_view)
1819 gboolean can_close = TRUE;
1820 gboolean done = FALSE;
1821 int iteration = 0;
1822 int button = 0;
1823 Workbook *wb = wb_view_get_workbook (wb_view);
1824 static int in_can_close;
1826 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
1828 if (in_can_close)
1829 return 0;
1830 in_can_close = TRUE;
1832 while (go_doc_is_dirty (GO_DOC (wb)) && !done) {
1833 iteration++;
1834 button = wbcg_show_save_dialog(wbcg, wb);
1836 switch (button) {
1837 case GTK_RESPONSE_YES:
1838 done = gui_file_save (wbcg, wb_view);
1839 break;
1841 case GNM_RESPONSE_SAVE_ALL:
1842 done = gui_file_save (wbcg, wb_view);
1843 break;
1845 case GTK_RESPONSE_NO:
1846 done = TRUE;
1847 go_doc_set_dirty (GO_DOC (wb), FALSE);
1848 break;
1850 case GNM_RESPONSE_DISCARD_ALL:
1851 done = TRUE;
1852 go_doc_set_dirty (GO_DOC (wb), FALSE);
1853 break;
1855 default: /* CANCEL */
1856 can_close = FALSE;
1857 done = TRUE;
1858 break;
1862 in_can_close = FALSE;
1864 if (can_close) {
1865 gnm_x_store_clipboard_if_needed (wb);
1866 g_object_unref (wb);
1867 switch (button) {
1868 case GNM_RESPONSE_SAVE_ALL:
1869 return 3;
1870 case GNM_RESPONSE_DISCARD_ALL:
1871 return 4;
1872 default:
1873 return 1;
1875 } else
1876 return 0;
1880 * wbc_gtk_close:
1881 * @wbcg: #WBCGtk
1883 * Returns TRUE if the control should NOT be closed.
1885 gboolean
1886 wbc_gtk_close (WBCGtk *wbcg)
1888 WorkbookView *wb_view = wb_control_view (GNM_WBC (wbcg));
1890 g_return_val_if_fail (GNM_IS_WORKBOOK_VIEW (wb_view), TRUE);
1891 g_return_val_if_fail (wb_view->wb_controls != NULL, TRUE);
1893 /* If we were editing when the quit request came make sure we don't
1894 * lose any entered text
1896 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
1897 return TRUE;
1899 /* If something is still using the control
1900 * eg progress meter for a new book */
1901 if (G_OBJECT (wbcg)->ref_count > 1)
1902 return TRUE;
1904 /* This is the last control */
1905 if (wb_view->wb_controls->len <= 1) {
1906 Workbook *wb = wb_view_get_workbook (wb_view);
1908 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), TRUE);
1909 g_return_val_if_fail (wb->wb_views != NULL, TRUE);
1911 /* This is the last view */
1912 if (wb->wb_views->len <= 1) {
1913 if (wbcg_close_if_user_permits (wbcg, wb_view) == 0)
1914 return TRUE;
1915 return FALSE;
1918 g_object_unref (wb_view);
1919 } else
1920 g_object_unref (wbcg);
1922 _gnm_app_flag_windows_changed ();
1924 return FALSE;
1927 static void
1928 cb_cancel_input (WBCGtk *wbcg)
1930 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1933 static void
1934 cb_accept_input (WBCGtk *wbcg)
1936 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL);
1939 static void
1940 cb_accept_input_wo_ac (WBCGtk *wbcg)
1942 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_WO_AC, NULL);
1945 static void
1946 cb_accept_input_array (WBCGtk *wbcg)
1948 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_ARRAY, NULL);
1951 static void
1952 cb_accept_input_selected_cells (WBCGtk *wbcg)
1954 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_RANGE, NULL);
1957 static void
1958 cb_accept_input_selected_merged (WBCGtk *wbcg)
1960 Sheet *sheet = wbcg->editing_sheet;
1962 #warning FIXME: this creates 2 undo items!
1963 if (wbcg_is_editing (wbcg) &&
1964 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL)) {
1965 WorkbookControl *wbc = GNM_WBC (wbcg);
1966 WorkbookView *wbv = wb_control_view (wbc);
1967 SheetView *sv = sheet_get_view (sheet, wbv);
1968 GnmRange sel = *(selection_first_range (sv, NULL, NULL));
1969 GSList *selection = g_slist_prepend (NULL, &sel);
1971 cmd_merge_cells (wbc, sheet, selection, FALSE);
1972 g_slist_free (selection);
1976 /* static void */
1977 /* cb_accept_input_sheets_collector (Sheet *sheet, GSList **n) */
1978 /* { */
1979 /* if (sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE) */
1980 /* (*n) = g_slist_prepend (*n, sheet); */
1981 /* } */
1983 /* static void */
1984 /* cb_accept_input_sheets (WBCGtk *wbcg) */
1985 /* { */
1986 /* GSList *sheets = workbook_sheets */
1987 /* (wb_control_get_workbook (GNM_WBC (wbcg))); */
1988 /* GSList *vis_sheets = NULL; */
1990 /* g_slist_foreach (sheets, */
1991 /* (GFunc) cb_accept_input_sheets_collector, */
1992 /* &vis_sheets); */
1994 /* wbcg_edit_multisheet_finish (wbcg, WBC_EDIT_ACCEPT, NULL, vis_sheets); */
1996 /* g_slist_free (sheets); */
1997 /* g_slist_free (vis_sheets); */
1998 /* } */
2000 /* static void */
2001 /* cb_accept_input_menu_sensitive_sheets_counter (Sheet *sheet, gint *n) */
2002 /* { */
2003 /* if (sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE) */
2004 /* (*n)++; */
2005 /* } */
2007 /* static gboolean */
2008 /* cb_accept_input_menu_sensitive_sheets (WBCGtk *wbcg) */
2009 /* { */
2010 /* GSList *sheets = workbook_sheets */
2011 /* (wb_control_get_workbook (GNM_WBC (wbcg))); */
2012 /* gint n = 0; */
2014 /* g_slist_foreach (sheets, */
2015 /* (GFunc) cb_accept_input_menu_sensitive_sheets_counter, */
2016 /* &n); */
2017 /* g_slist_free (sheets); */
2018 /* return (n > 1); */
2019 /* } */
2021 /* static gboolean */
2022 /* cb_accept_input_menu_sensitive_selected_sheets (WBCGtk *wbcg) */
2023 /* { */
2024 /* GSList *sheets = workbook_sheets */
2025 /* (wb_control_get_workbook (GNM_WBC (wbcg))); */
2026 /* gint n = 0; */
2028 /* g_slist_foreach (sheets, */
2029 /* (GFunc) cb_accept_input_menu_sensitive_sheets_counter, */
2030 /* &n); */
2031 /* g_slist_free (sheets); */
2032 /* return (n > 2); */
2033 /* } */
2035 static gboolean
2036 cb_accept_input_menu_sensitive_selected_cells (WBCGtk *wbcg)
2038 WorkbookControl *wbc = GNM_WBC (wbcg);
2039 WorkbookView *wbv = wb_control_view (wbc);
2040 SheetView *sv = sheet_get_view (wbcg->editing_sheet, wbv);
2041 gboolean result = TRUE;
2042 GSList *selection = selection_get_ranges (sv, FALSE), *l;
2044 for (l = selection; l != NULL; l = l->next) {
2045 GnmRange const *sel = l->data;
2046 if (sheet_range_splits_array
2047 (wbcg->editing_sheet, sel, NULL, NULL, NULL)) {
2048 result = FALSE;
2049 break;
2052 range_fragment_free (selection);
2053 return result;
2056 static gboolean
2057 cb_accept_input_menu_sensitive_selected_merged (WBCGtk *wbcg)
2059 WorkbookControl *wbc = GNM_WBC (wbcg);
2060 WorkbookView *wbv = wb_control_view (wbc);
2061 SheetView *sv = sheet_get_view (wbcg->editing_sheet, wbv);
2062 GnmRange const *sel = selection_first_range (sv, NULL, NULL);
2064 return (sel && !range_is_singleton (sel) &&
2065 sv->edit_pos.col == sel->start.col &&
2066 sv->edit_pos.row == sel->start.row &&
2067 !sheet_range_splits_array
2068 (wbcg->editing_sheet, sel, NULL, NULL, NULL));
2071 static void
2072 cb_accept_input_menu (GtkMenuToolButton *button, WBCGtk *wbcg)
2074 GtkWidget *menu = gtk_menu_tool_button_get_menu (button);
2075 GList *l, *children = gtk_container_get_children (GTK_CONTAINER (menu));
2077 struct AcceptInputMenu {
2078 gchar const *text;
2079 void (*function) (WBCGtk *wbcg);
2080 gboolean (*sensitive) (WBCGtk *wbcg);
2081 } const accept_input_actions [] = {
2082 { N_("Enter in current cell"), cb_accept_input,
2083 NULL },
2084 { N_("Enter in current cell without autocorrection"), cb_accept_input_wo_ac,
2085 NULL },
2086 /* { N_("Enter on all non-hidden sheets"), cb_accept_input_sheets, */
2087 /* cb_accept_input_menu_sensitive_sheets}, */
2088 /* { N_("Enter on multiple sheets..."), cb_accept_input_selected_sheets, */
2089 /* cb_accept_input_menu_sensitive_selected_sheets }, */
2090 { NULL, NULL, NULL },
2091 { N_("Enter in current range merged"), cb_accept_input_selected_merged,
2092 cb_accept_input_menu_sensitive_selected_merged },
2093 { NULL, NULL, NULL },
2094 { N_("Enter in selected ranges"), cb_accept_input_selected_cells,
2095 cb_accept_input_menu_sensitive_selected_cells },
2096 { N_("Enter in selected ranges as array"), cb_accept_input_array,
2097 cb_accept_input_menu_sensitive_selected_cells },
2099 unsigned int ui;
2100 GtkWidget *item;
2101 const struct AcceptInputMenu *it;
2103 if (children == NULL)
2104 for (ui = 0; ui < G_N_ELEMENTS (accept_input_actions); ui++) {
2105 it = accept_input_actions + ui;
2107 if (it->text) {
2108 item = gtk_image_menu_item_new_with_label
2109 (_(it->text));
2110 if (it->function)
2111 g_signal_connect_swapped
2112 (G_OBJECT (item), "activate",
2113 G_CALLBACK (it->function),
2114 wbcg);
2115 if (wbcg->editing_sheet) {
2116 if (it->sensitive)
2117 gtk_widget_set_sensitive
2118 (item, (it->sensitive) (wbcg));
2119 else
2120 gtk_widget_set_sensitive (item, TRUE);
2121 } else
2122 gtk_widget_set_sensitive (item, FALSE);
2123 } else
2124 item = gtk_separator_menu_item_new ();
2125 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2126 gtk_widget_show (item);
2128 else
2129 for (ui = 0, l = children;
2130 ui < G_N_ELEMENTS (accept_input_actions) && l != NULL;
2131 ui++, l = l->next) {
2132 it = accept_input_actions + ui;
2133 if (wbcg->editing_sheet) {
2134 if (it->sensitive)
2135 gtk_widget_set_sensitive
2136 (GTK_WIDGET (l->data),
2137 (it->sensitive) (wbcg));
2138 else
2139 gtk_widget_set_sensitive
2140 (GTK_WIDGET (l->data), TRUE);
2141 } else
2142 gtk_widget_set_sensitive (l->data, FALSE);
2146 g_list_free (children);
2149 static gboolean
2150 cb_editline_focus_in (GtkWidget *w, GdkEventFocus *event,
2151 WBCGtk *wbcg)
2153 if (!wbcg_is_editing (wbcg))
2154 if (!wbcg_edit_start (wbcg, FALSE, TRUE)) {
2155 #if 0
2156 GtkEntry *entry = GTK_ENTRY (w);
2157 #endif
2158 wbcg_focus_cur_scg (wbcg);
2159 #warning GTK3: what can we do there for gtk3?
2160 #if 0
2161 entry->in_drag = FALSE;
2163 * ->button is private, ugh. Since the text area
2164 * never gets a release event, there seems to be
2165 * no official way of returning the widget to its
2166 * correct state.
2168 entry->button = 0;
2169 #endif
2170 return TRUE;
2173 return FALSE;
2176 static void
2177 cb_statusbox_activate (GtkEntry *entry, WBCGtk *wbcg)
2179 WorkbookControl *wbc = GNM_WBC (wbcg);
2180 wb_control_parse_and_jump (wbc, gtk_entry_get_text (entry));
2181 wbcg_focus_cur_scg (wbcg);
2182 wb_view_selection_desc (wb_control_view (wbc), TRUE, wbc);
2185 static gboolean
2186 cb_statusbox_focus (GtkEntry *entry, GdkEventFocus *event,
2187 WBCGtk *wbcg)
2189 gtk_editable_select_region (GTK_EDITABLE (entry), 0, 0);
2190 return FALSE;
2193 /******************************************************************************/
2195 static void
2196 dump_size_tree (GtkWidget *w, gpointer indent_)
2198 int indent = GPOINTER_TO_INT (indent_);
2199 int h1, h2;
2200 GtkAllocation a;
2202 g_printerr ("%*s", indent, "");
2203 if (gtk_widget_get_name (w))
2204 g_printerr ("\"%s\" ", gtk_widget_get_name (w));
2206 gtk_widget_get_preferred_height (w, &h1, &h2);
2207 gtk_widget_get_allocation (w, &a);
2209 g_printerr ("%s %p viz=%d act=%dx%d minheight=%d natheight=%d\n",
2210 g_type_name_from_instance ((GTypeInstance *)w), w,
2211 gtk_widget_get_visible (w),
2212 a.width, a.height,
2213 h1, h2);
2215 if (GTK_IS_CONTAINER (w)) {
2216 gtk_container_foreach (GTK_CONTAINER (w),
2217 dump_size_tree,
2218 GINT_TO_POINTER (indent + 2));
2223 static void
2224 cb_workbook_debug_info (WBCGtk *wbcg)
2226 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
2228 if (gnm_debug_flag ("notebook-size"))
2229 dump_size_tree (GTK_WIDGET (wbcg_toplevel (wbcg)), GINT_TO_POINTER (0));
2231 if (gnm_debug_flag ("deps")) {
2232 dependents_dump (wb);
2235 if (gnm_debug_flag ("expr-sharer")) {
2236 GnmExprSharer *es = workbook_share_expressions (wb, FALSE);
2237 gnm_expr_sharer_report (es);
2238 gnm_expr_sharer_destroy (es);
2241 if (gnm_debug_flag ("style-optimize")) {
2242 workbook_optimize_style (wb);
2245 if (gnm_debug_flag ("name-collections")) {
2246 gnm_named_expr_collection_dump (wb->names, "workbook");
2247 WORKBOOK_FOREACH_SHEET(wb, sheet, {
2248 gnm_named_expr_collection_dump (sheet->names,
2249 sheet->name_unquoted);
2254 static void
2255 cb_autofunction (WBCGtk *wbcg)
2257 GtkEntry *entry;
2258 gchar const *txt;
2260 if (wbcg_is_editing (wbcg))
2261 return;
2263 entry = wbcg_get_entry (wbcg);
2264 txt = gtk_entry_get_text (entry);
2265 if (strncmp (txt, "=", 1)) {
2266 if (!wbcg_edit_start (wbcg, TRUE, TRUE))
2267 return; /* attempt to edit failed */
2268 gtk_entry_set_text (entry, "=");
2269 gtk_editable_set_position (GTK_EDITABLE (entry), 1);
2270 } else {
2271 if (!wbcg_edit_start (wbcg, FALSE, TRUE))
2272 return; /* attempt to edit failed */
2274 /* FIXME : This is crap!
2275 * When the function druid is more complete use that.
2277 gtk_editable_set_position (GTK_EDITABLE (entry),
2278 gtk_entry_get_text_length (entry)-1);
2283 * We must not crash on focus=NULL. We're called like that as a result of
2284 * gtk_window_set_focus (toplevel, NULL) if the first sheet view is destroyed
2285 * just after being created. This happens e.g when we cancel a file import or
2286 * the import fails.
2288 static void
2289 cb_set_focus (GtkWindow *window, GtkWidget *focus, WBCGtk *wbcg)
2291 if (focus && !gtk_window_get_focus (window))
2292 wbcg_focus_cur_scg (wbcg);
2295 /***************************************************************************/
2297 static gboolean
2298 cb_scroll_wheel (GtkWidget *w, GdkEventScroll *event,
2299 WBCGtk *wbcg)
2301 SheetControlGUI *scg = wbcg_get_scg (wbcg, wbcg_focus_cur_scg (wbcg));
2302 Sheet *sheet = scg_sheet (scg);
2303 /* scroll always operates on pane 0 */
2304 GnmPane *pane = scg_pane (scg, 0);
2305 gboolean go_horiz = (event->direction == GDK_SCROLL_LEFT ||
2306 event->direction == GDK_SCROLL_RIGHT);
2307 gboolean go_back = (event->direction == GDK_SCROLL_UP ||
2308 event->direction == GDK_SCROLL_LEFT);
2310 if (!pane ||
2311 !gtk_widget_get_realized (w) ||
2312 event->direction == GDK_SCROLL_SMOOTH)
2313 return FALSE;
2315 if ((event->state & GDK_SHIFT_MASK))
2316 go_horiz = !go_horiz;
2318 if ((event->state & GDK_CONTROL_MASK)) { /* zoom */
2319 int zoom = (int)(sheet->last_zoom_factor_used * 100. + .5) - 10;
2321 if ((zoom % 15) != 0) {
2322 zoom = 15 * (int)(zoom/15);
2323 if (go_back)
2324 zoom += 15;
2325 } else {
2326 if (go_back)
2327 zoom += 15;
2328 else
2329 zoom -= 15;
2332 if (0 <= zoom && zoom <= 390)
2333 cmd_zoom (GNM_WBC (wbcg), g_slist_append (NULL, sheet),
2334 (double) (zoom + 10) / 100);
2335 } else if (go_horiz) {
2336 int col = (pane->last_full.col - pane->first.col) / 4;
2337 if (col < 1)
2338 col = 1;
2339 if (go_back)
2340 col = pane->first.col - col;
2341 else
2342 col = pane->first.col + col;
2343 scg_set_left_col (pane->simple.scg, col);
2344 } else {
2345 int row = (pane->last_full.row - pane->first.row) / 4;
2346 if (row < 1)
2347 row = 1;
2348 if (go_back)
2349 row = pane->first.row - row;
2350 else
2351 row = pane->first.row + row;
2352 scg_set_top_row (pane->simple.scg, row);
2354 return TRUE;
2358 * Make current control size the default. Toplevel would resize
2359 * spontaneously. This makes it stay the same size until user resizes.
2361 static void
2362 cb_realize (GtkWindow *toplevel, WBCGtk *wbcg)
2364 GtkAllocation ta;
2366 g_return_if_fail (GTK_IS_WINDOW (toplevel));
2368 gtk_widget_get_allocation (GTK_WIDGET (toplevel), &ta);
2369 gtk_window_set_default_size (toplevel, ta.width, ta.height);
2371 /* if we are already initialized set the focus. Without this loading a
2372 * multpage book sometimes leaves focus on the last book rather than
2373 * the current book. Which leads to a slew of errors for keystrokes
2374 * until focus is corrected.
2376 if (wbcg->snotebook) {
2377 wbcg_focus_cur_scg (wbcg);
2378 wbcg_update_menu_feedback (wbcg, wbcg_cur_sheet (wbcg));
2382 static void
2383 cb_css_parse_error (GtkCssProvider *css, GtkCssSection *section, GError *err)
2385 if (g_error_matches (err, GTK_CSS_PROVIDER_ERROR,
2386 GTK_CSS_PROVIDER_ERROR_DEPRECATED) &&
2387 !gnm_debug_flag ("css"))
2388 return;
2390 g_warning ("Theme parsing error: %s", err->message);
2393 struct css_provider_data {
2394 GtkCssProvider *css;
2395 GSList *screens;
2398 static void
2399 cb_unload_providers (gpointer data_)
2401 struct css_provider_data *data = data_;
2402 GSList *l;
2404 for (l = data->screens; l; l = l->next) {
2405 GdkScreen *screen = l->data;
2406 gtk_style_context_remove_provider_for_screen
2407 (screen, GTK_STYLE_PROVIDER (data->css));
2409 g_slist_free (data->screens);
2410 g_object_unref (data->css);
2411 g_free (data);
2414 static void
2415 cb_screen_changed (GtkWidget *widget)
2417 GdkScreen *screen = gtk_widget_get_screen (widget);
2418 GObject *app = gnm_app_get_app ();
2419 const char *app_key = "css-provider";
2420 struct css_provider_data *data;
2422 data = g_object_get_data (app, app_key);
2423 if (!data) {
2424 const char *resource = "/org/gnumeric/gnumeric/ui/gnumeric.css";
2425 GBytes *cssbytes = g_resources_lookup_data (resource, 0, NULL);
2426 const char *csstext = g_bytes_get_data (cssbytes, NULL);
2427 gboolean debug = gnm_debug_flag ("css");
2429 data = g_new (struct css_provider_data, 1);
2430 data->css = gtk_css_provider_new ();
2431 data->screens = NULL;
2433 if (debug)
2434 g_printerr ("Loading style from %s\n", resource);
2435 else
2436 g_signal_connect (data->css, "parsing-error",
2437 G_CALLBACK (cb_css_parse_error),
2438 NULL);
2440 gtk_css_provider_load_from_data (data->css, csstext, -1, NULL);
2441 g_object_set_data_full (app, app_key, data, cb_unload_providers);
2442 g_bytes_unref (cssbytes);
2445 if (screen && !g_slist_find (data->screens, screen)) {
2446 gtk_style_context_add_provider_for_screen
2447 (screen,
2448 GTK_STYLE_PROVIDER (data->css),
2449 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2450 data->screens = g_slist_prepend (data->screens, screen);
2454 void
2455 wbcg_set_status_text (WBCGtk *wbcg, char const *text)
2457 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2458 gtk_statusbar_pop (GTK_STATUSBAR (wbcg->status_text), 0);
2459 gtk_statusbar_push (GTK_STATUSBAR (wbcg->status_text), 0, text);
2462 static void
2463 set_visibility (WBCGtk *wbcg,
2464 char const *action_name,
2465 gboolean visible)
2467 GtkWidget *w = g_hash_table_lookup (wbcg->visibility_widgets, action_name);
2468 if (w)
2469 gtk_widget_set_visible (w, visible);
2470 wbc_gtk_set_toggle_action_state (wbcg, action_name, visible);
2474 void
2475 wbcg_toggle_visibility (WBCGtk *wbcg, GtkToggleAction *action)
2477 if (!wbcg->updating_ui && wbcg_ui_update_begin (wbcg)) {
2478 char const *name = gtk_action_get_name (GTK_ACTION (action));
2479 set_visibility (wbcg, name,
2480 gtk_toggle_action_get_active (action));
2481 wbcg_ui_update_end (wbcg);
2485 static void
2486 cb_visibility (char const *action, GtkWidget *orig_widget, WBCGtk *new_wbcg)
2488 set_visibility (new_wbcg, action, gtk_widget_get_visible (orig_widget));
2491 void
2492 wbcg_copy_toolbar_visibility (WBCGtk *new_wbcg,
2493 WBCGtk *wbcg)
2495 g_hash_table_foreach (wbcg->visibility_widgets,
2496 (GHFunc)cb_visibility, new_wbcg);
2500 void
2501 wbcg_set_end_mode (WBCGtk *wbcg, gboolean flag)
2503 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2505 if (!wbcg->last_key_was_end != !flag) {
2506 const char *txt = flag ? _("END") : "";
2507 wbcg_set_status_text (wbcg, txt);
2508 wbcg->last_key_was_end = flag;
2512 static PangoFontDescription *
2513 settings_get_font_desc (GtkSettings *settings)
2515 PangoFontDescription *font_desc;
2516 char *font_str;
2518 g_object_get (settings, "gtk-font-name", &font_str, NULL);
2519 font_desc = pango_font_description_from_string (
2520 font_str ? font_str : "sans 10");
2521 g_free (font_str);
2523 return font_desc;
2526 static void
2527 cb_update_item_bar_font (GtkWidget *w)
2529 SheetControlGUI *scg = get_scg (w);
2530 sc_resize ((SheetControl *)scg, TRUE);
2533 static void
2534 cb_desktop_font_changed (GtkSettings *settings, GParamSpec *pspec,
2535 WBCGtk *wbcg)
2537 if (wbcg->font_desc)
2538 pango_font_description_free (wbcg->font_desc);
2539 wbcg->font_desc = settings_get_font_desc (settings);
2540 gtk_container_foreach (GTK_CONTAINER (wbcg->snotebook),
2541 (GtkCallback)cb_update_item_bar_font, NULL);
2544 static GdkScreen *
2545 wbcg_get_screen (WBCGtk *wbcg)
2547 return gtk_widget_get_screen (wbcg->notebook_area);
2550 static GtkSettings *
2551 wbcg_get_gtk_settings (WBCGtk *wbcg)
2553 return gtk_settings_get_for_screen (wbcg_get_screen (wbcg));
2556 /* ------------------------------------------------------------------------- */
2558 static int
2559 show_gui (WBCGtk *wbcg)
2561 SheetControlGUI *scg;
2562 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
2563 int sx, sy;
2564 gdouble fx, fy;
2565 GdkRectangle rect;
2566 GdkScreen *screen = wbcg_get_screen (wbcg);
2568 /* In a Xinerama setup, we want the geometry of the actual display
2569 * unit, if available. See bug 59902. */
2570 gdk_screen_get_monitor_geometry (screen, 0, &rect);
2571 sx = MAX (rect.width, 600);
2572 sy = MAX (rect.height, 200);
2574 fx = gnm_conf_get_core_gui_window_x ();
2575 fy = gnm_conf_get_core_gui_window_y ();
2577 /* Successfully parsed geometry string and urged WM to comply */
2578 if (NULL != wbcg->preferred_geometry && NULL != wbcg->toplevel &&
2579 gtk_window_parse_geometry (GTK_WINDOW (wbcg->toplevel),
2580 wbcg->preferred_geometry)) {
2581 g_free (wbcg->preferred_geometry);
2582 wbcg->preferred_geometry = NULL;
2583 } else if (wbcg->snotebook != NULL &&
2584 wbv != NULL &&
2585 (wbv->preferred_width > 0 || wbv->preferred_height > 0)) {
2586 /* Set grid size to preferred width */
2587 int pwidth = MIN(wbv->preferred_width, gdk_screen_get_width (screen));
2588 int pheight = MIN(wbv->preferred_height, gdk_screen_get_height (screen));
2589 GtkRequisition requisition;
2591 pwidth = pwidth > 0 ? pwidth : -1;
2592 pheight = pheight > 0 ? pheight : -1;
2593 gtk_widget_set_size_request (GTK_WIDGET (wbcg->notebook_area),
2594 pwidth, pheight);
2595 gtk_widget_get_preferred_size (GTK_WIDGET (wbcg->toplevel),
2596 &requisition, NULL);
2597 /* We want to test if toplevel is bigger than screen.
2598 * gtk_widget_size_request tells us the space
2599 * allocated to the toplevel proper, but not how much is
2600 * need for WM decorations or a possible panel.
2602 * The test below should very rarely maximize when there is
2603 * actually room on the screen.
2605 * We maximize instead of resizing for two reasons:
2606 * - The preferred width / height is restored with one click on
2607 * unmaximize.
2608 * - We don't have to guess what size we should resize to.
2610 if (requisition.height + 20 > rect.height ||
2611 requisition.width > rect.width) {
2612 gtk_window_maximize (GTK_WINDOW (wbcg->toplevel));
2613 } else {
2614 gtk_window_set_default_size
2615 (wbcg_toplevel (wbcg),
2616 requisition.width, requisition.height);
2618 } else {
2619 /* Use default */
2620 gtk_window_set_default_size (wbcg_toplevel (wbcg), sx * fx, sy * fy);
2623 scg = wbcg_cur_scg (wbcg);
2624 if (scg)
2625 wbcg_set_direction (scg);
2627 gtk_widget_show (GTK_WIDGET (wbcg_toplevel (wbcg)));
2629 /* rehide headers if necessary */
2630 if (NULL != scg && wbcg_cur_sheet (wbcg))
2631 scg_adjust_preferences (scg);
2633 gtk_widget_set_size_request (GTK_WIDGET (wbcg->notebook_area),
2634 -1, -1);
2635 return FALSE;
2638 static GtkWidget *
2639 wbcg_get_label_for_position (WBCGtk *wbcg, GtkWidget *source,
2640 gint x)
2642 guint n, i;
2643 GtkWidget *last_visible = NULL;
2645 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
2647 n = wbcg_get_n_scg (wbcg);
2648 for (i = 0; i < n; i++) {
2649 GtkWidget *label = gnm_notebook_get_nth_label (wbcg->bnotebook, i);
2650 int x0, x1;
2651 GtkAllocation la;
2653 if (!gtk_widget_get_visible (label))
2654 continue;
2656 gtk_widget_get_allocation (label, &la);
2657 x0 = la.x;
2658 x1 = x0 + la.width;
2660 if (x <= x1) {
2662 * We are left of this label's right edge. Use it
2663 * even if we are far left of the label.
2665 return label;
2668 last_visible = label;
2671 return last_visible;
2674 static gboolean
2675 wbcg_is_local_drag (WBCGtk *wbcg, GtkWidget *source_widget)
2677 GtkWidget *top = (GtkWidget *)wbcg_toplevel (wbcg);
2678 return GNM_IS_PANE (source_widget) &&
2679 gtk_widget_get_toplevel (source_widget) == top;
2681 static gboolean
2682 cb_wbcg_drag_motion (GtkWidget *widget, GdkDragContext *context,
2683 gint x, gint y, guint time, WBCGtk *wbcg)
2685 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2687 if (GNM_IS_NOTEBOOK (gtk_widget_get_parent (source_widget))) {
2688 /* The user wants to reorder sheets. We simulate a
2689 * drag motion over a label.
2691 GtkWidget *label = wbcg_get_label_for_position (wbcg, source_widget, x);
2692 return cb_sheet_label_drag_motion (label, context, x, y,
2693 time, wbcg);
2694 } else if (wbcg_is_local_drag (wbcg, source_widget))
2695 gnm_pane_object_autoscroll (GNM_PANE (source_widget),
2696 context, x, y, time);
2698 return TRUE;
2701 static void
2702 cb_wbcg_drag_leave (GtkWidget *widget, GdkDragContext *context,
2703 guint time, WBCGtk *wbcg)
2705 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2707 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2709 if (GNM_IS_NOTEBOOK (gtk_widget_get_parent (source_widget)))
2710 gtk_widget_hide (
2711 g_object_get_data (G_OBJECT (source_widget), "arrow"));
2712 else if (wbcg_is_local_drag (wbcg, source_widget))
2713 gnm_pane_slide_stop (GNM_PANE (source_widget));
2716 static void
2717 cb_wbcg_drag_data_received (GtkWidget *widget, GdkDragContext *context,
2718 gint x, gint y, GtkSelectionData *selection_data,
2719 guint info, guint time, WBCGtk *wbcg)
2721 gchar *target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
2723 if (!strcmp (target_type, "text/uri-list")) { /* filenames from nautilus */
2724 scg_drag_data_received (wbcg_cur_scg (wbcg),
2725 gtk_drag_get_source_widget (context), 0, 0,
2726 selection_data);
2727 } else if (!strcmp (target_type, "GNUMERIC_SHEET")) {
2728 /* The user wants to reorder the sheets but hasn't dropped
2729 * the sheet onto a label. Never mind. We figure out
2730 * where the arrow is currently located and simulate a drop
2731 * on that label. */
2732 GtkWidget *label = wbcg_get_label_for_position (wbcg,
2733 gtk_drag_get_source_widget (context), x);
2734 cb_sheet_label_drag_data_received (label, context, x, y,
2735 selection_data, info, time, wbcg);
2736 } else {
2737 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2738 if (wbcg_is_local_drag (wbcg, source_widget))
2739 g_printerr ("autoscroll complete - stop it\n");
2740 else
2741 scg_drag_data_received (wbcg_cur_scg (wbcg),
2742 source_widget, 0, 0, selection_data);
2744 g_free (target_type);
2747 static void cb_cs_go_up (WBCGtk *wbcg)
2748 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_top); }
2749 static void cb_cs_go_down (WBCGtk *wbcg)
2750 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_bottom); }
2751 static void cb_cs_go_left (WBCGtk *wbcg)
2752 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_first); }
2753 static void cb_cs_go_right (WBCGtk *wbcg)
2754 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_last); }
2755 static void cb_cs_go_to_cell (WBCGtk *wbcg) { dialog_goto_cell (wbcg); }
2757 static void
2758 wbc_gtk_cell_selector_popup (G_GNUC_UNUSED GtkEntry *entry,
2759 G_GNUC_UNUSED GtkEntryIconPosition icon_pos,
2760 G_GNUC_UNUSED GdkEvent *event,
2761 gpointer data)
2763 if (event->type == GDK_BUTTON_PRESS) {
2764 WBCGtk *wbcg = data;
2766 struct CellSelectorMenu {
2767 gchar const *text;
2768 void (*function) (WBCGtk *wbcg);
2769 } const cell_selector_actions [] = {
2770 { N_("Go to Top"), &cb_cs_go_up },
2771 { N_("Go to Bottom"), &cb_cs_go_down },
2772 { N_("Go to First"), &cb_cs_go_left },
2773 { N_("Go to Last"), &cb_cs_go_right },
2774 { NULL, NULL },
2775 { N_("Go to Cell..."), &cb_cs_go_to_cell }
2777 unsigned int ui;
2778 GtkWidget *item, *menu = gtk_menu_new ();
2779 gboolean active = (!wbcg_is_editing (wbcg) &&
2780 NULL == wbc_gtk_get_guru (wbcg));
2782 for (ui = 0; ui < G_N_ELEMENTS (cell_selector_actions); ui++) {
2783 const struct CellSelectorMenu *it =
2784 cell_selector_actions + ui;
2785 if (it->text)
2786 item = gtk_image_menu_item_new_with_label
2787 (_(it->text));
2788 else
2789 item = gtk_separator_menu_item_new ();
2791 if (it->function)
2792 g_signal_connect_swapped
2793 (G_OBJECT (item), "activate",
2794 G_CALLBACK (it->function), wbcg);
2795 gtk_widget_set_sensitive (item, active);
2796 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2797 gtk_widget_show (item);
2800 gnumeric_popup_menu (GTK_MENU (menu), event);
2804 static void
2805 wbc_gtk_create_edit_area (WBCGtk *wbcg)
2807 GtkToolItem *item;
2808 GtkEntry *entry;
2809 int len;
2810 GtkWidget *debug_button;
2812 wbc_gtk_init_editline (wbcg);
2813 entry = wbcg_get_entry (wbcg);
2815 /* Set a reasonable width for the selection box. */
2816 len = gnm_widget_measure_string
2817 (GTK_WIDGET (wbcg_toplevel (wbcg)),
2818 cell_coord_name (GNM_MAX_COLS - 1, GNM_MAX_ROWS - 1));
2820 * Add a little extra since font might be proportional and since
2821 * we also put user defined names there.
2823 len = len * 3 / 2;
2824 gtk_widget_set_size_request (wbcg->selection_descriptor, len, -1);
2826 g_signal_connect_swapped (wbcg->cancel_button,
2827 "clicked", G_CALLBACK (cb_cancel_input),
2828 wbcg);
2830 g_signal_connect_swapped (wbcg->ok_button,
2831 "clicked", G_CALLBACK (cb_accept_input),
2832 wbcg);
2833 gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (wbcg->ok_button),
2834 gtk_menu_new ());
2835 gtk_menu_tool_button_set_arrow_tooltip_text
2836 (GTK_MENU_TOOL_BUTTON (wbcg->ok_button),
2837 _("Accept change in multiple cells"));
2838 g_signal_connect (wbcg->ok_button,
2839 "show-menu", G_CALLBACK (cb_accept_input_menu),
2840 wbcg);
2842 g_signal_connect_swapped (wbcg->func_button,
2843 "clicked", G_CALLBACK (cb_autofunction),
2844 wbcg);
2846 /* Dependency debugger */
2847 debug_button = GET_GUI_ITEM ("debug_button");
2848 if (gnm_debug_flag ("notebook-size") ||
2849 gnm_debug_flag ("deps") ||
2850 gnm_debug_flag ("expr-sharer") ||
2851 gnm_debug_flag ("style-optimize") ||
2852 gnm_debug_flag ("name-collections")) {
2853 g_signal_connect_swapped (debug_button,
2854 "clicked", G_CALLBACK (cb_workbook_debug_info),
2855 wbcg);
2856 } else {
2857 gtk_widget_destroy (debug_button);
2860 item = GET_GUI_ITEM ("edit_line_entry_item");
2861 gtk_container_add (GTK_CONTAINER (item),
2862 GTK_WIDGET (wbcg->edit_line.entry));
2863 gtk_widget_show_all (GTK_WIDGET (item));
2865 /* Do signal setup for the editing input line */
2866 g_signal_connect (G_OBJECT (entry),
2867 "focus-in-event",
2868 G_CALLBACK (cb_editline_focus_in), wbcg);
2870 /* status box */
2871 g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2872 "activate",
2873 G_CALLBACK (cb_statusbox_activate), wbcg);
2874 g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2875 "focus-out-event",
2876 G_CALLBACK (cb_statusbox_focus), wbcg);
2878 gtk_entry_set_icon_from_icon_name
2879 (GTK_ENTRY (wbcg->selection_descriptor),
2880 GTK_ENTRY_ICON_SECONDARY, "go-jump");
2881 gtk_entry_set_icon_sensitive
2882 (GTK_ENTRY (wbcg->selection_descriptor),
2883 GTK_ENTRY_ICON_SECONDARY, TRUE);
2884 gtk_entry_set_icon_activatable
2885 (GTK_ENTRY (wbcg->selection_descriptor),
2886 GTK_ENTRY_ICON_SECONDARY, TRUE);
2888 g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2889 "icon-press",
2890 G_CALLBACK
2891 (wbc_gtk_cell_selector_popup),
2892 wbcg);
2895 static int
2896 wbcg_validation_msg (WorkbookControl *wbc, ValidationStyle v,
2897 char const *title, char const *msg)
2899 WBCGtk *wbcg = (WBCGtk *)wbc;
2900 ValidationStatus res0, res1 = GNM_VALIDATION_STATUS_VALID; /* supress warning */
2901 char const *btn0, *btn1;
2902 GtkMessageType type;
2903 GtkWidget *dialog;
2904 int response;
2906 switch (v) {
2907 case GNM_VALIDATION_STYLE_STOP:
2908 res0 = GNM_VALIDATION_STATUS_INVALID_EDIT;
2909 btn0 = _("_Re-Edit");
2910 res1 = GNM_VALIDATION_STATUS_INVALID_DISCARD;
2911 btn1 = _("_Discard");
2912 type = GTK_MESSAGE_ERROR;
2913 break;
2914 case GNM_VALIDATION_STYLE_WARNING:
2915 res0 = GNM_VALIDATION_STATUS_VALID;
2916 btn0 = _("_Accept");
2917 res1 = GNM_VALIDATION_STATUS_INVALID_DISCARD;
2918 btn1 = _("_Discard");
2919 type = GTK_MESSAGE_WARNING;
2920 break;
2921 case GNM_VALIDATION_STYLE_INFO:
2922 res0 = GNM_VALIDATION_STATUS_VALID;
2923 btn0 = GNM_STOCK_OK;
2924 btn1 = NULL;
2925 type = GTK_MESSAGE_INFO;
2926 break;
2927 case GNM_VALIDATION_STYLE_PARSE_ERROR:
2928 res0 = GNM_VALIDATION_STATUS_INVALID_EDIT;
2929 btn0 = _("_Re-Edit");
2930 res1 = GNM_VALIDATION_STATUS_VALID;
2931 btn1 = _("_Accept");
2932 type = GTK_MESSAGE_ERROR;
2933 break;
2935 default:
2936 g_assert_not_reached ();
2939 dialog = gtk_message_dialog_new (wbcg_toplevel (wbcg),
2940 GTK_DIALOG_DESTROY_WITH_PARENT,
2941 type, GTK_BUTTONS_NONE, "%s", msg);
2942 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2943 btn0, GTK_RESPONSE_YES,
2944 btn1, GTK_RESPONSE_NO,
2945 NULL);
2946 /* TODO : what to use if nothing is specified ? */
2947 /* TODO : do we want the document name here too ? */
2948 if (title)
2949 gtk_window_set_title (GTK_WINDOW (dialog), title);
2950 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
2951 response = go_gtk_dialog_run (GTK_DIALOG (dialog),
2952 wbcg_toplevel (wbcg));
2953 return ((response == GTK_RESPONSE_NO || response == GTK_RESPONSE_CANCEL) ? res1 : res0);
2956 #define DISCONNECT(obj,field) \
2957 if (wbcg->field) { \
2958 if (obj) \
2959 g_signal_handler_disconnect (obj, wbcg->field); \
2960 wbcg->field = 0; \
2963 static void
2964 wbcg_view_changed (WBCGtk *wbcg,
2965 G_GNUC_UNUSED GParamSpec *pspec,
2966 Workbook *old_wb)
2968 WorkbookControl *wbc = GNM_WBC (wbcg);
2969 Workbook *wb = wb_control_get_workbook (wbc);
2970 WorkbookView *wbv = wb_control_view (wbc);
2972 /* Reconnect self because we need to change data. */
2973 DISCONNECT (wbc, sig_view_changed);
2974 wbcg->sig_view_changed =
2975 g_signal_connect_object
2976 (G_OBJECT (wbc),
2977 "notify::view",
2978 G_CALLBACK (wbcg_view_changed),
2982 DISCONNECT (wbcg->sig_wbv, sig_auto_expr_text);
2983 DISCONNECT (wbcg->sig_wbv, sig_auto_expr_attrs);
2984 DISCONNECT (wbcg->sig_wbv, sig_show_horizontal_scrollbar);
2985 DISCONNECT (wbcg->sig_wbv, sig_show_vertical_scrollbar);
2986 DISCONNECT (wbcg->sig_wbv, sig_show_notebook_tabs);
2987 if (wbcg->sig_wbv)
2988 g_object_remove_weak_pointer (wbcg->sig_wbv,
2989 &wbcg->sig_wbv);
2990 wbcg->sig_wbv = wbv;
2991 if (wbv) {
2992 g_object_add_weak_pointer (wbcg->sig_wbv,
2993 &wbcg->sig_wbv);
2994 wbcg->sig_auto_expr_text =
2995 g_signal_connect_object
2996 (G_OBJECT (wbv),
2997 "notify::auto-expr-value",
2998 G_CALLBACK (wbcg_auto_expr_value_changed),
2999 wbcg,
3001 wbcg_auto_expr_value_changed (wbv, NULL, wbcg);
3003 wbcg->sig_show_horizontal_scrollbar =
3004 g_signal_connect_object
3005 (G_OBJECT (wbv),
3006 "notify::show-horizontal-scrollbar",
3007 G_CALLBACK (wbcg_scrollbar_visibility),
3008 wbcg,
3010 wbcg->sig_show_vertical_scrollbar =
3011 g_signal_connect_object
3012 (G_OBJECT (wbv),
3013 "notify::show-vertical-scrollbar",
3014 G_CALLBACK (wbcg_scrollbar_visibility),
3015 wbcg,
3017 wbcg->sig_show_notebook_tabs =
3018 g_signal_connect_object
3019 (G_OBJECT (wbv),
3020 "notify::show-notebook-tabs",
3021 G_CALLBACK (wbcg_notebook_tabs_visibility),
3022 wbcg,
3024 wbcg_notebook_tabs_visibility (wbv, NULL, wbcg);
3027 DISCONNECT (old_wb, sig_sheet_order);
3028 DISCONNECT (old_wb, sig_notify_uri);
3029 DISCONNECT (old_wb, sig_notify_dirty);
3031 if (wb) {
3032 wbcg->sig_sheet_order =
3033 g_signal_connect_object
3034 (G_OBJECT (wb),
3035 "sheet-order-changed",
3036 G_CALLBACK (wbcg_sheet_order_changed),
3037 wbcg, G_CONNECT_SWAPPED);
3039 wbcg->sig_notify_uri =
3040 g_signal_connect_object
3041 (G_OBJECT (wb),
3042 "notify::uri",
3043 G_CALLBACK (wbcg_update_title),
3044 wbcg, G_CONNECT_SWAPPED);
3046 wbcg->sig_notify_dirty =
3047 g_signal_connect_object
3048 (G_OBJECT (wb),
3049 "notify::dirty",
3050 G_CALLBACK (wbcg_update_title),
3051 wbcg, G_CONNECT_SWAPPED);
3053 wbcg_update_title (wbcg);
3057 #undef DISCONNECT
3059 /***************************************************************************/
3061 static GOActionComboStack *
3062 ur_stack (WorkbookControl *wbc, gboolean is_undo)
3064 WBCGtk *wbcg = (WBCGtk *)wbc;
3065 return is_undo ? wbcg->undo_haction : wbcg->redo_haction;
3068 static void
3069 wbc_gtk_undo_redo_truncate (WorkbookControl *wbc, int n, gboolean is_undo)
3071 go_action_combo_stack_truncate (ur_stack (wbc, is_undo), n);
3074 static void
3075 wbc_gtk_undo_redo_pop (WorkbookControl *wbc, gboolean is_undo)
3077 go_action_combo_stack_pop (ur_stack (wbc, is_undo), 1);
3080 static void
3081 wbc_gtk_undo_redo_push (WorkbookControl *wbc, gboolean is_undo,
3082 char const *text, gpointer key)
3084 go_action_combo_stack_push (ur_stack (wbc, is_undo), text, key);
3087 /****************************************************************************/
3089 static void
3090 set_font_name_feedback (GtkAction *act, const char *family)
3092 PangoFontDescription *desc = pango_font_description_new ();
3093 pango_font_description_set_family (desc, family);
3094 wbcg_font_action_set_font_desc (act, desc);
3095 pango_font_description_free (desc);
3098 static void
3099 set_font_size_feedback (GtkAction *act, double size)
3101 PangoFontDescription *desc = pango_font_description_new ();
3102 pango_font_description_set_size (desc, size * PANGO_SCALE);
3103 wbcg_font_action_set_font_desc (act, desc);
3104 pango_font_description_free (desc);
3107 /****************************************************************************/
3109 static WorkbookControl *
3110 wbc_gtk_control_new (G_GNUC_UNUSED WorkbookControl *wbc,
3111 WorkbookView *wbv,
3112 Workbook *wb,
3113 gpointer extra)
3115 return (WorkbookControl *)wbc_gtk_new (wbv, wb,
3116 extra ? GDK_SCREEN (extra) : NULL, NULL);
3119 static void
3120 wbc_gtk_init_state (WorkbookControl *wbc)
3122 WorkbookView *wbv = wb_control_view (wbc);
3123 WBCGtk *wbcg = WBC_GTK (wbc);
3125 /* Share a colour history for all a view's controls */
3126 go_action_combo_color_set_group (wbcg->back_color, wbv);
3127 go_action_combo_color_set_group (wbcg->fore_color, wbv);
3130 static void
3131 wbc_gtk_style_feedback_real (WorkbookControl *wbc, GnmStyle const *changes)
3133 WorkbookView *wb_view = wb_control_view (wbc);
3134 WBCGtk *wbcg = (WBCGtk *)wbc;
3136 g_return_if_fail (wb_view != NULL);
3138 if (!wbcg_ui_update_begin (WBC_GTK (wbc)))
3139 return;
3141 if (changes == NULL)
3142 changes = wb_view->current_style;
3144 if (gnm_style_is_element_set (changes, MSTYLE_FONT_BOLD))
3145 gtk_toggle_action_set_active (wbcg->font.bold,
3146 gnm_style_get_font_bold (changes));
3147 if (gnm_style_is_element_set (changes, MSTYLE_FONT_ITALIC))
3148 gtk_toggle_action_set_active (wbcg->font.italic,
3149 gnm_style_get_font_italic (changes));
3150 if (gnm_style_is_element_set (changes, MSTYLE_FONT_UNDERLINE)) {
3151 gtk_toggle_action_set_active (wbcg->font.underline,
3152 gnm_style_get_font_uline (changes) == UNDERLINE_SINGLE);
3153 gtk_toggle_action_set_active (wbcg->font.d_underline,
3154 gnm_style_get_font_uline (changes) == UNDERLINE_DOUBLE);
3155 gtk_toggle_action_set_active (wbcg->font.sl_underline,
3156 gnm_style_get_font_uline (changes) == UNDERLINE_SINGLE_LOW);
3157 gtk_toggle_action_set_active (wbcg->font.dl_underline,
3158 gnm_style_get_font_uline (changes) == UNDERLINE_DOUBLE_LOW);
3160 if (gnm_style_is_element_set (changes, MSTYLE_FONT_STRIKETHROUGH))
3161 gtk_toggle_action_set_active (wbcg->font.strikethrough,
3162 gnm_style_get_font_strike (changes));
3164 if (gnm_style_is_element_set (changes, MSTYLE_FONT_SCRIPT)) {
3165 gtk_toggle_action_set_active (wbcg->font.superscript,
3166 gnm_style_get_font_script (changes) == GO_FONT_SCRIPT_SUPER);
3167 gtk_toggle_action_set_active (wbcg->font.subscript,
3168 gnm_style_get_font_script (changes) == GO_FONT_SCRIPT_SUB);
3169 } else {
3170 gtk_toggle_action_set_active (wbcg->font.superscript, FALSE);
3171 gtk_toggle_action_set_active (wbcg->font.subscript, FALSE);
3174 if (gnm_style_is_element_set (changes, MSTYLE_ALIGN_H)) {
3175 GnmHAlign align = gnm_style_get_align_h (changes);
3176 gtk_toggle_action_set_active (wbcg->h_align.left,
3177 align == GNM_HALIGN_LEFT);
3178 gtk_toggle_action_set_active (wbcg->h_align.center,
3179 align == GNM_HALIGN_CENTER);
3180 gtk_toggle_action_set_active (wbcg->h_align.right,
3181 align == GNM_HALIGN_RIGHT);
3182 gtk_toggle_action_set_active (wbcg->h_align.center_across_selection,
3183 align == GNM_HALIGN_CENTER_ACROSS_SELECTION);
3184 go_action_combo_pixmaps_select_id (wbcg->halignment, align);
3186 if (gnm_style_is_element_set (changes, MSTYLE_ALIGN_V)) {
3187 GnmVAlign align = gnm_style_get_align_v (changes);
3188 gtk_toggle_action_set_active (wbcg->v_align.top,
3189 align == GNM_VALIGN_TOP);
3190 gtk_toggle_action_set_active (wbcg->v_align.bottom,
3191 align == GNM_VALIGN_BOTTOM);
3192 gtk_toggle_action_set_active (wbcg->v_align.center,
3193 align == GNM_VALIGN_CENTER);
3194 go_action_combo_pixmaps_select_id (wbcg->valignment, align);
3197 if (gnm_style_is_element_set (changes, MSTYLE_FONT_SIZE)) {
3198 set_font_size_feedback (wbcg->font_name_haction,
3199 gnm_style_get_font_size (changes));
3200 set_font_size_feedback (wbcg->font_name_vaction,
3201 gnm_style_get_font_size (changes));
3204 if (gnm_style_is_element_set (changes, MSTYLE_FONT_NAME)) {
3205 set_font_name_feedback (wbcg->font_name_haction,
3206 gnm_style_get_font_name (changes));
3207 set_font_name_feedback (wbcg->font_name_vaction,
3208 gnm_style_get_font_name (changes));
3211 wbcg_ui_update_end (WBC_GTK (wbc));
3214 static gint
3215 cb_wbc_gtk_style_feedback (WBCGtk *gtk)
3217 wbc_gtk_style_feedback_real ((WorkbookControl *)gtk, NULL);
3218 gtk->idle_update_style_feedback = 0;
3219 return FALSE;
3221 static void
3222 wbc_gtk_style_feedback (WorkbookControl *wbc, GnmStyle const *changes)
3224 WBCGtk *wbcg = (WBCGtk *)wbc;
3226 if (changes)
3227 wbc_gtk_style_feedback_real (wbc, changes);
3228 else if (0 == wbcg->idle_update_style_feedback)
3229 wbcg->idle_update_style_feedback = g_timeout_add (200,
3230 (GSourceFunc) cb_wbc_gtk_style_feedback, wbc);
3233 static void
3234 cb_handlebox_dock_status (GtkHandleBox *hb,
3235 GtkToolbar *toolbar, gpointer pattached)
3237 gboolean attached = GPOINTER_TO_INT (pattached);
3238 gtk_toolbar_set_show_arrow (toolbar, attached);
3241 static char const *
3242 get_accel_label (GtkMenuItem *item, guint *key)
3244 GList *children = gtk_container_get_children (GTK_CONTAINER (item));
3245 GList *l;
3246 char const *res = NULL;
3248 *key = GDK_KEY_VoidSymbol;
3249 for (l = children; l; l = l->next) {
3250 GtkWidget *w = l->data;
3252 if (GTK_IS_ACCEL_LABEL (w)) {
3253 *key = gtk_label_get_mnemonic_keyval (GTK_LABEL (w));
3254 res = gtk_label_get_label (GTK_LABEL (w));
3255 break;
3259 g_list_free (children);
3260 return res;
3263 static void
3264 check_underlines (GtkWidget *w, char const *path)
3266 GList *children = gtk_container_get_children (GTK_CONTAINER (w));
3267 GHashTable *used = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
3268 GList *l;
3270 for (l = children; l; l = l->next) {
3271 GtkMenuItem *item = GTK_MENU_ITEM (l->data);
3272 GtkWidget *sub = gtk_menu_item_get_submenu (item);
3273 guint key;
3274 char const *label = get_accel_label (item, &key);
3276 if (sub) {
3277 char *newpath = g_strconcat (path, *path ? "->" : "", label, NULL);
3278 check_underlines (sub, newpath);
3279 g_free (newpath);
3282 if (key != GDK_KEY_VoidSymbol) {
3283 char const *prev = g_hash_table_lookup (used, GUINT_TO_POINTER (key));
3284 if (prev) {
3285 /* xgettext: Translators: if this warning shows up when
3286 * running Gnumeric in your locale, the underlines need
3287 * to be moved in strings representing menu entries.
3288 * One slightly tricky point here is that in certain cases,
3289 * the same menu entry shows up in more than one menu.
3291 g_warning (_("In the `%s' menu, the key `%s' is used for both `%s' and `%s'."),
3292 path, gdk_keyval_name (key), prev, label);
3293 } else
3294 g_hash_table_insert (used, GUINT_TO_POINTER (key), g_strdup (label));
3298 g_list_free (children);
3299 g_hash_table_destroy (used);
3302 /****************************************************************************/
3303 /* window list menu */
3305 static void
3306 cb_window_menu_activate (GObject *action, WBCGtk *wbcg)
3308 gtk_window_present (wbcg_toplevel (wbcg));
3311 static unsigned
3312 regenerate_window_menu (WBCGtk *gtk, Workbook *wb, unsigned i)
3314 int k, count;
3315 char *basename = GO_DOC (wb)->uri
3316 ? go_basename_from_uri (GO_DOC (wb)->uri)
3317 : NULL;
3319 /* How many controls are there? */
3320 count = 0;
3321 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc, {
3322 if (GNM_IS_WBC_GTK (wbc))
3323 count++;
3326 k = 1;
3327 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc, {
3328 if (i >= 20)
3329 return i;
3330 if (GNM_IS_WBC_GTK (wbc) && basename) {
3331 GString *label = g_string_new (NULL);
3332 char *name;
3333 char const *s;
3334 GtkActionEntry entry;
3336 if (i < 10) g_string_append_c (label, '_');
3337 g_string_append_printf (label, "%d ", i);
3339 for (s = basename; *s; s++) {
3340 if (*s == '_')
3341 g_string_append_c (label, '_');
3342 g_string_append_c (label, *s);
3345 if (count > 1)
3346 g_string_append_printf (label, " #%d", k++);
3348 entry.name = name = g_strdup_printf ("WindowListEntry%d", i);
3349 entry.stock_id = NULL;
3350 entry.label = label->str;
3351 entry.accelerator = NULL;
3352 entry.tooltip = NULL;
3353 entry.callback = G_CALLBACK (cb_window_menu_activate);
3355 gtk_action_group_add_actions (gtk->windows.actions,
3356 &entry, 1, wbc);
3358 g_string_free (label, TRUE);
3359 g_free (name);
3360 i++;
3361 }});
3362 g_free (basename);
3363 return i;
3366 static void
3367 cb_regenerate_window_menu (WBCGtk *gtk)
3369 Workbook *wb = wb_control_get_workbook (GNM_WBC (gtk));
3370 GList const *ptr;
3371 unsigned i;
3373 /* This can happen during exit. */
3374 if (!wb)
3375 return;
3377 if (gtk->windows.merge_id != 0)
3378 gtk_ui_manager_remove_ui (gtk->ui, gtk->windows.merge_id);
3379 gtk->windows.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
3381 if (gtk->windows.actions != NULL) {
3382 gtk_ui_manager_remove_action_group (gtk->ui,
3383 gtk->windows.actions);
3384 g_object_unref (gtk->windows.actions);
3386 gtk->windows.actions = gtk_action_group_new ("WindowList");
3388 gtk_ui_manager_insert_action_group (gtk->ui, gtk->windows.actions, 0);
3390 /* create the actions */
3391 i = regenerate_window_menu (gtk, wb, 1); /* current wb first */
3392 for (ptr = gnm_app_workbook_list (); ptr != NULL ; ptr = ptr->next)
3393 if (ptr->data != wb)
3394 i = regenerate_window_menu (gtk, ptr->data, i);
3396 /* merge them in */
3397 while (i-- > 1) {
3398 char *name = g_strdup_printf ("WindowListEntry%d", i);
3399 gtk_ui_manager_add_ui (gtk->ui, gtk->windows.merge_id,
3400 "/menubar/View/Windows", name, name,
3401 GTK_UI_MANAGER_AUTO, TRUE);
3402 g_free (name);
3406 typedef struct {
3407 GtkActionGroup *actions;
3408 guint merge_id;
3409 } CustomUIHandle;
3411 static void
3412 cb_custom_ui_handler (GObject *gtk_action, WorkbookControl *wbc)
3414 GnmAction *action = g_object_get_data (gtk_action, "GnmAction");
3415 GnmAppExtraUI *extra_ui = g_object_get_data (gtk_action, "ExtraUI");
3417 g_return_if_fail (action != NULL);
3418 g_return_if_fail (action->handler != NULL);
3419 g_return_if_fail (extra_ui != NULL);
3421 action->handler (action, wbc, extra_ui->user_data);
3424 static void
3425 cb_add_custom_ui (G_GNUC_UNUSED GnmApp *app,
3426 GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3428 CustomUIHandle *details;
3429 GSList *ptr;
3430 GError *error = NULL;
3431 const char *ui_substr;
3433 details = g_new0 (CustomUIHandle, 1);
3434 details->actions = gtk_action_group_new (extra_ui->group_name);
3436 for (ptr = extra_ui->actions; ptr != NULL ; ptr = ptr->next) {
3437 GnmAction *action = ptr->data;
3438 GtkAction *res;
3439 GtkActionEntry entry;
3441 entry.name = action->id;
3442 entry.stock_id = action->icon_name;
3443 entry.label = action->label;
3444 entry.accelerator = NULL;
3445 entry.tooltip = NULL;
3446 entry.callback = G_CALLBACK (cb_custom_ui_handler);
3447 gtk_action_group_add_actions (details->actions, &entry, 1, gtk);
3448 res = gtk_action_group_get_action (details->actions, action->id);
3449 g_object_set_data (G_OBJECT (res), "GnmAction", action);
3450 g_object_set_data (G_OBJECT (res), "ExtraUI", extra_ui);
3452 gtk_ui_manager_insert_action_group (gtk->ui, details->actions, 0);
3454 ui_substr = strstr (extra_ui->layout, "<ui>");
3455 if (ui_substr == extra_ui->layout)
3456 ui_substr = NULL;
3458 details->merge_id = gtk_ui_manager_add_ui_from_string
3459 (gtk->ui, extra_ui->layout, -1, ui_substr ? NULL : &error);
3460 if (details->merge_id == 0 && ui_substr) {
3461 /* Work around bug 569724. */
3462 details->merge_id = gtk_ui_manager_add_ui_from_string
3463 (gtk->ui, ui_substr, -1, &error);
3466 if (error) {
3467 g_message ("building menus failed: %s", error->message);
3468 g_error_free (error);
3469 gtk_ui_manager_remove_action_group (gtk->ui, details->actions);
3470 g_object_unref (details->actions);
3471 g_free (details);
3472 } else {
3473 g_hash_table_insert (gtk->custom_uis, extra_ui, details);
3476 static void
3477 cb_remove_custom_ui (G_GNUC_UNUSED GnmApp *app,
3478 GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3480 CustomUIHandle *details = g_hash_table_lookup (gtk->custom_uis, extra_ui);
3481 if (NULL != details) {
3482 gtk_ui_manager_remove_ui (gtk->ui, details->merge_id);
3483 gtk_ui_manager_remove_action_group (gtk->ui, details->actions);
3484 g_object_unref (details->actions);
3485 g_hash_table_remove (gtk->custom_uis, extra_ui);
3489 static void
3490 cb_init_extra_ui (GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3492 cb_add_custom_ui (NULL, extra_ui, gtk);
3495 /****************************************************************************/
3496 /* Toolbar menu */
3498 static void
3499 set_toolbar_style_for_position (GtkToolbar *tb, GtkPositionType pos)
3501 GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3503 static const GtkOrientation orientations[] = {
3504 GTK_ORIENTATION_VERTICAL, GTK_ORIENTATION_VERTICAL,
3505 GTK_ORIENTATION_HORIZONTAL, GTK_ORIENTATION_HORIZONTAL
3508 gtk_orientable_set_orientation (GTK_ORIENTABLE (tb),
3509 orientations[pos]);
3511 if (GTK_IS_HANDLE_BOX (box)) {
3512 static const GtkPositionType hdlpos[] = {
3513 GTK_POS_TOP, GTK_POS_TOP,
3514 GTK_POS_LEFT, GTK_POS_LEFT
3517 gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (box),
3518 hdlpos[pos]);
3520 if (pos == GTK_POS_TOP || pos == GTK_POS_BOTTOM)
3521 g_object_set (G_OBJECT (tb), "hexpand", TRUE, "vexpand", FALSE, NULL);
3522 else
3523 g_object_set (G_OBJECT (tb), "vexpand", TRUE, "hexpand", FALSE, NULL);
3526 static void
3527 set_toolbar_position (GtkToolbar *tb, GtkPositionType pos, WBCGtk *gtk)
3529 GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3530 GtkContainer *zone = GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (box)));
3531 GtkContainer *new_zone = GTK_CONTAINER (gtk->toolbar_zones[pos]);
3532 char const *name = g_object_get_data (G_OBJECT (box), "name");
3533 const char *key = "toolbar-order";
3534 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), key));
3535 GList *children, *l;
3536 int cpos = 0;
3538 if (zone == new_zone)
3539 return;
3541 g_object_ref (box);
3542 if (zone)
3543 gtk_container_remove (zone, box);
3544 set_toolbar_style_for_position (tb, pos);
3546 children = gtk_container_get_children (new_zone);
3547 for (l = children; l; l = l->next) {
3548 GObject *child = l->data;
3549 int nc = GPOINTER_TO_INT (g_object_get_data (child, key));
3550 if (nc < n) cpos++;
3552 g_list_free (children);
3554 gtk_container_add (new_zone, box);
3555 gtk_container_child_set (new_zone, box, "position", cpos, NULL);
3557 g_object_unref (box);
3559 if (zone && name)
3560 gnm_conf_set_toolbar_position (name, pos);
3563 static void
3564 cb_set_toolbar_position (GtkMenuItem *item, WBCGtk *gtk)
3566 GtkToolbar *tb = g_object_get_data (G_OBJECT (item), "toolbar");
3567 GtkPositionType side = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "side"));
3569 if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
3570 set_toolbar_position (tb, side, gtk);
3573 static void
3574 cb_tcm_hide (GtkWidget *widget, GtkWidget *box)
3576 gtk_widget_hide (box);
3579 static void
3580 toolbar_context_menu (GtkToolbar *tb, WBCGtk *gtk, GdkEvent *event)
3582 GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3583 GtkWidget *zone = gtk_widget_get_parent (GTK_WIDGET (box));
3584 GtkWidget *menu = gtk_menu_new ();
3585 GtkWidget *item;
3586 GSList *group = NULL;
3587 size_t ui;
3589 static const struct {
3590 char const *text;
3591 GtkPositionType pos;
3592 } const pos_items[] = {
3593 { N_("Display toolbar above sheets"), GTK_POS_TOP },
3594 { N_("Display toolbar to the left of sheets"), GTK_POS_LEFT },
3595 { N_("Display toolbar to the right of sheets"), GTK_POS_RIGHT }
3598 if (gnm_debug_flag ("toolbar-size"))
3599 dump_size_tree (GTK_WIDGET (tb), GINT_TO_POINTER (0));
3601 for (ui = 0; ui < G_N_ELEMENTS (pos_items); ui++) {
3602 char const *text = _(pos_items[ui].text);
3603 GtkPositionType pos = pos_items[ui].pos;
3605 item = gtk_radio_menu_item_new_with_label (group, text);
3606 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
3608 gtk_check_menu_item_set_active
3609 (GTK_CHECK_MENU_ITEM (item),
3610 (zone == gtk->toolbar_zones[pos]));
3612 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3613 g_object_set_data (G_OBJECT (item), "toolbar", tb);
3614 g_object_set_data (G_OBJECT (item), "side", GINT_TO_POINTER (pos));
3615 g_signal_connect (G_OBJECT (item), "activate",
3616 G_CALLBACK (cb_set_toolbar_position),
3617 gtk);
3620 item = gtk_separator_menu_item_new ();
3621 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3623 item = gtk_menu_item_new_with_label (_("Hide"));
3624 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3625 g_signal_connect (G_OBJECT (item), "activate",
3626 G_CALLBACK (cb_tcm_hide),
3627 box);
3629 gtk_widget_show_all (menu);
3630 gnumeric_popup_menu (GTK_MENU (menu), event);
3633 static gboolean
3634 cb_toolbar_button_press (GtkToolbar *tb, GdkEvent *event, WBCGtk *gtk)
3636 if (event->type == GDK_BUTTON_PRESS &&
3637 event->button.button == 3) {
3638 toolbar_context_menu (tb, gtk, event);
3639 return TRUE;
3642 return FALSE;
3645 static gboolean
3646 cb_handlebox_button_press (GtkHandleBox *hdlbox, GdkEvent *event, WBCGtk *gtk)
3648 if (event->type == GDK_BUTTON_PRESS &&
3649 event->button.button == 3) {
3650 GtkToolbar *tb = GTK_TOOLBAR (gtk_bin_get_child (GTK_BIN (hdlbox)));
3651 toolbar_context_menu (tb, gtk, event);
3652 return TRUE;
3655 return FALSE;
3659 static void
3660 cb_toolbar_activate (GtkToggleAction *action, WBCGtk *wbcg)
3662 wbcg_toggle_visibility (wbcg, action);
3665 static void
3666 cb_toolbar_box_visible (GtkWidget *box, G_GNUC_UNUSED GParamSpec *pspec,
3667 WBCGtk *wbcg)
3669 GtkToggleAction *toggle_action = g_object_get_data (
3670 G_OBJECT (box), "toggle_action");
3671 char const *name = g_object_get_data (G_OBJECT (box), "name");
3672 gboolean visible = gtk_widget_get_visible (box);
3674 gtk_toggle_action_set_active (toggle_action, visible);
3675 if (!wbcg->is_fullscreen) {
3677 * We do not persist changes made going-to/while-in/leaving
3678 * fullscreen mode.
3680 gnm_conf_set_toolbar_visible (name, visible);
3684 static struct ToolbarInfo {
3685 const char *name;
3686 const char *menu_text;
3687 const char *accel;
3688 } toolbar_info[] = {
3689 { "StandardToolbar", N_("Standard Toolbar"), "<control>7" },
3690 { "FormatToolbar", N_("Format Toolbar"), NULL },
3691 { "ObjectToolbar", N_("Object Toolbar"), NULL },
3692 { NULL, NULL, NULL }
3696 static void
3697 cb_add_menus_toolbars (G_GNUC_UNUSED GtkUIManager *ui,
3698 GtkWidget *w, WBCGtk *gtk)
3700 if (GTK_IS_TOOLBAR (w)) {
3701 WBCGtk *wbcg = (WBCGtk *)gtk;
3702 char const *name = gtk_widget_get_name (w);
3703 GtkToggleActionEntry entry;
3704 char *toggle_name = g_strconcat ("ViewMenuToolbar", name, NULL);
3705 char *tooltip = g_strdup_printf (_("Show/Hide toolbar %s"), _(name));
3706 gboolean visible = gnm_conf_get_toolbar_visible (name);
3707 int n = g_hash_table_size (wbcg->visibility_widgets);
3708 GtkWidget *vw;
3709 const struct ToolbarInfo *ti;
3710 GtkWidget *box;
3711 GtkPositionType pos = gnm_conf_get_toolbar_position (name);
3713 // See bug 761142. This isn't supposed to be necessary.
3714 gtk_style_context_invalidate (gtk_widget_get_style_context (w));
3716 if (gnm_conf_get_detachable_toolbars ()) {
3717 box = gtk_handle_box_new ();
3718 g_object_connect (box,
3719 "signal::child_attached", G_CALLBACK (cb_handlebox_dock_status), GINT_TO_POINTER (TRUE),
3720 "signal::child_detached", G_CALLBACK (cb_handlebox_dock_status), GINT_TO_POINTER (FALSE),
3721 NULL);
3722 } else
3723 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
3724 g_signal_connect (G_OBJECT (w),
3725 "button_press_event",
3726 G_CALLBACK (cb_toolbar_button_press),
3727 gtk);
3728 g_signal_connect (G_OBJECT (box),
3729 "button_press_event",
3730 G_CALLBACK (cb_handlebox_button_press),
3731 gtk);
3733 gtk_container_add (GTK_CONTAINER (box), w);
3734 gtk_widget_show_all (box);
3735 if (!visible)
3736 gtk_widget_hide (box);
3737 g_object_set_data (G_OBJECT (box), "toolbar-order",
3738 GINT_TO_POINTER (n));
3739 set_toolbar_position (GTK_TOOLBAR (w), pos, gtk);
3741 g_signal_connect (box,
3742 "notify::visible",
3743 G_CALLBACK (cb_toolbar_box_visible),
3744 gtk);
3745 g_object_set_data_full (G_OBJECT (box), "name",
3746 g_strdup (name),
3747 (GDestroyNotify)g_free);
3749 vw = box;
3750 g_hash_table_insert (wbcg->visibility_widgets,
3751 g_strdup (toggle_name),
3752 g_object_ref (vw));
3754 gtk_toolbar_set_show_arrow (GTK_TOOLBAR (w), TRUE);
3755 gtk_toolbar_set_style (GTK_TOOLBAR (w), GTK_TOOLBAR_ICONS);
3756 gtk_toolbar_set_icon_size (GTK_TOOLBAR (w), GTK_ICON_SIZE_SMALL_TOOLBAR);
3758 entry.name = toggle_name;
3759 entry.stock_id = NULL;
3760 entry.label = name;
3761 entry.accelerator = NULL;
3762 entry.tooltip = tooltip;
3763 entry.callback = G_CALLBACK (cb_toolbar_activate);
3764 entry.is_active = visible;
3766 for (ti = toolbar_info; ti->name; ti++) {
3767 if (strcmp (name, ti->name) == 0) {
3768 entry.label = _(ti->menu_text);
3769 entry.accelerator = ti->accel;
3770 break;
3774 gtk_action_group_add_toggle_actions (gtk->toolbar.actions,
3775 &entry, 1, wbcg);
3776 g_object_set_data (G_OBJECT (box), "toggle_action",
3777 gtk_action_group_get_action (gtk->toolbar.actions, toggle_name));
3778 gtk_ui_manager_add_ui (gtk->ui, gtk->toolbar.merge_id,
3779 "/menubar/View/Toolbars", toggle_name, toggle_name,
3780 GTK_UI_MANAGER_AUTO, FALSE);
3781 wbcg->hide_for_fullscreen =
3782 g_slist_prepend (wbcg->hide_for_fullscreen,
3783 gtk_action_group_get_action (gtk->toolbar.actions,
3784 toggle_name));
3786 g_free (tooltip);
3787 g_free (toggle_name);
3788 } else {
3789 gtk_box_pack_start (GTK_BOX (gtk->menu_zone), w, FALSE, TRUE, 0);
3790 gtk_widget_show_all (w);
3794 static void
3795 cb_clear_menu_tip (GOCmdContext *cc)
3797 go_cmd_context_progress_message_set (cc, " ");
3800 static void
3801 cb_show_menu_tip (GtkWidget *proxy, GOCmdContext *cc)
3803 GtkAction *action = g_object_get_data (G_OBJECT (proxy), "GtkAction");
3804 char *tip = NULL;
3805 g_object_get (action, "tooltip", &tip, NULL);
3806 if (tip) {
3807 go_cmd_context_progress_message_set (cc, _(tip));
3808 g_free (tip);
3809 } else
3810 cb_clear_menu_tip (cc);
3813 static void
3814 cb_connect_proxy (G_GNUC_UNUSED GtkUIManager *ui,
3815 GtkAction *action,
3816 GtkWidget *proxy,
3817 GOCmdContext *cc)
3819 /* connect whether there is a tip or not it may change later */
3820 if (GTK_IS_MENU_ITEM (proxy)) {
3821 g_object_set_data (G_OBJECT (proxy), "GtkAction", action);
3822 g_object_connect (proxy,
3823 "signal::select", G_CALLBACK (cb_show_menu_tip), cc,
3824 "swapped_signal::deselect", G_CALLBACK (cb_clear_menu_tip), cc,
3825 NULL);
3829 static void
3830 cb_disconnect_proxy (G_GNUC_UNUSED GtkUIManager *ui,
3831 G_GNUC_UNUSED GtkAction *action,
3832 GtkWidget *proxy,
3833 GOCmdContext *cc)
3835 if (GTK_IS_MENU_ITEM (proxy)) {
3836 g_object_set_data (G_OBJECT (proxy), "GtkAction", NULL);
3837 g_object_disconnect (proxy,
3838 "any_signal::select", G_CALLBACK (cb_show_menu_tip), cc,
3839 "any_signal::deselect", G_CALLBACK (cb_clear_menu_tip), cc,
3840 NULL);
3844 static void
3845 cb_post_activate (G_GNUC_UNUSED GtkUIManager *manager, GtkAction *action, WBCGtk *wbcg)
3847 if (!wbcg_is_editing (wbcg) && strcmp(gtk_action_get_name (action), "EditGotoCellIndicator") != 0)
3848 wbcg_focus_cur_scg (wbcg);
3851 static void
3852 cb_wbcg_window_state_event (GtkWidget *widget,
3853 GdkEventWindowState *event,
3854 WBCGtk *wbcg)
3856 gboolean new_val = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0;
3857 if (!(event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) ||
3858 new_val == wbcg->is_fullscreen ||
3859 wbcg->updating_ui)
3860 return;
3862 wbc_gtk_set_toggle_action_state (wbcg, "ViewFullScreen", new_val);
3864 if (new_val) {
3865 GSList *l;
3867 wbcg->is_fullscreen = TRUE;
3868 for (l = wbcg->hide_for_fullscreen; l; l = l->next) {
3869 GtkToggleAction *ta = l->data;
3870 GOUndo *u;
3871 gboolean active = gtk_toggle_action_get_active (ta);
3872 u = go_undo_binary_new
3873 (ta, GUINT_TO_POINTER (active),
3874 (GOUndoBinaryFunc)gtk_toggle_action_set_active,
3875 NULL, NULL);
3876 wbcg->undo_for_fullscreen =
3877 go_undo_combine (wbcg->undo_for_fullscreen, u);
3878 gtk_toggle_action_set_active (ta, FALSE);
3880 } else {
3881 if (wbcg->undo_for_fullscreen) {
3882 go_undo_undo (wbcg->undo_for_fullscreen);
3883 g_object_unref (wbcg->undo_for_fullscreen);
3884 wbcg->undo_for_fullscreen = NULL;
3886 wbcg->is_fullscreen = FALSE;
3890 /****************************************************************************/
3892 static void
3893 cb_auto_expr_cell_changed (GtkWidget *item, WBCGtk *wbcg)
3895 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3896 const GnmEvalPos *ep;
3897 GnmExprTop const *texpr;
3898 GnmValue const *v;
3900 if (wbcg->updating_ui)
3901 return;
3903 ep = g_object_get_data (G_OBJECT (item), "evalpos");
3905 g_object_set (wbv,
3906 "auto-expr-func", NULL,
3907 "auto-expr-descr", NULL,
3908 "auto-expr-eval-pos", ep,
3909 NULL);
3911 /* Now we have the expression set. */
3912 texpr = wbv->auto_expr.dep.texpr;
3913 v = gnm_expr_top_get_constant (texpr);
3914 if (v)
3915 g_object_set (wbv,
3916 "auto-expr-descr", value_peek_string (v),
3917 NULL);
3920 static void
3921 cb_auto_expr_changed (GtkWidget *item, WBCGtk *wbcg)
3923 const GnmFunc *func;
3924 const char *descr;
3925 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3927 if (wbcg->updating_ui)
3928 return;
3930 func = g_object_get_data (G_OBJECT (item), "func");
3931 descr = g_object_get_data (G_OBJECT (item), "descr");
3933 g_object_set (wbv,
3934 "auto-expr-func", func,
3935 "auto-expr-descr", descr,
3936 "auto-expr-eval-pos", NULL,
3937 NULL);
3940 static void
3941 cb_auto_expr_precision_toggled (GtkWidget *item, WBCGtk *wbcg)
3943 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3944 if (wbcg->updating_ui)
3945 return;
3947 go_object_toggle (wbv, "auto-expr-max-precision");
3950 static void
3951 cb_auto_expr_insert_formula (WBCGtk *wbcg, gboolean below)
3953 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
3954 GnmRange const *selection = selection_first_range (scg_view (scg), NULL, NULL);
3955 GnmRange output;
3956 GnmRange *input;
3957 gboolean multiple, use_last_cr;
3958 data_analysis_output_t *dao;
3959 analysis_tools_data_auto_expression_t *specs;
3961 g_return_if_fail (selection != NULL);
3963 if (below) {
3964 multiple = (range_width (selection) > 1);
3965 output = *selection;
3966 range_normalize (&output);
3967 output.start.row = output.end.row;
3968 use_last_cr = (range_height (selection) > 1) && sheet_is_region_empty (scg_sheet (scg), &output);
3969 if (!use_last_cr) {
3970 if (range_translate (&output, scg_sheet (scg), 0, 1))
3971 return;
3972 if (multiple && gnm_sheet_get_last_col (scg_sheet (scg)) > output.end.col)
3973 output.end.col++;
3975 input = gnm_range_dup (selection);
3976 range_normalize (input);
3977 if (use_last_cr)
3978 input->end.row--;
3979 } else {
3980 multiple = (range_height (selection) > 1);
3981 output = *selection;
3982 range_normalize (&output);
3983 output.start.col = output.end.col;
3984 use_last_cr = (range_width (selection) > 1) && sheet_is_region_empty (scg_sheet (scg), &output);
3985 if (!use_last_cr) {
3986 if (range_translate (&output, scg_sheet (scg), 1, 0))
3987 return;
3988 if (multiple && gnm_sheet_get_last_row (scg_sheet (scg)) > output.end.row)
3989 output.end.row++;
3991 input = gnm_range_dup (selection);
3992 range_normalize (input);
3993 if (use_last_cr)
3994 input->end.col--;
3998 dao = dao_init (NULL, RangeOutput);
3999 dao->start_col = output.start.col;
4000 dao->start_row = output.start.row;
4001 dao->cols = range_width (&output);
4002 dao->rows = range_height (&output);
4003 dao->sheet = scg_sheet (scg);
4004 dao->autofit_flag = FALSE;
4005 dao->put_formulas = TRUE;
4007 specs = g_new0 (analysis_tools_data_auto_expression_t, 1);
4008 specs->base.wbc = GNM_WBC (wbcg);
4009 specs->base.input = g_slist_prepend (NULL, value_new_cellrange_r (scg_sheet (scg), input));
4010 g_free (input);
4011 specs->base.group_by = below ? GROUPED_BY_COL : GROUPED_BY_ROW;
4012 specs->base.labels = FALSE;
4013 specs->multiple = multiple;
4014 specs->below = below;
4015 specs->func = NULL;
4016 g_object_get (G_OBJECT (wb_control_view (GNM_WBC (wbcg))),
4017 "auto-expr-func", &(specs->func), NULL);
4018 if (specs->func == NULL) {
4019 specs->func = gnm_func_lookup_or_add_placeholder ("sum");
4020 gnm_func_ref (specs->func);
4023 cmd_analysis_tool (GNM_WBC (wbcg), scg_sheet (scg),
4024 dao, specs, analysis_tool_auto_expression_engine,
4025 TRUE);
4028 static void
4029 cb_auto_expr_insert_formula_below (G_GNUC_UNUSED GtkWidget *item, WBCGtk *wbcg)
4031 cb_auto_expr_insert_formula (wbcg, TRUE);
4034 static void
4035 cb_auto_expr_insert_formula_to_side (G_GNUC_UNUSED GtkWidget *item, WBCGtk *wbcg)
4037 cb_auto_expr_insert_formula (wbcg, FALSE);
4041 static gboolean
4042 cb_select_auto_expr (GtkWidget *widget, GdkEvent *event, WBCGtk *wbcg)
4045 * WARNING * WARNING * WARNING
4047 * Keep the functions in lower case.
4048 * We currently register the functions in lower case and some locales
4049 * (notably tr_TR) do not have the same encoding for tolower that
4050 * locale C does.
4052 * eg tolower ('I') != 'i'
4053 * Which would break function lookup when looking up for function 'selectIon'
4054 * when it was registered as 'selection'
4056 * WARNING * WARNING * WARNING
4058 static struct {
4059 char const * const displayed_name;
4060 char const * const function;
4061 } const quick_compute_routines [] = {
4062 { N_("Sum"), "sum" },
4063 { N_("Min"), "min" },
4064 { N_("Max"), "max" },
4065 { N_("Average"), "average" },
4066 { N_("Count"), "count" },
4067 { NULL, NULL }
4070 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
4071 Sheet *sheet = wb_view_cur_sheet (wbv);
4072 GtkWidget *item, *menu;
4073 int i;
4074 char *cell_item;
4075 GnmCellPos const *pos;
4076 GnmEvalPos ep;
4078 if (event->button.button != 3)
4079 return FALSE;
4081 menu = gtk_menu_new ();
4083 for (i = 0; quick_compute_routines[i].displayed_name; i++) {
4084 GnmParsePos pp;
4085 char const *fname = quick_compute_routines[i].function;
4086 char const *dispname =
4087 _(quick_compute_routines[i].displayed_name);
4088 GnmExprTop const *new_auto_expr;
4089 GtkWidget *item;
4090 char *expr_txt;
4092 /* Test the expression... */
4093 parse_pos_init (&pp, sheet->workbook, sheet, 0, 0);
4094 expr_txt = g_strconcat (fname, "(",
4095 parsepos_as_string (&pp),
4096 ")", NULL);
4097 new_auto_expr = gnm_expr_parse_str
4098 (expr_txt, &pp, GNM_EXPR_PARSE_DEFAULT,
4099 sheet_get_conventions (sheet), NULL);
4100 g_free (expr_txt);
4101 if (!new_auto_expr)
4102 continue;
4103 gnm_expr_top_unref (new_auto_expr);
4105 item = gtk_menu_item_new_with_label (dispname);
4106 g_object_set_data (G_OBJECT (item),
4107 "func", gnm_func_lookup (fname, NULL));
4108 g_object_set_data (G_OBJECT (item),
4109 "descr", (gpointer)dispname);
4110 g_signal_connect (G_OBJECT (item),
4111 "activate",
4112 G_CALLBACK (cb_auto_expr_changed), wbcg);
4113 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4114 gtk_widget_show (item);
4117 item = gtk_separator_menu_item_new ();
4118 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4119 gtk_widget_show (item);
4121 pos = &(scg_view (wbcg_cur_scg (wbcg)))->edit_pos;
4122 eval_pos_init_pos (&ep, sheet, pos);
4123 cell_item = g_strdup_printf (_("Content of %s"), cellpos_as_string (pos));
4124 item = gtk_menu_item_new_with_label (cell_item);
4125 g_free (cell_item);
4126 g_object_set_data_full (G_OBJECT (item),
4127 "evalpos", g_memdup (&ep, sizeof (ep)),
4128 (GDestroyNotify)g_free);
4129 g_signal_connect (G_OBJECT (item), "activate",
4130 G_CALLBACK (cb_auto_expr_cell_changed), wbcg);
4131 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4132 gtk_widget_show (item);
4134 item = gtk_separator_menu_item_new ();
4135 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4136 gtk_widget_show (item);
4138 item = gtk_check_menu_item_new_with_label (_("Use Maximum Precision"));
4139 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
4140 wbv->auto_expr.use_max_precision);
4141 g_signal_connect (G_OBJECT (item), "activate",
4142 G_CALLBACK (cb_auto_expr_precision_toggled), wbcg);
4143 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4144 gtk_widget_show (item);
4146 item = gtk_separator_menu_item_new ();
4147 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4148 gtk_widget_show (item);
4150 item = gtk_menu_item_new_with_label (_("Insert Formula Below"));
4151 g_signal_connect (G_OBJECT (item), "activate",
4152 G_CALLBACK (cb_auto_expr_insert_formula_below), wbcg);
4153 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4154 gtk_widget_show (item);
4156 item = gtk_menu_item_new_with_label (_("Insert Formula to Side"));
4157 g_signal_connect (G_OBJECT (item), "activate",
4158 G_CALLBACK (cb_auto_expr_insert_formula_to_side), wbcg);
4159 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4160 gtk_widget_show (item);
4162 gnumeric_popup_menu (GTK_MENU (menu), event);
4163 return TRUE;
4166 static void
4167 wbc_gtk_create_status_area (WBCGtk *wbcg)
4169 GtkWidget *ebox;
4171 g_object_ref (wbcg->auto_expr_label);
4172 gtk_label_set_max_width_chars (GTK_LABEL (wbcg->auto_expr_label),
4173 strlen (AUTO_EXPR_SAMPLE));
4174 gtk_widget_set_size_request
4175 (wbcg->auto_expr_label,
4176 gnm_widget_measure_string (GTK_WIDGET (wbcg->toplevel),
4177 AUTO_EXPR_SAMPLE),
4178 -1);
4180 gtk_widget_set_size_request
4181 (wbcg->status_text,
4182 gnm_widget_measure_string (GTK_WIDGET (wbcg->toplevel),
4183 "W") * 5,
4184 -1);
4185 ebox = GET_GUI_ITEM ("auto_expr_event_box");
4186 gtk_style_context_add_class (gtk_widget_get_style_context (ebox),
4187 "auto-expr");
4188 g_signal_connect (G_OBJECT (ebox),
4189 "button_press_event",
4190 G_CALLBACK (cb_select_auto_expr), wbcg);
4192 g_hash_table_insert (wbcg->visibility_widgets,
4193 g_strdup ("ViewStatusbar"),
4194 g_object_ref (wbcg->status_area));
4196 /* disable statusbar by default going to fullscreen */
4197 wbcg->hide_for_fullscreen =
4198 g_slist_prepend (wbcg->hide_for_fullscreen,
4199 wbcg_find_action (wbcg, "ViewStatusbar"));
4200 g_assert (wbcg->hide_for_fullscreen->data);
4203 /****************************************************************************/
4205 static void
4206 cb_file_history_activate (GObject *action, WBCGtk *wbcg)
4208 gui_file_read (wbcg, g_object_get_data (action, "uri"), NULL, NULL);
4211 static void
4212 wbc_gtk_reload_recent_file_menu (WBCGtk *wbcg)
4214 WBCGtk *gtk = (WBCGtk *)wbcg;
4215 GSList *history, *ptr;
4216 unsigned i;
4217 gboolean any_history;
4218 GtkAction *full_history;
4220 if (gtk->file_history.merge_id != 0)
4221 gtk_ui_manager_remove_ui (gtk->ui, gtk->file_history.merge_id);
4222 gtk->file_history.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
4224 if (gtk->file_history.actions != NULL) {
4225 gtk_ui_manager_remove_action_group (gtk->ui,
4226 gtk->file_history.actions);
4227 g_object_unref (gtk->file_history.actions);
4229 gtk->file_history.actions = gtk_action_group_new ("FileHistory");
4231 /* create the actions */
4232 history = gnm_app_history_get_list (3);
4233 any_history = (history != NULL);
4234 for (i = 1, ptr = history; ptr != NULL ; ptr = ptr->next, i++) {
4235 GtkActionEntry entry;
4236 GtkAction *action;
4237 char const *uri = ptr->data;
4238 char *name = g_strdup_printf ("FileHistoryEntry%d", i);
4239 char *label = gnm_history_item_label (uri, i);
4240 char *filename = go_filename_from_uri (uri);
4241 char *filename_utf8 = filename ? g_filename_to_utf8 (filename, -1, NULL, NULL, NULL) : NULL;
4242 char *tooltip = g_strdup_printf (_("Open %s"), filename_utf8 ? filename_utf8 : uri);
4244 entry.name = name;
4245 entry.stock_id = NULL;
4246 entry.label = label;
4247 entry.accelerator = NULL;
4248 entry.tooltip = tooltip;
4249 entry.callback = G_CALLBACK (cb_file_history_activate);
4250 gtk_action_group_add_actions (gtk->file_history.actions,
4251 &entry, 1, (WBCGtk *)wbcg);
4252 action = gtk_action_group_get_action (gtk->file_history.actions,
4253 name);
4254 g_object_set_data_full (G_OBJECT (action), "uri",
4255 g_strdup (uri), (GDestroyNotify)g_free);
4257 g_free (name);
4258 g_free (label);
4259 g_free (filename);
4260 g_free (filename_utf8);
4261 g_free (tooltip);
4263 g_slist_free_full (history, (GDestroyNotify)g_free);
4265 gtk_ui_manager_insert_action_group (gtk->ui, gtk->file_history.actions, 0);
4267 /* merge them in */
4268 while (i-- > 1) {
4269 char *name = g_strdup_printf ("FileHistoryEntry%d", i);
4270 gtk_ui_manager_add_ui (gtk->ui, gtk->file_history.merge_id,
4271 "/menubar/File/FileHistory", name, name,
4272 GTK_UI_MANAGER_AUTO, TRUE);
4273 g_free (name);
4276 full_history = wbcg_find_action (wbcg, "FileHistoryFull");
4277 g_object_set (G_OBJECT (full_history), "sensitive", any_history, NULL);
4280 static void
4281 cb_new_from_template (GObject *action, WBCGtk *wbcg)
4283 const char *uri = g_object_get_data (action, "uri");
4284 gnm_gui_file_template (wbcg, uri);
4287 static void
4288 add_template_dir (const char *path, GHashTable *h)
4290 GDir *dir;
4291 const char *name;
4293 dir = g_dir_open (path, 0, NULL);
4294 if (!dir)
4295 return;
4297 while ((name = g_dir_read_name (dir))) {
4298 char *fullname = g_build_filename (path, name, NULL);
4301 * Unconditionally remove, so we can link to /dev/null
4302 * and cause a system file to be hidden.
4304 g_hash_table_remove (h, name);
4306 if (g_file_test (fullname, G_FILE_TEST_IS_REGULAR)) {
4307 char *uri = go_filename_to_uri (fullname);
4308 g_hash_table_insert (h, g_strdup (name), uri);
4310 g_free (fullname);
4312 g_dir_close (dir);
4315 static void
4316 wbc_gtk_reload_templates (WBCGtk *gtk)
4318 unsigned i;
4319 GSList *l, *names;
4320 char *path;
4321 GHashTable *h;
4323 if (gtk->templates.merge_id != 0)
4324 gtk_ui_manager_remove_ui (gtk->ui, gtk->templates.merge_id);
4325 gtk->templates.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
4327 if (gtk->templates.actions != NULL) {
4328 gtk_ui_manager_remove_action_group (gtk->ui,
4329 gtk->templates.actions);
4330 g_object_unref (gtk->templates.actions);
4332 gtk->templates.actions = gtk_action_group_new ("TemplateList");
4334 gtk_ui_manager_insert_action_group (gtk->ui, gtk->templates.actions, 0);
4336 h = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
4338 path = g_build_filename (gnm_sys_data_dir (), "templates", NULL);
4339 add_template_dir (path, h);
4340 g_free (path);
4342 /* Possibly override the above with user templates without version. */
4343 path = g_build_filename (gnm_usr_dir (FALSE), "templates", NULL);
4344 add_template_dir (path, h);
4345 g_free (path);
4347 /* Possibly override the above with user templates with version. */
4348 path = g_build_filename (gnm_usr_dir (TRUE), "templates", NULL);
4349 add_template_dir (path, h);
4350 g_free (path);
4352 names = g_slist_sort (go_hash_keys (h), (GCompareFunc)g_utf8_collate);
4354 for (i = 1, l = names; l; l = l->next) {
4355 const char *uri = g_hash_table_lookup (h, l->data);
4356 GString *label = g_string_new (NULL);
4357 GtkActionEntry entry;
4358 char *gname;
4359 const char *gpath;
4360 char *basename = go_basename_from_uri (uri);
4361 const char *s;
4362 GtkAction *action;
4364 if (i < 10) g_string_append_c (label, '_');
4365 g_string_append_printf (label, "%d ", i);
4367 for (s = basename; *s; s++) {
4368 if (*s == '_') g_string_append_c (label, '_');
4369 g_string_append_c (label, *s);
4372 entry.name = gname = g_strdup_printf ("Template%d", i);
4373 entry.stock_id = NULL;
4374 entry.label = label->str;
4375 entry.accelerator = NULL;
4376 entry.tooltip = NULL;
4377 entry.callback = G_CALLBACK (cb_new_from_template);
4379 gtk_action_group_add_actions (gtk->templates.actions,
4380 &entry, 1, gtk);
4382 action = gtk_action_group_get_action (gtk->templates.actions,
4383 entry.name);
4385 g_object_set_data_full (G_OBJECT (action), "uri",
4386 g_strdup (uri), (GDestroyNotify)g_free);
4389 gpath = "/menubar/File/Templates";
4390 gtk_ui_manager_add_ui (gtk->ui, gtk->templates.merge_id,
4391 gpath, gname, gname,
4392 GTK_UI_MANAGER_AUTO, FALSE);
4394 g_string_free (label, TRUE);
4395 g_free (gname);
4396 g_free (basename);
4397 i++;
4400 g_slist_free (names);
4401 g_hash_table_destroy (h);
4404 gboolean
4405 wbc_gtk_load_templates (WBCGtk *wbcg)
4407 if (wbcg->templates.merge_id == 0) {
4408 wbc_gtk_reload_templates (wbcg);
4411 wbcg->template_loader_handler = 0;
4412 return FALSE;
4415 static void
4416 wbcg_set_toplevel (WBCGtk *wbcg, GtkWidget *w)
4418 static GtkTargetEntry const drag_types[] = {
4419 { (char *) "text/uri-list", 0, TARGET_URI_LIST },
4420 { (char *) "GNUMERIC_SHEET", 0, TARGET_SHEET },
4421 { (char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0 }
4424 g_return_if_fail (wbcg->toplevel == NULL);
4426 wbcg->toplevel = w;
4427 w = GTK_WIDGET (wbcg_toplevel (wbcg));
4428 g_return_if_fail (GTK_IS_WINDOW (w));
4430 g_object_set (G_OBJECT (w),
4431 "resizable", TRUE,
4432 NULL);
4434 g_signal_connect_data (w, "delete_event",
4435 G_CALLBACK (wbc_gtk_close), wbcg, NULL,
4436 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
4437 g_signal_connect_after (w, "set_focus",
4438 G_CALLBACK (cb_set_focus), wbcg);
4439 g_signal_connect (w, "scroll-event",
4440 G_CALLBACK (cb_scroll_wheel), wbcg);
4441 g_signal_connect (w, "realize",
4442 G_CALLBACK (cb_realize), wbcg);
4443 g_signal_connect (w, "screen-changed",
4444 G_CALLBACK (cb_screen_changed), NULL);
4445 cb_screen_changed (w);
4447 /* Setup a test of Drag and Drop */
4448 gtk_drag_dest_set (GTK_WIDGET (w),
4449 GTK_DEST_DEFAULT_ALL, drag_types, G_N_ELEMENTS (drag_types),
4450 GDK_ACTION_COPY | GDK_ACTION_MOVE);
4451 gtk_drag_dest_add_image_targets (GTK_WIDGET (w));
4452 gtk_drag_dest_add_text_targets (GTK_WIDGET (w));
4453 g_object_connect (G_OBJECT (w),
4454 "signal::drag-leave", G_CALLBACK (cb_wbcg_drag_leave), wbcg,
4455 "signal::drag-data-received", G_CALLBACK (cb_wbcg_drag_data_received), wbcg,
4456 "signal::drag-motion", G_CALLBACK (cb_wbcg_drag_motion), wbcg,
4457 #if 0
4458 "signal::drag-data-get", G_CALLBACK (wbcg_drag_data_get), wbc,
4459 #endif
4460 NULL);
4463 /***************************************************************************/
4465 static void
4466 wbc_gtk_get_property (GObject *object, guint property_id,
4467 GValue *value, GParamSpec *pspec)
4469 WBCGtk *wbcg = (WBCGtk *)object;
4471 switch (property_id) {
4472 case WBG_GTK_PROP_AUTOSAVE_PROMPT:
4473 g_value_set_boolean (value, wbcg->autosave_prompt);
4474 break;
4475 case WBG_GTK_PROP_AUTOSAVE_TIME:
4476 g_value_set_int (value, wbcg->autosave_time);
4477 break;
4478 default:
4479 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
4480 break;
4484 static void
4485 wbc_gtk_set_property (GObject *object, guint property_id,
4486 const GValue *value, GParamSpec *pspec)
4488 WBCGtk *wbcg = (WBCGtk *)object;
4490 switch (property_id) {
4491 case WBG_GTK_PROP_AUTOSAVE_PROMPT:
4492 wbcg->autosave_prompt = g_value_get_boolean (value);
4493 break;
4494 case WBG_GTK_PROP_AUTOSAVE_TIME:
4495 wbcg_set_autosave_time (wbcg, g_value_get_int (value));
4496 break;
4497 default:
4498 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
4499 break;
4503 static void
4504 wbc_gtk_finalize (GObject *obj)
4506 WBCGtk *wbcg = WBC_GTK (obj);
4508 if (wbcg->idle_update_style_feedback != 0)
4509 g_source_remove (wbcg->idle_update_style_feedback);
4511 if (wbcg->template_loader_handler != 0) {
4512 g_source_remove (wbcg->template_loader_handler);
4513 wbcg->template_loader_handler = 0;
4516 if (wbcg->file_history.merge_id != 0)
4517 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->file_history.merge_id);
4518 g_clear_object (&wbcg->file_history.actions);
4520 if (wbcg->toolbar.merge_id != 0)
4521 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->toolbar.merge_id);
4522 g_clear_object (&wbcg->toolbar.actions);
4524 if (wbcg->windows.merge_id != 0)
4525 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->windows.merge_id);
4526 g_clear_object (&wbcg->windows.actions);
4528 if (wbcg->templates.merge_id != 0)
4529 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->templates.merge_id);
4530 g_clear_object (&wbcg->templates.actions);
4533 GSList *l, *uis = go_hash_keys (wbcg->custom_uis);
4534 for (l = uis; l; l = l->next) {
4535 GnmAppExtraUI *extra_ui = l->data;
4536 cb_remove_custom_ui (NULL, extra_ui, wbcg);
4538 g_slist_free (uis);
4541 g_hash_table_destroy (wbcg->custom_uis);
4542 wbcg->custom_uis = NULL;
4544 g_clear_object (&wbcg->zoom_vaction);
4545 g_clear_object (&wbcg->zoom_haction);
4546 g_clear_object (&wbcg->borders);
4547 g_clear_object (&wbcg->fore_color);
4548 g_clear_object (&wbcg->back_color);
4549 g_clear_object (&wbcg->font_name_haction);
4550 g_clear_object (&wbcg->font_name_vaction);
4551 g_clear_object (&wbcg->redo_haction);
4552 g_clear_object (&wbcg->redo_vaction);
4553 g_clear_object (&wbcg->undo_haction);
4554 g_clear_object (&wbcg->undo_vaction);
4555 g_clear_object (&wbcg->halignment);
4556 g_clear_object (&wbcg->valignment);
4557 g_clear_object (&wbcg->actions);
4558 g_clear_object (&wbcg->permanent_actions);
4559 g_clear_object (&wbcg->font_actions);
4560 g_clear_object (&wbcg->data_only_actions);
4561 g_clear_object (&wbcg->semi_permanent_actions);
4562 g_clear_object (&wbcg->ui);
4564 /* Disconnect signals that would attempt to change things during
4565 * destruction.
4568 wbcg_autosave_cancel (wbcg);
4570 if (wbcg->bnotebook != NULL)
4571 g_signal_handlers_disconnect_by_func (
4572 G_OBJECT (wbcg->bnotebook),
4573 G_CALLBACK (cb_notebook_switch_page), wbcg);
4574 g_clear_object (&wbcg->bnotebook);
4576 g_signal_handlers_disconnect_by_func (
4577 G_OBJECT (wbcg->toplevel),
4578 G_CALLBACK (cb_set_focus), wbcg);
4580 wbcg_auto_complete_destroy (wbcg);
4582 gtk_window_set_focus (wbcg_toplevel (wbcg), NULL);
4584 if (wbcg->toplevel != NULL) {
4585 gtk_widget_destroy (wbcg->toplevel);
4586 wbcg->toplevel = NULL;
4589 if (wbcg->font_desc) {
4590 pango_font_description_free (wbcg->font_desc);
4591 wbcg->font_desc = NULL;
4594 g_clear_object (&wbcg->auto_expr_label);
4596 g_hash_table_destroy (wbcg->visibility_widgets);
4597 g_clear_object (&wbcg->undo_for_fullscreen);
4599 g_slist_free (wbcg->hide_for_fullscreen);
4600 wbcg->hide_for_fullscreen = NULL;
4603 g_free (wbcg->preferred_geometry);
4604 wbcg->preferred_geometry = NULL;
4606 g_clear_object (&wbcg->gui);
4608 parent_class->finalize (obj);
4611 /***************************************************************************/
4613 typedef struct {
4614 GnmExprEntry *entry;
4615 GogDataset *dataset;
4616 int dim_i;
4617 gboolean suppress_update;
4618 GogDataType data_type;
4619 gboolean changed;
4621 gulong dataset_changed_handler;
4622 gulong entry_update_handler;
4623 guint idle;
4624 } GraphDimEditor;
4626 static void
4627 cb_graph_dim_editor_update (GnmExprEntry *gee,
4628 G_GNUC_UNUSED gboolean user_requested,
4629 GraphDimEditor *editor)
4631 GOData *data = NULL;
4632 Sheet *sheet;
4633 SheetControlGUI *scg;
4634 editor->changed = FALSE;
4636 /* Ignore changes while we are insensitive. useful for displaying
4637 * values, without storing them as Data. Also ignore updates if the
4638 * dataset has been cleared via the weakref handler */
4639 if (!gtk_widget_is_sensitive (GTK_WIDGET (gee)) ||
4640 editor->dataset == NULL)
4641 return;
4643 scg = gnm_expr_entry_get_scg (gee);
4644 sheet = scg_sheet (scg);
4646 /* If we are setting something */
4647 if (!gnm_expr_entry_is_blank (editor->entry)) {
4648 GnmParsePos pos;
4649 GnmParseError perr;
4650 GnmExprTop const *texpr;
4651 GnmExprParseFlags flags =
4652 (editor->data_type == GOG_DATA_VECTOR)?
4653 GNM_EXPR_PARSE_PERMIT_MULTIPLE_EXPRESSIONS |
4654 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS:
4655 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS;
4657 parse_error_init (&perr);
4658 /* Setting start_sel=FALSE to avoid */
4659 /* https://bugzilla.gnome.org/show_bug.cgi?id=658223 */
4660 texpr = gnm_expr_entry_parse (editor->entry,
4661 parse_pos_init_sheet (&pos, sheet),
4662 &perr, FALSE, flags);
4664 /* TODO : add some error dialogs split out
4665 * the code in workbook_edit to add parens. */
4666 if (texpr == NULL) {
4667 if (editor->data_type == GOG_DATA_SCALAR)
4668 texpr = gnm_expr_top_new_constant (
4669 value_new_string (
4670 gnm_expr_entry_get_text (editor->entry)));
4671 else {
4672 g_return_if_fail (perr.err != NULL);
4674 wb_control_validation_msg (GNM_WBC (scg_wbcg (scg)),
4675 GNM_VALIDATION_STYLE_INFO, NULL, perr.err->message);
4676 parse_error_free (&perr);
4677 gtk_editable_select_region (GTK_EDITABLE (gnm_expr_entry_get_entry (editor->entry)), 0, G_MAXINT);
4678 editor->changed = TRUE;
4679 return;
4683 switch (editor->data_type) {
4684 case GOG_DATA_SCALAR:
4685 data = gnm_go_data_scalar_new_expr (sheet, texpr);
4686 break;
4687 case GOG_DATA_VECTOR:
4688 data = gnm_go_data_vector_new_expr (sheet, texpr);
4689 break;
4690 case GOG_DATA_MATRIX:
4691 data = gnm_go_data_matrix_new_expr (sheet, texpr);
4695 /* The SheetObjectGraph does the magic to link things in */
4696 editor->suppress_update = TRUE;
4697 gog_dataset_set_dim (editor->dataset, editor->dim_i, data, NULL);
4698 editor->suppress_update = FALSE;
4701 static gboolean
4702 cb_update_idle (GraphDimEditor *editor)
4704 cb_graph_dim_editor_update (editor->entry, FALSE, editor);
4705 editor->idle = 0;
4706 return FALSE;
4709 static void
4710 graph_dim_cancel_idle (GraphDimEditor *editor)
4712 if (editor->idle) {
4713 g_source_remove (editor->idle);
4714 editor->idle = 0;
4718 static gboolean
4719 cb_graph_dim_entry_focus_out_event (G_GNUC_UNUSED GtkEntry *ignored,
4720 G_GNUC_UNUSED GdkEventFocus *event,
4721 GraphDimEditor *editor)
4723 if (!editor->changed)
4724 return FALSE;
4725 graph_dim_cancel_idle (editor);
4726 editor->idle = g_idle_add ((GSourceFunc) cb_update_idle, editor);
4728 return FALSE;
4731 static void
4732 cb_graph_dim_entry_changed (GraphDimEditor *editor)
4734 editor->changed = TRUE;
4737 static void
4738 set_entry_contents (GnmExprEntry *entry, GOData *val)
4740 if (GNM_IS_GO_DATA_SCALAR (val)) {
4741 GnmValue const *v = gnm_expr_top_get_constant (gnm_go_data_get_expr (val));
4742 if (v && VALUE_IS_NUMBER (v)) {
4743 double d = go_data_get_scalar_value (val);
4744 GODateConventions const *date_conv = go_data_date_conv (val);
4745 gog_data_editor_set_value_double (GOG_DATA_EDITOR (entry),
4746 d, date_conv);
4747 return;
4751 if (GO_IS_DATA_SCALAR (val) && go_data_has_value (val)) {
4752 double d = go_data_get_scalar_value (val);
4753 GODateConventions const *date_conv = go_data_date_conv (val);
4754 gog_data_editor_set_value_double (GOG_DATA_EDITOR (entry),
4755 d, date_conv);
4756 return;
4760 SheetControlGUI *scg = gnm_expr_entry_get_scg (entry);
4761 Sheet const *sheet = scg_sheet (scg);
4762 char *txt = go_data_serialize (val, (gpointer)sheet->convs);
4763 gnm_expr_entry_load_from_text (entry, txt);
4764 g_free (txt);
4768 static void
4769 cb_dataset_changed (GogDataset *dataset,
4770 gboolean resize,
4771 GraphDimEditor *editor)
4773 GOData *val = gog_dataset_get_dim (dataset, editor->dim_i);
4774 if (val != NULL && !editor->suppress_update) {
4775 g_signal_handler_block (editor->entry,
4776 editor->entry_update_handler);
4777 set_entry_contents (editor->entry, val);
4778 g_signal_handler_unblock (editor->entry,
4779 editor->entry_update_handler);
4783 static void
4784 cb_dim_editor_weakref_notify (GraphDimEditor *editor, GogDataset *dataset)
4786 g_return_if_fail (editor->dataset == dataset);
4787 editor->dataset = NULL;
4790 static void
4791 graph_dim_editor_free (GraphDimEditor *editor)
4793 graph_dim_cancel_idle (editor);
4794 if (editor->dataset) {
4795 g_signal_handler_disconnect (editor->dataset, editor->dataset_changed_handler);
4796 g_object_weak_unref (G_OBJECT (editor->dataset),
4797 (GWeakNotify) cb_dim_editor_weakref_notify, editor);
4799 g_free (editor);
4802 static GogDataEditor *
4803 wbcg_data_allocator_editor (GogDataAllocator *dalloc,
4804 GogDataset *dataset, int dim_i, GogDataType data_type)
4806 WBCGtk *wbcg = WBC_GTK (dalloc);
4807 GraphDimEditor *editor;
4808 GOData *val;
4810 editor = g_new (GraphDimEditor, 1);
4811 editor->dataset = dataset;
4812 editor->dim_i = dim_i;
4813 editor->suppress_update = FALSE;
4814 editor->data_type = data_type;
4815 editor->entry = gnm_expr_entry_new (wbcg, TRUE);
4816 editor->idle = 0;
4817 editor->changed = FALSE;
4818 g_object_weak_ref (G_OBJECT (editor->dataset),
4819 (GWeakNotify) cb_dim_editor_weakref_notify, editor);
4821 gnm_expr_entry_set_update_policy (editor->entry,
4822 GNM_UPDATE_DISCONTINUOUS);
4824 val = gog_dataset_get_dim (dataset, dim_i);
4825 if (val != NULL) {
4826 set_entry_contents (editor->entry, val);
4829 gnm_expr_entry_set_flags (editor->entry, GNM_EE_FORCE_ABS_REF, GNM_EE_MASK);
4831 editor->entry_update_handler = g_signal_connect (G_OBJECT (editor->entry),
4832 "update",
4833 G_CALLBACK (cb_graph_dim_editor_update), editor);
4834 g_signal_connect (G_OBJECT (gnm_expr_entry_get_entry (editor->entry)),
4835 "focus-out-event",
4836 G_CALLBACK (cb_graph_dim_entry_focus_out_event), editor);
4837 g_signal_connect_swapped (G_OBJECT (gnm_expr_entry_get_entry (editor->entry)),
4838 "changed",
4839 G_CALLBACK (cb_graph_dim_entry_changed), editor);
4840 editor->dataset_changed_handler = g_signal_connect (G_OBJECT (editor->dataset),
4841 "changed", G_CALLBACK (cb_dataset_changed), editor);
4842 g_object_set_data_full (G_OBJECT (editor->entry),
4843 "editor", editor, (GDestroyNotify) graph_dim_editor_free);
4845 return GOG_DATA_EDITOR (editor->entry);
4848 static void
4849 wbcg_data_allocator_allocate (GogDataAllocator *dalloc, GogPlot *plot)
4851 SheetControlGUI *scg = wbcg_cur_scg (WBC_GTK (dalloc));
4852 sv_selection_to_plot (scg_view (scg), plot);
4856 static void
4857 wbcg_go_plot_data_allocator_init (GogDataAllocatorClass *iface)
4859 iface->editor = wbcg_data_allocator_editor;
4860 iface->allocate = wbcg_data_allocator_allocate;
4863 /*************************************************************************/
4864 static char *
4865 wbcg_get_password (GOCmdContext *cc, char const* filename)
4867 WBCGtk *wbcg = WBC_GTK (cc);
4869 return dialog_get_password (wbcg_toplevel (wbcg), filename);
4871 static void
4872 wbcg_set_sensitive (GOCmdContext *cc, gboolean sensitive)
4874 GtkWindow *toplevel = wbcg_toplevel (WBC_GTK (cc));
4875 if (toplevel != NULL)
4876 gtk_widget_set_sensitive (GTK_WIDGET (toplevel), sensitive);
4878 static void
4879 wbcg_error_error (GOCmdContext *cc, GError *err)
4881 go_gtk_notice_dialog (wbcg_toplevel (WBC_GTK (cc)),
4882 GTK_MESSAGE_ERROR,
4883 "%s", err->message);
4886 static void
4887 wbcg_error_error_info (GOCmdContext *cc, GOErrorInfo *error)
4889 gnm_go_error_info_dialog_show (
4890 wbcg_toplevel (WBC_GTK (cc)), error);
4893 static void
4894 wbcg_error_error_info_list (GOCmdContext *cc, GSList *errs)
4896 gnm_go_error_info_list_dialog_show
4897 (wbcg_toplevel (WBC_GTK (cc)), errs);
4900 static void
4901 wbcg_progress_set (GOCmdContext *cc, double val)
4903 WBCGtk *wbcg = WBC_GTK (cc);
4904 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wbcg->progress_bar), val);
4906 static void
4907 wbcg_progress_message_set (GOCmdContext *cc, gchar const *msg)
4909 WBCGtk *wbcg = WBC_GTK (cc);
4910 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wbcg->progress_bar), msg);
4912 static void
4913 wbcg_gnm_cmd_context_init (GOCmdContextClass *iface)
4915 iface->get_password = wbcg_get_password;
4916 iface->set_sensitive = wbcg_set_sensitive;
4917 iface->error.error = wbcg_error_error;
4918 iface->error.error_info = wbcg_error_error_info;
4919 iface->error.error_info_list = wbcg_error_error_info_list;
4920 iface->progress_set = wbcg_progress_set;
4921 iface->progress_message_set = wbcg_progress_message_set;
4924 /*************************************************************************/
4926 static void
4927 wbc_gtk_class_init (GObjectClass *gobject_class)
4929 WorkbookControlClass *wbc_class =
4930 GNM_WBC_CLASS (gobject_class);
4932 g_return_if_fail (wbc_class != NULL);
4934 debug_tab_order = gnm_debug_flag ("tab-order");
4936 parent_class = g_type_class_peek_parent (gobject_class);
4937 gobject_class->get_property = wbc_gtk_get_property;
4938 gobject_class->set_property = wbc_gtk_set_property;
4939 gobject_class->finalize = wbc_gtk_finalize;
4941 wbc_class->edit_line_set = wbcg_edit_line_set;
4942 wbc_class->selection_descr_set = wbcg_edit_selection_descr_set;
4943 wbc_class->update_action_sensitivity = wbcg_update_action_sensitivity;
4945 wbc_class->sheet.add = wbcg_sheet_add;
4946 wbc_class->sheet.remove = wbcg_sheet_remove;
4947 wbc_class->sheet.focus = wbcg_sheet_focus;
4948 wbc_class->sheet.remove_all = wbcg_sheet_remove_all;
4950 wbc_class->undo_redo.labels = wbcg_undo_redo_labels;
4951 wbc_class->undo_redo.truncate = wbc_gtk_undo_redo_truncate;
4952 wbc_class->undo_redo.pop = wbc_gtk_undo_redo_pop;
4953 wbc_class->undo_redo.push = wbc_gtk_undo_redo_push;
4955 wbc_class->menu_state.update = wbcg_menu_state_update;
4957 wbc_class->claim_selection = wbcg_claim_selection;
4958 wbc_class->paste_from_selection = wbcg_paste_from_selection;
4959 wbc_class->validation_msg = wbcg_validation_msg;
4961 wbc_class->control_new = wbc_gtk_control_new;
4962 wbc_class->init_state = wbc_gtk_init_state;
4963 wbc_class->style_feedback = wbc_gtk_style_feedback;
4965 g_object_class_install_property (gobject_class,
4966 WBG_GTK_PROP_AUTOSAVE_PROMPT,
4967 g_param_spec_boolean ("autosave-prompt",
4968 P_("Autosave prompt"),
4969 P_("Ask about autosave?"),
4970 FALSE,
4971 GSF_PARAM_STATIC | G_PARAM_READWRITE));
4972 g_object_class_install_property (gobject_class,
4973 WBG_GTK_PROP_AUTOSAVE_TIME,
4974 g_param_spec_int ("autosave-time",
4975 P_("Autosave time in seconds"),
4976 P_("Seconds before autosave"),
4977 0, G_MAXINT, 0,
4978 GSF_PARAM_STATIC | G_PARAM_READWRITE));
4980 wbc_gtk_signals [WBC_GTK_MARKUP_CHANGED] = g_signal_new ("markup-changed",
4981 GNM_WBC_GTK_TYPE,
4982 G_SIGNAL_RUN_LAST,
4983 G_STRUCT_OFFSET (WBCGtkClass, markup_changed),
4984 NULL, NULL,
4985 g_cclosure_marshal_VOID__VOID,
4986 G_TYPE_NONE,
4987 0, G_TYPE_NONE);
4989 gtk_window_set_default_icon_name ("gnumeric");
4992 static void
4993 wbc_gtk_init (GObject *obj)
4995 WBCGtk *wbcg = (WBCGtk *)obj;
4996 GError *error = NULL;
4997 char *uifile;
4998 unsigned i;
4999 GEnumClass *posclass;
5000 GtkStyleContext *ctxt;
5001 guint merge_id;
5003 wbcg->gui = gnm_gtk_builder_load ("res:ui/wbcg.ui", NULL, NULL);
5004 wbcg->cancel_button = GET_GUI_ITEM ("cancel_button");
5005 wbcg->ok_button = GET_GUI_ITEM ("ok_button");
5006 wbcg->func_button = GET_GUI_ITEM ("func_button");
5007 wbcg->progress_bar = GET_GUI_ITEM ("progress_bar");
5008 wbcg->auto_expr_label = GET_GUI_ITEM ("auto_expr_label");
5009 wbcg->status_text = GET_GUI_ITEM ("status_text");
5010 wbcg->tabs_paned = GET_GUI_ITEM ("tabs_paned");
5011 wbcg->status_area = GET_GUI_ITEM ("status_area");
5012 wbcg->notebook_area = GET_GUI_ITEM ("notebook_area");
5013 wbcg->snotebook = GET_GUI_ITEM ("snotebook");
5014 wbcg->selection_descriptor = GET_GUI_ITEM ("selection_descriptor");
5015 wbcg->menu_zone = GET_GUI_ITEM ("menu_zone");
5016 wbcg->toolbar_zones[GTK_POS_TOP] = GET_GUI_ITEM ("toolbar_zone_top");
5017 wbcg->toolbar_zones[GTK_POS_BOTTOM] = NULL;
5018 wbcg->toolbar_zones[GTK_POS_LEFT] = GET_GUI_ITEM ("toolbar_zone_left");
5019 wbcg->toolbar_zones[GTK_POS_RIGHT] = GET_GUI_ITEM ("toolbar_zone_right");
5020 wbcg->updating_ui = FALSE;
5022 posclass = G_ENUM_CLASS (g_type_class_peek (gtk_position_type_get_type ()));
5023 for (i = 0; i < posclass->n_values; i++) {
5024 GEnumValue const *ev = posclass->values + i;
5025 GtkWidget *zone = wbcg->toolbar_zones[ev->value];
5026 GtkStyleContext *ctxt;
5027 if (!zone)
5028 continue;
5029 ctxt = gtk_widget_get_style_context (zone);
5030 gtk_style_context_add_class (ctxt, "toolbarzone");
5031 gtk_style_context_add_class (ctxt, ev->value_nick);
5034 wbcg->visibility_widgets = g_hash_table_new_full (g_str_hash,
5035 g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_object_unref);
5036 wbcg->undo_for_fullscreen = NULL;
5037 wbcg->hide_for_fullscreen = NULL;
5039 wbcg->autosave_prompt = FALSE;
5040 wbcg->autosave_time = 0;
5041 wbcg->autosave_timer = 0;
5043 /* We are not in edit mode */
5044 wbcg->editing = FALSE;
5045 wbcg->editing_sheet = NULL;
5046 wbcg->editing_cell = NULL;
5048 wbcg->new_object = NULL;
5050 wbcg->idle_update_style_feedback = 0;
5052 wbcg_set_toplevel (wbcg, GET_GUI_ITEM ("toplevel"));
5053 ctxt = gtk_widget_get_style_context (GTK_WIDGET (wbcg_toplevel (wbcg)));
5054 gtk_style_context_add_class (ctxt, "gnumeric");
5056 g_signal_connect (wbcg_toplevel (wbcg), "window_state_event",
5057 G_CALLBACK (cb_wbcg_window_state_event),
5058 wbcg);
5060 wbc_gtk_init_actions (wbcg);
5061 wbcg->ui = gtk_ui_manager_new ();
5062 g_object_connect (wbcg->ui,
5063 "signal::add_widget", G_CALLBACK (cb_add_menus_toolbars), wbcg,
5064 "signal::connect_proxy", G_CALLBACK (cb_connect_proxy), wbcg,
5065 "signal::disconnect_proxy", G_CALLBACK (cb_disconnect_proxy), wbcg,
5066 "signal::post_activate", G_CALLBACK (cb_post_activate), wbcg,
5067 NULL);
5068 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->permanent_actions, 0);
5069 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->actions, 0);
5070 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->font_actions, 0);
5071 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->data_only_actions, 0);
5072 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->semi_permanent_actions, 0);
5073 gtk_window_add_accel_group (wbcg_toplevel (wbcg),
5074 gtk_ui_manager_get_accel_group (wbcg->ui));
5076 if (extra_actions)
5077 gtk_action_group_add_actions (wbcg->actions, extra_actions,
5078 extra_actions_nb, wbcg);
5080 if (uifilename) {
5081 if (strncmp (uifilename, "res:", 4) == 0)
5082 uifile = g_strdup (uifilename);
5083 else
5084 uifile = g_build_filename (gnm_sys_data_dir (),
5085 uifilename,
5086 NULL);
5087 } else
5088 uifile = g_strdup ("res:/org/gnumeric/gnumeric/ui/GNOME_Gnumeric-gtk.xml");
5090 if (strncmp (uifile, "res:", 4) == 0)
5091 merge_id = gtk_ui_manager_add_ui_from_resource
5092 (wbcg->ui, uifile + 4, &error);
5093 else
5094 merge_id = gtk_ui_manager_add_ui_from_file
5095 (wbcg->ui, uifile, &error);
5096 if (!merge_id) {
5097 g_message ("building menus failed: %s", error->message);
5098 g_error_free (error);
5101 g_free (uifile);
5103 wbcg->custom_uis = g_hash_table_new_full (g_direct_hash, g_direct_equal,
5104 NULL, g_free);
5106 wbcg->file_history.actions = NULL;
5107 wbcg->file_history.merge_id = 0;
5109 wbcg->toolbar.merge_id = gtk_ui_manager_new_merge_id (wbcg->ui);
5110 wbcg->toolbar.actions = gtk_action_group_new ("Toolbars");
5111 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->toolbar.actions, 0);
5113 wbcg->windows.actions = NULL;
5114 wbcg->windows.merge_id = 0;
5116 wbcg->templates.actions = NULL;
5117 wbcg->templates.merge_id = 0;
5119 gnm_app_foreach_extra_ui ((GFunc) cb_init_extra_ui, wbcg);
5120 g_object_connect ((GObject *) gnm_app_get_app (),
5121 "swapped-object-signal::window-list-changed",
5122 G_CALLBACK (cb_regenerate_window_menu), wbcg,
5123 "object-signal::custom-ui-added",
5124 G_CALLBACK (cb_add_custom_ui), wbcg,
5125 "object-signal::custom-ui-removed",
5126 G_CALLBACK (cb_remove_custom_ui), wbcg,
5127 NULL);
5129 gtk_ui_manager_ensure_update (wbcg->ui);
5131 /* updates the undo/redo menu labels before check_underlines
5132 * to avoid problems like #324692. */
5133 wb_control_undo_redo_labels (GNM_WBC (wbcg), NULL, NULL);
5134 if (GNM_VERSION_MAJOR % 2 != 0 ||
5135 gnm_debug_flag ("underlines")) {
5136 gtk_container_foreach (GTK_CONTAINER (wbcg->menu_zone),
5137 (GtkCallback)check_underlines,
5138 (gpointer)"");
5141 wbcg_set_autosave_time (wbcg, gnm_conf_get_core_workbook_autosave_time ());
5144 GSF_CLASS_FULL (WBCGtk, wbc_gtk, NULL, NULL, wbc_gtk_class_init, NULL,
5145 wbc_gtk_init, GNM_WBC_TYPE, 0,
5146 GSF_INTERFACE (wbcg_go_plot_data_allocator_init, GOG_TYPE_DATA_ALLOCATOR);
5147 GSF_INTERFACE (wbcg_gnm_cmd_context_init, GO_TYPE_CMD_CONTEXT))
5149 /******************************************************************************/
5151 void
5152 wbc_gtk_markup_changer (WBCGtk *wbcg)
5154 g_signal_emit (G_OBJECT (wbcg), wbc_gtk_signals [WBC_GTK_MARKUP_CHANGED], 0);
5157 /******************************************************************************/
5159 * wbc_gtk_new:
5160 * @optional_view: (allow-none): #WorkbookView
5161 * @optional_wb: (allow-none) (transfer full): #Workbook
5162 * @optional_screen: (allow-none): #GdkScreen.
5163 * @optional_geometry: (allow-none): string.
5165 * Returns: (transfer none): (allow-none): the new #WBCGtk or %NULL.
5167 WBCGtk *
5168 wbc_gtk_new (WorkbookView *optional_view,
5169 Workbook *optional_wb,
5170 GdkScreen *optional_screen,
5171 gchar *optional_geometry)
5173 Sheet *sheet;
5174 WorkbookView *wbv;
5175 WBCGtk *wbcg = g_object_new (wbc_gtk_get_type (), NULL);
5176 WorkbookControl *wbc = (WorkbookControl *)wbcg;
5178 wbcg->preferred_geometry = g_strdup (optional_geometry);
5180 wbc_gtk_create_edit_area (wbcg);
5181 wbc_gtk_create_status_area (wbcg);
5182 wbc_gtk_reload_recent_file_menu (wbcg);
5184 g_signal_connect_object (gnm_app_get_app (),
5185 "notify::file-history-list",
5186 G_CALLBACK (wbc_gtk_reload_recent_file_menu), wbcg, G_CONNECT_SWAPPED);
5188 wb_control_set_view (wbc, optional_view, optional_wb);
5189 wbv = wb_control_view (wbc);
5190 sheet = wbv->current_sheet;
5191 if (sheet != NULL) {
5192 wb_control_menu_state_update (wbc, MS_ALL);
5193 wb_control_update_action_sensitivity (wbc);
5194 wb_control_style_feedback (wbc, NULL);
5195 cb_zoom_change (sheet, NULL, wbcg);
5198 wbc_gtk_create_notebook_area (wbcg);
5200 wbcg_view_changed (wbcg, NULL, NULL);
5202 if (optional_screen)
5203 gtk_window_set_screen (wbcg_toplevel (wbcg), optional_screen);
5205 /* Postpone showing the GUI, so that we may resize it freely. */
5206 g_idle_add ((GSourceFunc)show_gui, wbcg);
5208 /* Load this later when thing have calmed down. If this does not
5209 trigger by the time the file menu is activated, then the UI is
5210 updated right then -- and that looks sub-optimal because the
5211 "Templates" menu is empty (and thus not shown) until the
5212 update is done. */
5213 wbcg->template_loader_handler =
5214 g_timeout_add (1000, (GSourceFunc)wbc_gtk_load_templates, wbcg);
5216 wb_control_init_state (wbc);
5217 return wbcg;
5221 * wbcg_toplevel:
5222 * @wbcg: #WBCGtk
5224 * Returns: (transfer none): the toplevel #GtkWindow.
5226 GtkWindow *
5227 wbcg_toplevel (WBCGtk *wbcg)
5229 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5230 return GTK_WINDOW (wbcg->toplevel);
5233 void
5234 wbcg_set_transient (WBCGtk *wbcg, GtkWindow *window)
5236 go_gtk_window_set_transient (wbcg_toplevel (wbcg), window);
5240 wbcg_get_n_scg (WBCGtk const *wbcg)
5242 return (GTK_IS_NOTEBOOK (wbcg->snotebook))?
5243 gtk_notebook_get_n_pages (wbcg->snotebook): -1;
5247 * wbcg_get_nth_scg:
5248 * @wbcg: #WBCGtk
5249 * @i:
5251 * Returns: (transfer none): the scg associated with the @i-th tab in
5252 * @wbcg's notebook.
5253 * NOTE : @i != scg->sv->sheet->index_in_wb
5255 SheetControlGUI *
5256 wbcg_get_nth_scg (WBCGtk *wbcg, int i)
5258 SheetControlGUI *scg;
5259 GtkWidget *w;
5261 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5263 if (NULL != wbcg->snotebook &&
5264 NULL != (w = gtk_notebook_get_nth_page (wbcg->snotebook, i)) &&
5265 NULL != (scg = get_scg (w)) &&
5266 NULL != scg->grid &&
5267 NULL != scg_sheet (scg) &&
5268 NULL != scg_view (scg))
5269 return scg;
5271 return NULL;
5274 #warning merge these and clarfy whether we want the visible scg, or the logical (view) scg
5276 * wbcg_focus_cur_scg:
5277 * @wbcg: The workbook control to operate on.
5279 * A utility routine to safely ensure that the keyboard focus
5280 * is attached to the item-grid. This is required when a user
5281 * edits a combo-box or and entry-line which grab focus.
5283 * It is called for zoom, font name/size, and accept/cancel for the editline.
5284 * Returns: (transfer none): the sheet.
5286 Sheet *
5287 wbcg_focus_cur_scg (WBCGtk *wbcg)
5289 SheetControlGUI *scg;
5291 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5293 if (wbcg->snotebook == NULL)
5294 return NULL;
5296 scg = wbcg_get_nth_scg (wbcg,
5297 gtk_notebook_get_current_page (wbcg->snotebook));
5299 g_return_val_if_fail (scg != NULL, NULL);
5301 scg_take_focus (scg);
5302 return scg_sheet (scg);
5306 * wbcg_cur_scg:
5307 * @wbcg: #WBCGtk
5309 * Returns: (transfer none): the current #ShetControlGUI.
5311 SheetControlGUI *
5312 wbcg_cur_scg (WBCGtk *wbcg)
5314 return wbcg_get_scg (wbcg, wbcg_cur_sheet (wbcg));
5318 * wbcg_cur_sheet:
5319 * @wbcg: #WBCGtk
5321 * Returns: (transfer none): the current #Sheet.
5323 Sheet *
5324 wbcg_cur_sheet (WBCGtk *wbcg)
5326 return wb_control_cur_sheet (GNM_WBC (wbcg));
5329 PangoFontDescription *
5330 wbcg_get_font_desc (WBCGtk *wbcg)
5332 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5334 if (!wbcg->font_desc) {
5335 GtkSettings *settings = wbcg_get_gtk_settings (wbcg);
5336 wbcg->font_desc = settings_get_font_desc (settings);
5337 g_signal_connect_object (settings, "notify::gtk-font-name",
5338 G_CALLBACK (cb_desktop_font_changed),
5339 wbcg, 0);
5341 return wbcg->font_desc;
5345 * wbcg_find_for_workbook:
5346 * @wb: #Workbook
5347 * @candidate: a candidate #WBCGtk
5348 * @pref_screen: the preferred screen.
5349 * @pref_display: the preferred display.
5351 * Returns: (transfer none): the found #WBCGtk or %NULL.
5353 WBCGtk *
5354 wbcg_find_for_workbook (Workbook *wb,
5355 WBCGtk *candidate,
5356 GdkScreen *pref_screen,
5357 GdkDisplay *pref_display)
5359 gboolean has_screen, has_display;
5361 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
5362 g_return_val_if_fail (candidate == NULL || GNM_IS_WBC_GTK (candidate), NULL);
5364 if (candidate && wb_control_get_workbook (GNM_WBC (candidate)) == wb)
5365 return candidate;
5367 if (!pref_screen && candidate)
5368 pref_screen = wbcg_get_screen (candidate);
5370 if (!pref_display && pref_screen)
5371 pref_display = gdk_screen_get_display (pref_screen);
5373 candidate = NULL;
5374 has_screen = FALSE;
5375 has_display = FALSE;
5376 WORKBOOK_FOREACH_CONTROL(wb, wbv, wbc, {
5377 if (GNM_IS_WBC_GTK (wbc)) {
5378 WBCGtk *wbcg = WBC_GTK (wbc);
5379 GdkScreen *screen = wbcg_get_screen (wbcg);
5380 GdkDisplay *display = gdk_screen_get_display (screen);
5382 if (pref_screen == screen && !has_screen) {
5383 has_screen = has_display = TRUE;
5384 candidate = wbcg;
5385 } else if (pref_display == display && !has_display) {
5386 has_display = TRUE;
5387 candidate = wbcg;
5388 } else if (!candidate)
5389 candidate = wbcg;
5393 return candidate;
5396 void
5397 wbcg_focus_current_cell_indicator (WBCGtk const *wbcg)
5399 gtk_widget_grab_focus (GTK_WIDGET (wbcg->selection_descriptor));
5400 gtk_editable_select_region (GTK_EDITABLE (wbcg->selection_descriptor), 0, -1);