Update Spanish translation
[gnumeric.git] / src / wbc-gtk.c
blob6738cf35bcc45eecb9996f1ff3c4c3f735e8327f
2 /*
3 * wbc-gtk.c: A gtk based WorkbookControl
5 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
6 * Copyright (C) 2006-2012 Morten Welinder (terra@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <wbc-gtk-impl.h>
26 #include <workbook-view.h>
27 #include <workbook-priv.h>
28 #include <gui-util.h>
29 #include <gutils.h>
30 #include <gui-file.h>
31 #include <sheet-control-gui-priv.h>
32 #include <sheet.h>
33 #include <sheet-private.h>
34 #include <sheet-view.h>
35 #include <sheet-style.h>
36 #include <sheet-filter.h>
37 #include <commands.h>
38 #include <dependent.h>
39 #include <application.h>
40 #include <history.h>
41 #include <func.h>
42 #include <value.h>
43 #include <style-font.h>
44 #include <gnm-format.h>
45 #include <expr.h>
46 #include <style-color.h>
47 #include <style-border.h>
48 #include <gnumeric-conf.h>
49 #include <dialogs/dialogs.h>
50 #include <gui-clipboard.h>
51 #include <libgnumeric.h>
52 #include <gnm-pane-impl.h>
53 #include <graph.h>
54 #include <selection.h>
55 #include <file-autoft.h>
56 #include <ranges.h>
57 #include <tools/analysis-auto-expression.h>
58 #include <sheet-object-cell-comment.h>
59 #include <print-info.h>
60 #include <expr-name.h>
62 #include <goffice/goffice.h>
63 #include <gsf/gsf-impl-utils.h>
64 #include <gsf/gsf-doc-meta-data.h>
65 #include <gdk/gdkkeysyms-compat.h>
66 #include <gnm-i18n.h>
67 #include <string.h>
69 #define GET_GUI_ITEM(i_) (gpointer)(gtk_builder_get_object(wbcg->gui, (i_)))
71 #define SHEET_CONTROL_KEY "SheetControl"
73 #define AUTO_EXPR_SAMPLE "Sumerage = -012345678901234"
76 enum {
77 WBG_GTK_PROP_0,
78 WBG_GTK_PROP_AUTOSAVE_PROMPT,
79 WBG_GTK_PROP_AUTOSAVE_TIME
82 enum {
83 WBC_GTK_MARKUP_CHANGED,
84 WBC_GTK_LAST_SIGNAL
87 enum {
88 TARGET_URI_LIST,
89 TARGET_SHEET
93 static gboolean debug_tab_order;
94 static char const *uifilename = NULL;
95 static GtkActionEntry const *extra_actions = NULL;
96 static int extra_actions_nb;
97 static guint wbc_gtk_signals[WBC_GTK_LAST_SIGNAL];
98 static GObjectClass *parent_class = NULL;
100 static gboolean
101 wbcg_ui_update_begin (WBCGtk *wbcg)
103 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
104 g_return_val_if_fail (!wbcg->updating_ui, FALSE);
106 return (wbcg->updating_ui = TRUE);
109 static void
110 wbcg_ui_update_end (WBCGtk *wbcg)
112 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
113 g_return_if_fail (wbcg->updating_ui);
115 wbcg->updating_ui = FALSE;
118 /****************************************************************************/
120 G_MODULE_EXPORT void
121 set_uifilename (char const *name, GtkActionEntry const *actions, int nb)
123 uifilename = name;
124 extra_actions = actions;
125 extra_actions_nb = nb;
129 * wbcg_find_action:
130 * @wbcg: the workbook control gui
131 * @name: name of action
133 * Returns: (transfer none): The action with the given name
135 GtkAction *
136 wbcg_find_action (WBCGtk *wbcg, const char *name)
138 GtkAction *a;
140 a = gtk_action_group_get_action (wbcg->actions, name);
141 if (a == NULL)
142 a = gtk_action_group_get_action (wbcg->permanent_actions, name);
143 if (a == NULL)
144 a = gtk_action_group_get_action (wbcg->semi_permanent_actions, name);
145 if (a == NULL)
146 a = gtk_action_group_get_action (wbcg->data_only_actions, name);
147 if (a == NULL)
148 a = gtk_action_group_get_action (wbcg->font_actions, name);
149 if (a == NULL)
150 a = gtk_action_group_get_action (wbcg->toolbar.actions, name);
152 return a;
155 static void
156 wbc_gtk_set_action_sensitivity (WBCGtk *wbcg,
157 char const *action, gboolean sensitive)
159 GtkAction *a = wbcg_find_action (wbcg, action);
160 g_object_set (G_OBJECT (a), "sensitive", sensitive, NULL);
163 /* NOTE : The semantics of prefix and suffix seem contrived. Why are we
164 * handling it at this end ? That stuff should be done in the undo/redo code
166 static void
167 wbc_gtk_set_action_label (WBCGtk *wbcg,
168 char const *action,
169 char const *prefix,
170 char const *suffix,
171 char const *new_tip)
173 GtkAction *a = wbcg_find_action (wbcg, action);
175 if (prefix != NULL) {
176 char *text;
177 gboolean is_suffix = (suffix != NULL);
179 text = is_suffix ? g_strdup_printf ("%s: %s", prefix, suffix) : (char *) prefix;
180 g_object_set (G_OBJECT (a),
181 "label", text,
182 "sensitive", is_suffix,
183 NULL);
184 if (is_suffix)
185 g_free (text);
186 } else
187 g_object_set (G_OBJECT (a), "label", suffix, NULL);
189 if (new_tip != NULL)
190 g_object_set (G_OBJECT (a), "tooltip", new_tip, NULL);
193 static void
194 wbc_gtk_set_toggle_action_state (WBCGtk *wbcg,
195 char const *action, gboolean state)
197 GtkAction *a = wbcg_find_action (wbcg, action);
198 gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (a), state);
201 /****************************************************************************/
203 static SheetControlGUI *
204 wbcg_get_scg (WBCGtk *wbcg, Sheet *sheet)
206 SheetControlGUI *scg;
207 int i, npages;
209 if (sheet == NULL || wbcg->snotebook == NULL)
210 return NULL;
212 npages = wbcg_get_n_scg (wbcg);
213 if (npages == 0) {
215 * This can happen during construction when the clipboard is
216 * being cleared. Ctrl-C Ctrl-Q.
218 return NULL;
221 g_return_val_if_fail (IS_SHEET (sheet), NULL);
222 g_return_val_if_fail (sheet->index_in_wb >= 0, NULL);
224 scg = wbcg_get_nth_scg (wbcg, sheet->index_in_wb);
225 if (NULL != scg && scg_sheet (scg) == sheet)
226 return scg;
229 * index_in_wb is probably not accurate because we are in the
230 * middle of removing or adding a sheet.
232 for (i = 0; i < npages; i++) {
233 scg = wbcg_get_nth_scg (wbcg, i);
234 if (NULL != scg && scg_sheet (scg) == sheet)
235 return scg;
238 g_warning ("Failed to find scg for sheet %s", sheet->name_quoted);
239 return NULL;
242 static SheetControlGUI *
243 get_scg (const GtkWidget *w)
245 return g_object_get_data (G_OBJECT (w), SHEET_CONTROL_KEY);
248 static GSList *
249 get_all_scgs (WBCGtk *wbcg)
251 int i, n = gtk_notebook_get_n_pages (wbcg->snotebook);
252 GSList *l = NULL;
254 for (i = 0; i < n; i++) {
255 GtkWidget *w = gtk_notebook_get_nth_page (wbcg->snotebook, i);
256 SheetControlGUI *scg = get_scg (w);
257 l = g_slist_prepend (l, scg);
260 return g_slist_reverse (l);
263 /* Autosave */
265 static gboolean
266 cb_autosave (WBCGtk *wbcg)
268 WorkbookView *wb_view;
270 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
272 wb_view = wb_control_view (GNM_WBC (wbcg));
274 if (wb_view == NULL)
275 return FALSE;
277 if (wbcg->autosave_time > 0 &&
278 go_doc_is_dirty (wb_view_get_doc (wb_view))) {
279 if (wbcg->autosave_prompt && !dialog_autosave_prompt (wbcg))
280 return TRUE;
281 gui_file_save (wbcg, wb_view);
283 return TRUE;
287 * wbcg_rangesel_possible:
288 * @wbcg: the workbook control gui
290 * Returns true if the cursor keys should be used to select
291 * a cell range (if the cursor is in a spot in the expression
292 * where it makes sense to have a cell reference), false if not.
294 gboolean
295 wbcg_rangesel_possible (WBCGtk const *wbcg)
297 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
299 /* Already range selecting */
300 if (wbcg->rangesel != NULL)
301 return TRUE;
303 /* Rangesel requires that we be editing somthing */
304 if (!wbcg_is_editing (wbcg) && !wbcg_entry_has_logical (wbcg))
305 return FALSE;
307 return gnm_expr_entry_can_rangesel (wbcg_get_entry_logical (wbcg));
310 gboolean
311 wbcg_is_editing (WBCGtk const *wbcg)
313 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
314 return wbcg->editing;
317 static void
318 wbcg_autosave_cancel (WBCGtk *wbcg)
320 if (wbcg->autosave_timer != 0) {
321 g_source_remove (wbcg->autosave_timer);
322 wbcg->autosave_timer = 0;
326 static void
327 wbcg_autosave_activate (WBCGtk *wbcg)
329 wbcg_autosave_cancel (wbcg);
331 if (wbcg->autosave_time > 0) {
332 int secs = MIN (wbcg->autosave_time, G_MAXINT / 1000);
333 wbcg->autosave_timer =
334 g_timeout_add (secs * 1000,
335 (GSourceFunc) cb_autosave,
336 wbcg);
340 static void
341 wbcg_set_autosave_time (WBCGtk *wbcg, int secs)
343 if (secs == wbcg->autosave_time)
344 return;
346 wbcg->autosave_time = secs;
347 wbcg_autosave_activate (wbcg);
350 /****************************************************************************/
352 static void
353 wbcg_edit_line_set (WorkbookControl *wbc, char const *text)
355 GtkEntry *entry = wbcg_get_entry ((WBCGtk*)wbc);
356 gtk_entry_set_text (entry, text);
359 static void
360 wbcg_edit_selection_descr_set (WorkbookControl *wbc, char const *text)
362 WBCGtk *wbcg = (WBCGtk *)wbc;
363 gtk_entry_set_text (GTK_ENTRY (wbcg->selection_descriptor), text);
366 static void
367 wbcg_update_action_sensitivity (WorkbookControl *wbc)
369 WBCGtk *wbcg = WBC_GTK (wbc);
370 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
371 gboolean edit_object = scg != NULL &&
372 (scg->selected_objects != NULL || wbcg->new_object != NULL ||
373 scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT);
374 gboolean enable_actions = TRUE;
375 gboolean enable_edit_ok_cancel = FALSE;
377 if (edit_object || wbcg->edit_line.guru != NULL)
378 enable_actions = FALSE;
379 else if (wbcg_is_editing (wbcg)) {
380 enable_actions = FALSE;
381 enable_edit_ok_cancel = TRUE;
384 /* These are only sensitive while editing */
385 gtk_widget_set_sensitive (wbcg->ok_button, enable_edit_ok_cancel);
386 gtk_widget_set_sensitive (wbcg->cancel_button, enable_edit_ok_cancel);
387 gtk_widget_set_sensitive (wbcg->func_button, enable_actions);
389 if (wbcg->snotebook) {
390 gboolean tab_context_menu =
391 enable_actions ||
392 scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT;
393 int i, N = wbcg_get_n_scg (wbcg);
394 for (i = 0; i < N; i++) {
395 GtkWidget *label =
396 gnm_notebook_get_nth_label (wbcg->bnotebook, i);
397 g_object_set_data (G_OBJECT (label), "editable",
398 GINT_TO_POINTER (tab_context_menu));
402 g_object_set (G_OBJECT (wbcg->actions),
403 "sensitive", enable_actions,
404 NULL);
405 g_object_set (G_OBJECT (wbcg->font_actions),
406 "sensitive", enable_actions || enable_edit_ok_cancel,
407 NULL);
409 if (scg && scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT) {
410 g_object_set (G_OBJECT (wbcg->data_only_actions),
411 "sensitive", FALSE,
412 NULL);
413 g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
414 "sensitive",TRUE,
415 NULL);
416 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), FALSE);
417 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), FALSE);
418 } else {
419 g_object_set (G_OBJECT (wbcg->data_only_actions),
420 "sensitive", TRUE,
421 NULL);
422 g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
423 "sensitive", enable_actions,
424 NULL);
425 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), TRUE);
426 gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), TRUE);
430 void
431 wbcg_insert_sheet (GtkWidget *unused, WBCGtk *wbcg)
433 WorkbookControl *wbc = GNM_WBC (wbcg);
434 Sheet *sheet = wb_control_cur_sheet (wbc);
435 Workbook *wb = sheet->workbook;
436 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
437 /* Use same size as current sheet. */
438 workbook_sheet_add (wb, sheet->index_in_wb,
439 gnm_sheet_get_max_cols (sheet),
440 gnm_sheet_get_max_rows (sheet));
441 cmd_reorganize_sheets (wbc, old_state, sheet);
444 void
445 wbcg_append_sheet (GtkWidget *unused, WBCGtk *wbcg)
447 WorkbookControl *wbc = GNM_WBC (wbcg);
448 Sheet *sheet = wb_control_cur_sheet (wbc);
449 Workbook *wb = sheet->workbook;
450 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
451 /* Use same size as current sheet. */
452 workbook_sheet_add (wb, -1,
453 gnm_sheet_get_max_cols (sheet),
454 gnm_sheet_get_max_rows (sheet));
455 cmd_reorganize_sheets (wbc, old_state, sheet);
458 void
459 wbcg_clone_sheet (GtkWidget *unused, WBCGtk *wbcg)
461 WorkbookControl *wbc = GNM_WBC (wbcg);
462 Sheet *sheet = wb_control_cur_sheet (wbc);
463 Workbook *wb = sheet->workbook;
464 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
465 Sheet *new_sheet = sheet_dup (sheet);
466 workbook_sheet_attach_at_pos (wb, new_sheet, sheet->index_in_wb + 1);
467 /* See workbook_sheet_add: */
468 g_signal_emit_by_name (G_OBJECT (wb), "sheet_added", 0);
469 cmd_reorganize_sheets (wbc, old_state, sheet);
470 g_object_unref (new_sheet);
473 static void
474 cb_show_sheet (SheetControlGUI *scg)
476 WBCGtk *wbcg = scg->wbcg;
477 int page_number = gtk_notebook_page_num (wbcg->snotebook,
478 GTK_WIDGET (scg->grid));
479 gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
484 static void cb_sheets_manage (SheetControlGUI *scg) { dialog_sheet_order (scg->wbcg); }
485 static void cb_sheets_insert (SheetControlGUI *scg) { wbcg_insert_sheet (NULL, scg->wbcg); }
486 static void cb_sheets_add (SheetControlGUI *scg) { wbcg_append_sheet (NULL, scg->wbcg); }
487 static void cb_sheets_clone (SheetControlGUI *scg) { wbcg_clone_sheet (NULL, scg->wbcg); }
488 static void cb_sheets_rename (SheetControlGUI *scg) { dialog_sheet_rename (scg->wbcg, scg_sheet (scg)); }
489 static void cb_sheets_resize (SheetControlGUI *scg) { dialog_sheet_resize (scg->wbcg); }
492 static gint
493 cb_by_scg_sheet_name (gconstpointer a_, gconstpointer b_)
495 const SheetControlGUI *a = a_;
496 const SheetControlGUI *b = b_;
497 Sheet *sa = scg_sheet (a);
498 Sheet *sb = scg_sheet (b);
500 return g_utf8_collate (sa->name_unquoted, sb->name_unquoted);
504 static void
505 sheet_menu_label_run (SheetControlGUI *scg, GdkEvent *event)
507 enum { CM_MULTIPLE = 1, CM_DATA_SHEET = 2 };
508 struct SheetTabMenu {
509 char const *text;
510 void (*function) (SheetControlGUI *scg);
511 int flags;
512 int submenu;
513 } const sheet_label_context_actions [] = {
514 { N_("Manage Sheets..."), &cb_sheets_manage, 0, 0},
515 { NULL, NULL, 0, 0 },
516 { N_("Insert"), &cb_sheets_insert, 0, 0 },
517 { N_("Append"), &cb_sheets_add, 0, 0 },
518 { N_("Duplicate"), &cb_sheets_clone, 0, 0 },
519 { N_("Remove"), &scg_delete_sheet_if_possible, CM_MULTIPLE, 0 },
520 { N_("Rename"), &cb_sheets_rename, 0, 0 },
521 { N_("Resize..."), &cb_sheets_resize, CM_DATA_SHEET, 0 },
522 { N_("Select"), NULL, 0, 1 },
523 { N_("Select (sorted)"), NULL, 0, 2 }
526 unsigned int ui;
527 GtkWidget *item, *menu = gtk_menu_new ();
528 GtkWidget *guru = wbc_gtk_get_guru (scg_wbcg (scg));
529 unsigned int N_visible, pass;
530 GtkWidget *submenus[2 + 1];
531 GSList *scgs = get_all_scgs (scg->wbcg);
533 for (pass = 1; pass <= 2; pass++) {
534 GSList *l;
536 submenus[pass] = gtk_menu_new ();
537 N_visible = 0;
538 for (l = scgs; l; l = l->next) {
539 SheetControlGUI *scg1 = l->data;
540 Sheet *sheet = scg_sheet (scg1);
541 if (!sheet_is_visible (sheet))
542 continue;
544 N_visible++;
546 item = gtk_menu_item_new_with_label (sheet->name_unquoted);
547 g_signal_connect_swapped (G_OBJECT (item), "activate",
548 G_CALLBACK (cb_show_sheet), scg1);
549 gtk_menu_shell_append (GTK_MENU_SHELL (submenus[pass]), item);
550 gtk_widget_show (item);
553 scgs = g_slist_sort (scgs, cb_by_scg_sheet_name);
555 g_slist_free (scgs);
557 for (ui = 0; ui < G_N_ELEMENTS (sheet_label_context_actions); ui++) {
558 const struct SheetTabMenu *it =
559 sheet_label_context_actions + ui;
560 gboolean inactive =
561 ((it->flags & CM_MULTIPLE) && N_visible <= 1) ||
562 ((it->flags & CM_DATA_SHEET) && scg_sheet (scg)->sheet_type != GNM_SHEET_DATA) ||
563 (!it->submenu && guru != NULL);
565 item = it->text
566 ? gtk_menu_item_new_with_label (_(it->text))
567 : gtk_separator_menu_item_new ();
568 if (it->function)
569 g_signal_connect_swapped (G_OBJECT (item), "activate",
570 G_CALLBACK (it->function), scg);
571 if (it->submenu)
572 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
573 submenus[it->submenu]);
575 gtk_widget_set_sensitive (item, !inactive);
576 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
577 gtk_widget_show (item);
580 gnumeric_popup_menu (GTK_MENU (menu), event);
584 * cb_sheet_label_button_press:
586 * Invoked when the user has clicked on the sheet name widget.
587 * This takes care of switching to the notebook that contains the label
589 static gboolean
590 cb_sheet_label_button_press (GtkWidget *widget, GdkEvent *event,
591 SheetControlGUI *scg)
593 WBCGtk *wbcg = scg->wbcg;
594 gint page_number;
596 if (event->type != GDK_BUTTON_PRESS)
597 return FALSE;
599 page_number = gtk_notebook_page_num (wbcg->snotebook,
600 GTK_WIDGET (scg->grid));
601 gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
603 if (event->button.button == 1 || NULL != wbcg->rangesel)
604 return FALSE;
606 if (event->button.button == 3) {
607 if ((scg_wbcg (scg))->edit_line.guru == NULL)
608 scg_object_unselect (scg, NULL);
609 if (g_object_get_data (G_OBJECT (widget), "editable")) {
610 sheet_menu_label_run (scg, event);
611 scg_take_focus (scg);
612 return TRUE;
616 return FALSE;
619 static void
620 cb_sheet_label_drag_data_get (GtkWidget *widget, GdkDragContext *context,
621 GtkSelectionData *selection_data,
622 guint info, guint time)
624 SheetControlGUI *scg = get_scg (widget);
625 g_return_if_fail (GNM_IS_SCG (scg));
627 scg_drag_data_get (scg, selection_data);
630 static void
631 cb_sheet_label_drag_data_received (GtkWidget *widget, GdkDragContext *context,
632 gint x, gint y, GtkSelectionData *data, guint info, guint time,
633 WBCGtk *wbcg)
635 GtkWidget *w_source;
636 SheetControlGUI *scg_src, *scg_dst;
637 Sheet *s_src, *s_dst;
639 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
640 g_return_if_fail (GTK_IS_WIDGET (widget));
642 w_source = gtk_drag_get_source_widget (context);
643 if (!w_source) {
644 g_warning ("Not yet implemented!"); /* Different process */
645 return;
648 scg_src = get_scg (w_source);
649 g_return_if_fail (scg_src != NULL);
650 s_src = scg_sheet (scg_src);
652 scg_dst = get_scg (widget);
653 g_return_if_fail (scg_dst != NULL);
654 s_dst = scg_sheet (scg_dst);
656 if (s_src == s_dst) {
657 /* Nothing */
658 } else if (s_src->workbook == s_dst->workbook) {
659 /* Move within workbook */
660 Workbook *wb = s_src->workbook;
661 int p_src = s_src->index_in_wb;
662 int p_dst = s_dst->index_in_wb;
663 WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
664 workbook_sheet_move (s_src, p_dst - p_src);
665 cmd_reorganize_sheets (GNM_WBC (wbcg),
666 old_state,
667 s_src);
668 } else {
669 g_return_if_fail (GNM_IS_SCG (gtk_selection_data_get_data (data)));
671 /* Different workbook, same process */
672 g_warning ("Not yet implemented!");
677 * Not currently reachable, I believe. We use the notebook's dragging.
679 static void
680 cb_sheet_label_drag_begin (GtkWidget *widget, GdkDragContext *context,
681 WBCGtk *wbcg)
683 GtkWidget *arrow, *image;
685 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
687 /* Create the arrow. */
688 arrow = gtk_window_new (GTK_WINDOW_POPUP);
689 gtk_window_set_screen (GTK_WINDOW (arrow),
690 gtk_widget_get_screen (widget));
691 gtk_widget_realize (arrow);
692 image = gtk_image_new_from_resource ("/org/gnumeric/gnumeric/images/sheet_move_marker.png");
693 gtk_widget_show (image);
694 gtk_container_add (GTK_CONTAINER (arrow), image);
695 g_object_ref_sink (arrow);
696 g_object_set_data (G_OBJECT (widget), "arrow", arrow);
699 static void
700 cb_sheet_label_drag_end (GtkWidget *widget, GdkDragContext *context,
701 WBCGtk *wbcg)
703 GtkWidget *arrow;
705 g_return_if_fail (GNM_IS_WBC (wbcg));
707 /* Destroy the arrow. */
708 arrow = g_object_get_data (G_OBJECT (widget), "arrow");
709 gtk_widget_destroy (arrow);
710 g_object_unref (arrow);
711 g_object_set_data (G_OBJECT (widget), "arrow", NULL);
714 static void
715 cb_sheet_label_drag_leave (GtkWidget *widget, GdkDragContext *context,
716 guint time, WBCGtk *wbcg)
718 GtkWidget *w_source, *arrow;
720 /* Hide the arrow. */
721 w_source = gtk_drag_get_source_widget (context);
722 if (w_source) {
723 arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
724 gtk_widget_hide (arrow);
728 static gboolean
729 cb_sheet_label_drag_motion (GtkWidget *widget, GdkDragContext *context,
730 gint x, gint y, guint time, WBCGtk *wbcg)
732 SheetControlGUI *scg_src, *scg_dst;
733 GtkWidget *w_source, *arrow, *window;
734 gint root_x, root_y, pos_x, pos_y;
735 GtkAllocation wa, wsa;
737 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
738 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);
740 /* Make sure we are really hovering over another label. */
741 w_source = gtk_drag_get_source_widget (context);
742 if (!w_source)
743 return FALSE;
745 arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
747 scg_src = get_scg (w_source);
748 scg_dst = get_scg (widget);
750 if (scg_src == scg_dst) {
751 gtk_widget_hide (arrow);
752 return (FALSE);
755 /* Move the arrow to the correct position and show it. */
756 window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
757 gtk_window_get_position (GTK_WINDOW (window), &root_x, &root_y);
758 gtk_widget_get_allocation (widget ,&wa);
759 pos_x = root_x + wa.x;
760 pos_y = root_y + wa.y;
761 gtk_widget_get_allocation (w_source ,&wsa);
762 if (wsa.x < wa.x)
763 pos_x += wa.width;
764 gtk_window_move (GTK_WINDOW (arrow), pos_x, pos_y);
765 gtk_widget_show (arrow);
767 return (TRUE);
770 static void
771 set_dir (GtkWidget *w, GtkTextDirection *dir)
773 gtk_widget_set_direction (w, *dir);
774 if (GTK_IS_CONTAINER (w))
775 gtk_container_foreach (GTK_CONTAINER (w),
776 (GtkCallback)&set_dir,
777 dir);
780 static void
781 wbcg_set_direction (SheetControlGUI const *scg)
783 GtkWidget *w = (GtkWidget *)scg->wbcg->snotebook;
784 gboolean text_is_rtl = scg_sheet (scg)->text_is_rtl;
785 GtkTextDirection dir = text_is_rtl
786 ? GTK_TEXT_DIR_RTL
787 : GTK_TEXT_DIR_LTR;
789 if (dir != gtk_widget_get_direction (w))
790 set_dir (w, &dir);
791 if (scg->hs)
792 g_object_set (scg->hs, "inverted", text_is_rtl, NULL);
795 static void
796 cb_direction_change (G_GNUC_UNUSED Sheet *null_sheet,
797 G_GNUC_UNUSED GParamSpec *null_pspec,
798 SheetControlGUI const *scg)
800 if (scg && scg == wbcg_cur_scg (scg->wbcg))
801 wbcg_set_direction (scg);
804 static void
805 wbcg_update_menu_feedback (WBCGtk *wbcg, Sheet const *sheet)
807 g_return_if_fail (IS_SHEET (sheet));
809 if (!wbcg_ui_update_begin (wbcg))
810 return;
812 wbc_gtk_set_toggle_action_state (wbcg,
813 "SheetDisplayFormulas", sheet->display_formulas);
814 wbc_gtk_set_toggle_action_state (wbcg,
815 "SheetHideZeros", sheet->hide_zero);
816 wbc_gtk_set_toggle_action_state (wbcg,
817 "SheetHideGridlines", sheet->hide_grid);
818 wbc_gtk_set_toggle_action_state (wbcg,
819 "SheetHideColHeader", sheet->hide_col_header);
820 wbc_gtk_set_toggle_action_state (wbcg,
821 "SheetHideRowHeader", sheet->hide_row_header);
822 wbc_gtk_set_toggle_action_state (wbcg,
823 "SheetDisplayOutlines", sheet->display_outlines);
824 wbc_gtk_set_toggle_action_state (wbcg,
825 "SheetOutlineBelow", sheet->outline_symbols_below);
826 wbc_gtk_set_toggle_action_state (wbcg,
827 "SheetOutlineRight", sheet->outline_symbols_right);
828 wbc_gtk_set_toggle_action_state (wbcg,
829 "SheetUseR1C1", sheet->convs->r1c1_addresses);
830 wbcg_ui_update_end (wbcg);
833 static void
834 cb_zoom_change (Sheet *sheet,
835 G_GNUC_UNUSED GParamSpec *null_pspec,
836 WBCGtk *wbcg)
838 if (wbcg_ui_update_begin (wbcg)) {
839 int pct = sheet->last_zoom_factor_used * 100 + .5;
840 char *label = g_strdup_printf ("%d%%", pct);
841 go_action_combo_text_set_entry (wbcg->zoom_haction, label,
842 GO_ACTION_COMBO_SEARCH_CURRENT);
843 g_free (label);
844 wbcg_ui_update_end (wbcg);
848 static void
849 cb_notebook_switch_page (G_GNUC_UNUSED GtkNotebook *notebook_,
850 G_GNUC_UNUSED GtkWidget *page_,
851 guint page_num, WBCGtk *wbcg)
853 Sheet *sheet;
854 SheetControlGUI *new_scg;
856 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
858 /* Ignore events during destruction */
859 if (wbcg->snotebook == NULL)
860 return;
862 if (debug_tab_order)
863 g_printerr ("Notebook page switch\n");
865 /* While initializing adding the sheets will trigger page changes, but
866 * we do not actually want to change the focus sheet for the view
868 if (wbcg->updating_ui)
869 return;
871 /* If we are not at a subexpression boundary then finish editing */
872 if (NULL != wbcg->rangesel)
873 scg_rangesel_stop (wbcg->rangesel, TRUE);
876 * Make snotebook follow bnotebook. This should be the only place
877 * that changes pages for snotebook.
879 gtk_notebook_set_current_page (wbcg->snotebook, page_num);
881 new_scg = wbcg_get_nth_scg (wbcg, page_num);
882 wbcg_set_direction (new_scg);
884 if (wbcg_is_editing (wbcg) && wbcg_rangesel_possible (wbcg)) {
886 * When we are editing, sheet changes are not done fully.
887 * We revert to the original sheet later.
889 * On the other hand, when we are selecting a range for a
890 * dialog, we do change sheet fully.
892 scg_take_focus (new_scg);
893 return;
896 gnm_expr_entry_set_scg (wbcg->edit_line.entry, new_scg);
899 * Make absolutely sure the expression doesn't get 'lost',
900 * if it's invalid then prompt the user and don't switch
901 * the notebook page.
903 if (wbcg_is_editing (wbcg)) {
904 guint prev = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (wbcg->snotebook),
905 "previous_page"));
907 if (prev == page_num)
908 return;
910 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
911 gnm_notebook_set_current_page (wbcg->bnotebook,
912 prev);
913 else
914 /* Looks silly, but is really neccesarry */
915 gnm_notebook_set_current_page (wbcg->bnotebook,
916 page_num);
918 return;
921 g_object_set_data (G_OBJECT (wbcg->snotebook), "previous_page",
922 GINT_TO_POINTER (gtk_notebook_get_current_page (wbcg->snotebook)));
924 /* if we are not selecting a range for an expression update */
925 sheet = wbcg_focus_cur_scg (wbcg);
926 if (sheet != wbcg_cur_sheet (wbcg)) {
927 wbcg_update_menu_feedback (wbcg, sheet);
928 sheet_flag_status_update_range (sheet, NULL);
929 sheet_update (sheet);
930 wb_view_sheet_focus (wb_control_view (GNM_WBC (wbcg)), sheet);
931 cb_zoom_change (sheet, NULL, wbcg);
935 static gboolean
936 cb_bnotebook_button_press (GtkWidget *widget, GdkEventButton *event)
938 if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
940 * Eat the click so cb_paned_button_press doesn't see it.
941 * see bug #607794.
943 return TRUE;
946 return FALSE;
949 static void
950 cb_bnotebook_page_reordered (GtkNotebook *notebook, GtkWidget *child,
951 int page_num, WBCGtk *wbcg)
953 GtkNotebook *snotebook = GTK_NOTEBOOK (wbcg->snotebook);
954 int old = gtk_notebook_get_current_page (snotebook);
956 if (wbcg->updating_ui)
957 return;
959 if (debug_tab_order)
960 g_printerr ("Reordered %d -> %d\n", old, page_num);
962 if (old != page_num) {
963 WorkbookControl * wbc = GNM_WBC (wbcg);
964 Workbook *wb = wb_control_get_workbook (wbc);
965 Sheet *sheet = workbook_sheet_by_index (wb, old);
966 WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
967 workbook_sheet_move (sheet, page_num - old);
968 cmd_reorganize_sheets (wbc, old_state, sheet);
973 static void
974 wbc_gtk_create_notebook_area (WBCGtk *wbcg)
976 GtkWidget *placeholder;
978 wbcg->bnotebook = g_object_new (GNM_NOTEBOOK_TYPE,
979 "can-focus", FALSE,
980 NULL);
981 g_object_ref (wbcg->bnotebook);
983 g_signal_connect_after (G_OBJECT (wbcg->bnotebook),
984 "switch_page",
985 G_CALLBACK (cb_notebook_switch_page), wbcg);
986 g_signal_connect (G_OBJECT (wbcg->bnotebook),
987 "button-press-event",
988 G_CALLBACK (cb_bnotebook_button_press),
989 NULL);
990 g_signal_connect (G_OBJECT (wbcg->bnotebook),
991 "page-reordered",
992 G_CALLBACK (cb_bnotebook_page_reordered),
993 wbcg);
994 placeholder = gtk_paned_get_child1 (wbcg->tabs_paned);
995 if (placeholder)
996 gtk_widget_destroy (placeholder);
997 gtk_paned_pack1 (wbcg->tabs_paned, GTK_WIDGET (wbcg->bnotebook), FALSE, TRUE);
999 gtk_widget_show_all (GTK_WIDGET (wbcg->tabs_paned));
1003 static void
1004 wbcg_menu_state_sheet_count (WBCGtk *wbcg)
1006 int const sheet_count = gnm_notebook_get_n_visible (wbcg->bnotebook);
1007 /* Should we enable commands requiring multiple sheets */
1008 gboolean const multi_sheet = (sheet_count > 1);
1010 wbc_gtk_set_action_sensitivity (wbcg, "SheetRemove", multi_sheet);
1013 static void
1014 cb_sheet_direction_change (Sheet *sheet,
1015 G_GNUC_UNUSED GParamSpec *pspec,
1016 GtkAction *a)
1018 g_object_set (a,
1019 "icon-name", (sheet->text_is_rtl
1020 ? "format-text-direction-rtl"
1021 : "format-text-direction-ltr"),
1022 NULL);
1025 static void
1026 cb_sheet_tab_change (Sheet *sheet,
1027 G_GNUC_UNUSED GParamSpec *pspec,
1028 GtkWidget *widget)
1030 GdkRGBA cfore, cback;
1031 SheetControlGUI *scg = get_scg (widget);
1033 g_return_if_fail (GNM_IS_SCG (scg));
1035 /* We're lazy and just set all relevant attributes. */
1036 g_object_set (widget,
1037 "label", sheet->name_unquoted,
1038 "background-color",
1039 (sheet->tab_color
1040 ? go_color_to_gdk_rgba (sheet->tab_color->go_color,
1041 &cback)
1042 : NULL),
1043 "text-color",
1044 (sheet->tab_text_color
1045 ? go_color_to_gdk_rgba (sheet->tab_text_color->go_color,
1046 &cfore)
1047 : NULL),
1048 NULL);
1051 static void
1052 cb_toggle_menu_item_changed (Sheet *sheet,
1053 G_GNUC_UNUSED GParamSpec *pspec,
1054 WBCGtk *wbcg)
1056 /* We're lazy and just update all. */
1057 wbcg_update_menu_feedback (wbcg, sheet);
1060 static void
1061 cb_sheet_visibility_change (Sheet *sheet,
1062 G_GNUC_UNUSED GParamSpec *pspec,
1063 SheetControlGUI *scg)
1065 gboolean viz;
1067 g_return_if_fail (GNM_IS_SCG (scg));
1069 viz = sheet_is_visible (sheet);
1070 gtk_widget_set_visible (GTK_WIDGET (scg->grid), viz);
1071 gtk_widget_set_visible (GTK_WIDGET (scg->label), viz);
1073 wbcg_menu_state_sheet_count (scg->wbcg);
1076 static void
1077 disconnect_sheet_focus_signals (WBCGtk *wbcg)
1079 SheetControlGUI *scg = wbcg->active_scg;
1080 Sheet *sheet;
1082 if (!scg)
1083 return;
1085 sheet = scg_sheet (scg);
1087 #if 0
1088 g_printerr ("Disconnecting focus for %s with scg=%p\n", sheet->name_unquoted, scg);
1089 #endif
1091 g_signal_handlers_disconnect_by_func (sheet, cb_toggle_menu_item_changed, wbcg);
1092 g_signal_handlers_disconnect_by_func (sheet, cb_direction_change, scg);
1093 g_signal_handlers_disconnect_by_func (sheet, cb_zoom_change, wbcg);
1095 wbcg->active_scg = NULL;
1098 static void
1099 disconnect_sheet_signals (SheetControlGUI *scg)
1101 WBCGtk *wbcg = scg->wbcg;
1102 Sheet *sheet = scg_sheet (scg);
1104 if (scg == wbcg->active_scg)
1105 disconnect_sheet_focus_signals (wbcg);
1107 #if 0
1108 g_printerr ("Disconnecting all for %s with scg=%p\n", sheet->name_unquoted, scg);
1109 #endif
1111 g_signal_handlers_disconnect_by_func (sheet, cb_sheet_direction_change,
1112 wbcg_find_action (wbcg, "SheetDirection"));
1113 g_signal_handlers_disconnect_by_func (sheet, cb_sheet_tab_change, scg->label);
1114 g_signal_handlers_disconnect_by_func (sheet, cb_sheet_visibility_change, scg);
1117 static void
1118 wbcg_sheet_add (WorkbookControl *wbc, SheetView *sv)
1120 static GtkTargetEntry const drag_types[] = {
1121 { (char *)"GNUMERIC_SHEET", GTK_TARGET_SAME_APP, TARGET_SHEET },
1122 { (char *)"UTF8_STRING", 0, 0 },
1123 { (char *)"image/svg+xml", 0, 0 },
1124 { (char *)"image/x-wmf", 0, 0 },
1125 { (char *)"image/x-emf", 0, 0 },
1126 { (char *)"image/png", 0, 0 },
1127 { (char *)"image/jpeg", 0, 0 },
1128 { (char *)"image/bmp", 0, 0 }
1131 WBCGtk *wbcg = (WBCGtk *)wbc;
1132 SheetControlGUI *scg;
1133 Sheet *sheet = sv_sheet (sv);
1134 gboolean visible = sheet_is_visible (sheet);
1136 g_return_if_fail (wbcg != NULL);
1138 scg = sheet_control_gui_new (sv, wbcg);
1140 g_object_set_data (G_OBJECT (scg->grid), SHEET_CONTROL_KEY, scg);
1142 g_object_set_data (G_OBJECT (scg->label), SHEET_CONTROL_KEY, scg);
1144 /* do not preempt the editable label handler */
1145 g_signal_connect_after (G_OBJECT (scg->label),
1146 "button_press_event",
1147 G_CALLBACK (cb_sheet_label_button_press), scg);
1149 /* Drag & Drop */
1150 gtk_drag_source_set (scg->label, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
1151 drag_types, G_N_ELEMENTS (drag_types),
1152 GDK_ACTION_MOVE);
1153 gtk_drag_dest_set (scg->label, GTK_DEST_DEFAULT_ALL,
1154 drag_types, G_N_ELEMENTS (drag_types),
1155 GDK_ACTION_MOVE);
1156 g_object_connect (G_OBJECT (scg->label),
1157 "signal::drag_begin", G_CALLBACK (cb_sheet_label_drag_begin), wbcg,
1158 "signal::drag_end", G_CALLBACK (cb_sheet_label_drag_end), wbcg,
1159 "signal::drag_leave", G_CALLBACK (cb_sheet_label_drag_leave), wbcg,
1160 "signal::drag_data_get", G_CALLBACK (cb_sheet_label_drag_data_get), NULL,
1161 "signal::drag_data_received", G_CALLBACK (cb_sheet_label_drag_data_received), wbcg,
1162 "signal::drag_motion", G_CALLBACK (cb_sheet_label_drag_motion), wbcg,
1163 NULL);
1165 gtk_widget_show (scg->label);
1166 gtk_widget_show_all (GTK_WIDGET (scg->grid));
1167 if (!visible) {
1168 gtk_widget_hide (GTK_WIDGET (scg->grid));
1169 gtk_widget_hide (GTK_WIDGET (scg->label));
1171 g_object_connect (G_OBJECT (sheet),
1172 "signal::notify::visibility", cb_sheet_visibility_change, scg,
1173 "signal::notify::name", cb_sheet_tab_change, scg->label,
1174 "signal::notify::tab-foreground", cb_sheet_tab_change, scg->label,
1175 "signal::notify::tab-background", cb_sheet_tab_change, scg->label,
1176 "signal::notify::text-is-rtl", cb_sheet_direction_change, wbcg_find_action (wbcg, "SheetDirection"),
1177 NULL);
1179 if (wbcg_ui_update_begin (wbcg)) {
1181 * Just let wbcg_sheet_order_changed deal with where to put
1182 * it.
1184 int pos = -1;
1185 gtk_notebook_insert_page (wbcg->snotebook,
1186 GTK_WIDGET (scg->grid), NULL,
1187 pos);
1188 gnm_notebook_insert_tab (wbcg->bnotebook,
1189 GTK_WIDGET (scg->label),
1190 pos);
1191 wbcg_menu_state_sheet_count (wbcg);
1192 wbcg_ui_update_end (wbcg);
1195 scg_adjust_preferences (scg);
1196 if (sheet == wb_control_cur_sheet (wbc)) {
1197 scg_take_focus (scg);
1198 wbcg_set_direction (scg);
1199 cb_zoom_change (sheet, NULL, wbcg);
1200 cb_toggle_menu_item_changed (sheet, NULL, wbcg);
1204 static void
1205 wbcg_sheet_remove (WorkbookControl *wbc, Sheet *sheet)
1207 WBCGtk *wbcg = (WBCGtk *)wbc;
1208 SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1210 /* During destruction we may have already removed the notebook */
1211 if (scg == NULL)
1212 return;
1214 disconnect_sheet_signals (scg);
1216 gtk_widget_destroy (GTK_WIDGET (scg->label));
1217 gtk_widget_destroy (GTK_WIDGET (scg->grid));
1219 wbcg_menu_state_sheet_count (wbcg);
1222 static void
1223 wbcg_sheet_focus (WorkbookControl *wbc, Sheet *sheet)
1225 WBCGtk *wbcg = (WBCGtk *)wbc;
1226 SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1228 if (scg) {
1229 int n = gtk_notebook_page_num (wbcg->snotebook,
1230 GTK_WIDGET (scg->grid));
1231 gnm_notebook_set_current_page (wbcg->bnotebook, n);
1233 if (wbcg->rangesel == NULL)
1234 gnm_expr_entry_set_scg (wbcg->edit_line.entry, scg);
1237 disconnect_sheet_focus_signals (wbcg);
1239 if (sheet) {
1240 wbcg_update_menu_feedback (wbcg, sheet);
1242 if (scg)
1243 wbcg_set_direction (scg);
1245 #if 0
1246 g_printerr ("Connecting for %s with scg=%p\n", sheet->name_unquoted, scg);
1247 #endif
1249 g_object_connect
1250 (G_OBJECT (sheet),
1251 "signal::notify::display-formulas", cb_toggle_menu_item_changed, wbcg,
1252 "signal::notify::display-zeros", cb_toggle_menu_item_changed, wbcg,
1253 "signal::notify::display-grid", cb_toggle_menu_item_changed, wbcg,
1254 "signal::notify::display-column-header", cb_toggle_menu_item_changed, wbcg,
1255 "signal::notify::display-row-header", cb_toggle_menu_item_changed, wbcg,
1256 "signal::notify::display-outlines", cb_toggle_menu_item_changed, wbcg,
1257 "signal::notify::display-outlines-below", cb_toggle_menu_item_changed, wbcg,
1258 "signal::notify::display-outlines-right", cb_toggle_menu_item_changed, wbcg,
1259 "signal::notify::text-is-rtl", cb_direction_change, scg,
1260 "signal::notify::zoom-factor", cb_zoom_change, wbcg,
1261 NULL);
1263 wbcg->active_scg = scg;
1267 static gint
1268 by_sheet_index (gconstpointer a, gconstpointer b)
1270 SheetControlGUI *scga = (SheetControlGUI *)a;
1271 SheetControlGUI *scgb = (SheetControlGUI *)b;
1272 return scg_sheet (scga)->index_in_wb - scg_sheet (scgb)->index_in_wb;
1275 static void
1276 wbcg_sheet_order_changed (WBCGtk *wbcg)
1278 if (wbcg_ui_update_begin (wbcg)) {
1279 GSList *l, *scgs;
1280 int i;
1282 /* Reorder all tabs so they end up in index_in_wb order. */
1283 scgs = g_slist_sort (get_all_scgs (wbcg), by_sheet_index);
1285 for (i = 0, l = scgs; l; l = l->next, i++) {
1286 SheetControlGUI *scg = l->data;
1287 gtk_notebook_reorder_child (wbcg->snotebook,
1288 GTK_WIDGET (scg->grid),
1290 gnm_notebook_move_tab (wbcg->bnotebook,
1291 GTK_WIDGET (scg->label),
1294 g_slist_free (scgs);
1296 wbcg_ui_update_end (wbcg);
1300 static void
1301 wbcg_update_title (WBCGtk *wbcg)
1303 GODoc *doc = wb_control_get_doc (GNM_WBC (wbcg));
1304 char *basename = doc->uri ? go_basename_from_uri (doc->uri) : NULL;
1305 char *title = g_strconcat
1306 (go_doc_is_dirty (doc) ? "*" : "",
1307 basename ? basename : doc->uri,
1308 _(" - Gnumeric"),
1309 NULL);
1310 gtk_window_set_title (wbcg_toplevel (wbcg), title);
1311 g_free (title);
1312 g_free (basename);
1315 static void
1316 wbcg_sheet_remove_all (WorkbookControl *wbc)
1318 WBCGtk *wbcg = (WBCGtk *)wbc;
1320 if (wbcg->snotebook != NULL) {
1321 GtkNotebook *tmp = wbcg->snotebook;
1322 GSList *l, *all = get_all_scgs (wbcg);
1323 SheetControlGUI *current = wbcg_cur_scg (wbcg);
1325 /* Clear notebook to disable updates as focus changes for pages
1326 * during destruction */
1327 wbcg->snotebook = NULL;
1329 /* Be sure we are no longer editing */
1330 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1332 for (l = all; l; l = l->next) {
1333 SheetControlGUI *scg = l->data;
1334 disconnect_sheet_signals (scg);
1335 if (scg != current) {
1336 gtk_widget_destroy (GTK_WIDGET (scg->label));
1337 gtk_widget_destroy (GTK_WIDGET (scg->grid));
1341 g_slist_free (all);
1343 /* Do current scg last. */
1344 if (current) {
1345 gtk_widget_destroy (GTK_WIDGET (current->label));
1346 gtk_widget_destroy (GTK_WIDGET (current->grid));
1349 wbcg->snotebook = tmp;
1353 static double
1354 color_diff (const GdkRGBA *a, const GdkRGBA *b)
1356 /* Ignoring alpha. */
1357 return ((a->red - b->red) * (a->red - b->red) +
1358 (a->green - b->green) * (a->green - b->green) +
1359 (a->blue - b->blue) * (a->blue - b->blue));
1363 static gboolean
1364 cb_adjust_foreground_attributes (PangoAttribute *attribute,
1365 gpointer data)
1367 const GdkRGBA *back = data;
1369 if (attribute->klass->type == PANGO_ATTR_FOREGROUND) {
1370 PangoColor *pfore = &((PangoAttrColor *)attribute)->color;
1371 GdkRGBA fore;
1372 const double threshold = 0.01;
1374 fore.red = pfore->red / 65535.0;
1375 fore.green = pfore->green / 65535.0;
1376 fore.blue = pfore->blue / 65535.0;
1378 if (color_diff (&fore, back) < threshold) {
1379 static const GdkRGBA black = { 0, 0, 0, 1 };
1380 static const GdkRGBA white = { 1, 1, 1, 1 };
1381 double back_norm = color_diff (back, &black);
1382 double f = 0.2;
1383 const GdkRGBA *ref =
1384 back_norm > 0.75 ? &black : &white;
1386 #define DO_CHANNEL(channel) \
1387 do { \
1388 double val = fore.channel * (1 - f) + ref->channel * f; \
1389 pfore->channel = CLAMP (val, 0, 1) * 65535; \
1390 } while (0)
1391 DO_CHANNEL(red);
1392 DO_CHANNEL(green);
1393 DO_CHANNEL(blue);
1394 #undef DO_CHANNEL
1397 return FALSE;
1400 static void
1401 adjust_foreground_attributes (PangoAttrList *attrs, GtkWidget *w)
1403 GdkRGBA c;
1404 GtkStyleContext *ctxt = gtk_widget_get_style_context (w);
1406 gtk_style_context_get_background_color (ctxt, GTK_STATE_FLAG_NORMAL,
1407 &c);
1409 if (0)
1410 g_printerr ("back=%s\n", gdk_rgba_to_string (&c));
1412 pango_attr_list_unref
1413 (pango_attr_list_filter
1414 (attrs,
1415 cb_adjust_foreground_attributes,
1416 &c));
1420 static void
1421 wbcg_auto_expr_value_changed (WorkbookView *wbv,
1422 G_GNUC_UNUSED GParamSpec *pspec,
1423 WBCGtk *wbcg)
1425 GtkLabel *lbl = GTK_LABEL (wbcg->auto_expr_label);
1426 GnmValue const *v = wbv->auto_expr.value;
1428 if (v) {
1429 GOFormat const *format = VALUE_FMT (v);
1430 GString *str = g_string_new (wbv->auto_expr.descr);
1431 PangoAttrList *attrs = NULL;
1433 g_string_append (str, " = ");
1435 if (format) {
1436 PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (wbcg->toplevel), NULL);
1437 gsize old_len = str->len;
1438 GODateConventions const *date_conv = workbook_date_conv (wb_view_get_workbook (wbv));
1439 int max_width = go_format_is_general (format) && VALUE_IS_NUMBER (v)
1440 ? 14
1441 : strlen (AUTO_EXPR_SAMPLE) - g_utf8_strlen (str->str, -1);
1442 GOFormatNumberError err =
1443 format_value_layout (layout, format, v,
1444 max_width, date_conv);
1445 switch (err) {
1446 case GO_FORMAT_NUMBER_OK:
1447 case GO_FORMAT_NUMBER_DATE_ERROR: {
1448 PangoAttrList *atl;
1450 go_pango_translate_layout (layout); /* translating custom attributes */
1451 g_string_append (str, pango_layout_get_text (layout));
1452 /* We need to shift the attribute list */
1453 atl = pango_attr_list_ref (pango_layout_get_attributes (layout));
1454 if (atl != NULL) {
1455 attrs = pango_attr_list_new ();
1456 pango_attr_list_splice
1457 (attrs, atl, old_len,
1458 str->len - old_len);
1459 pango_attr_list_unref (atl);
1460 /* Adjust colours to make text visible. */
1461 adjust_foreground_attributes
1462 (attrs,
1463 gtk_widget_get_parent (GTK_WIDGET (lbl)));
1465 break;
1467 default:
1468 case GO_FORMAT_NUMBER_INVALID_FORMAT:
1469 g_string_append (str, _("Invalid format"));
1470 break;
1472 g_object_unref (layout);
1473 } else
1474 g_string_append (str, value_peek_string (v));
1476 gtk_label_set_text (lbl, str->str);
1477 gtk_label_set_attributes (lbl, attrs);
1479 pango_attr_list_unref (attrs);
1480 g_string_free (str, TRUE);
1481 } else {
1482 gtk_label_set_text (lbl, "");
1483 gtk_label_set_attributes (lbl, NULL);
1487 static void
1488 wbcg_scrollbar_visibility (WorkbookView *wbv,
1489 G_GNUC_UNUSED GParamSpec *pspec,
1490 WBCGtk *wbcg)
1492 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
1493 scg_adjust_preferences (scg);
1496 static void
1497 wbcg_notebook_tabs_visibility (WorkbookView *wbv,
1498 G_GNUC_UNUSED GParamSpec *pspec,
1499 WBCGtk *wbcg)
1501 gtk_widget_set_visible (GTK_WIDGET (wbcg->bnotebook),
1502 wbv->show_notebook_tabs);
1506 static void
1507 wbcg_menu_state_update (WorkbookControl *wbc, int flags)
1509 WBCGtk *wbcg = (WBCGtk *)wbc;
1510 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
1511 SheetView const *sv = wb_control_cur_sheet_view (wbc);
1512 Sheet const *sheet = wb_control_cur_sheet (wbc);
1513 gboolean const has_guru = wbc_gtk_get_guru (wbcg) != NULL;
1514 gboolean edit_object = scg != NULL &&
1515 (scg->selected_objects != NULL || wbcg->new_object != NULL);
1516 gboolean has_print_area;
1518 if (MS_INSERT_COLS & flags)
1519 wbc_gtk_set_action_sensitivity (wbcg, "InsertColumns",
1520 sv->enable_insert_cols);
1521 if (MS_INSERT_ROWS & flags)
1522 wbc_gtk_set_action_sensitivity (wbcg, "InsertRows",
1523 sv->enable_insert_rows);
1524 if (MS_INSERT_CELLS & flags)
1525 wbc_gtk_set_action_sensitivity (wbcg, "InsertCells",
1526 sv->enable_insert_cells);
1527 if (MS_SHOWHIDE_DETAIL & flags) {
1528 wbc_gtk_set_action_sensitivity (wbcg, "DataOutlineShowDetail",
1529 sheet->priv->enable_showhide_detail);
1530 wbc_gtk_set_action_sensitivity (wbcg, "DataOutlineHideDetail",
1531 sheet->priv->enable_showhide_detail);
1533 if (MS_PASTE_SPECIAL & flags)
1534 wbc_gtk_set_action_sensitivity (wbcg, "EditPasteSpecial",
1535 // Inter-process paste special is now allowed
1536 !gnm_app_clipboard_is_cut () &&
1537 !edit_object);
1538 if (MS_PRINT_SETUP & flags)
1539 wbc_gtk_set_action_sensitivity (wbcg, "FilePageSetup", !has_guru);
1540 if (MS_SEARCH_REPLACE & flags)
1541 wbc_gtk_set_action_sensitivity (wbcg, "EditReplace", !has_guru);
1542 if (MS_DEFINE_NAME & flags) {
1543 wbc_gtk_set_action_sensitivity (wbcg, "EditNames", !has_guru);
1544 wbc_gtk_set_action_sensitivity (wbcg, "InsertNames", !has_guru);
1546 if (MS_CONSOLIDATE & flags)
1547 wbc_gtk_set_action_sensitivity (wbcg, "DataConsolidate", !has_guru);
1548 if (MS_FILTER_STATE_CHANGED & flags)
1549 wbc_gtk_set_action_sensitivity (wbcg, "DataFilterShowAll", sheet->has_filtered_rows);
1550 if (MS_SHOW_PRINTAREA & flags) {
1551 GnmRange *print_area = sheet_get_nominal_printarea (sheet);
1552 has_print_area = (print_area != NULL);
1553 g_free (print_area);
1554 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaClear", has_print_area);
1555 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaShow", has_print_area);
1557 if (MS_PAGE_BREAKS & flags) {
1558 gint col = sv->edit_pos.col;
1559 gint row = sv->edit_pos.row;
1560 GnmPrintInformation *pi = sheet->print_info;
1561 char const* new_label = NULL;
1562 char const *new_tip = NULL;
1564 if (pi->page_breaks.v != NULL &&
1565 gnm_page_breaks_get_break (pi->page_breaks.v, col) == GNM_PAGE_BREAK_MANUAL) {
1566 new_label = _("Remove Column Page Break");
1567 new_tip = _("Remove the page break to the left of the current column");
1568 } else {
1569 new_label = _("Add Column Page Break");
1570 new_tip = _("Add a page break to the left of the current column");
1572 wbc_gtk_set_action_label (wbcg, "FilePrintAreaToggleColPageBreak",
1573 NULL, new_label, new_tip);
1574 if (pi->page_breaks.h != NULL &&
1575 gnm_page_breaks_get_break (pi->page_breaks.h, col) == GNM_PAGE_BREAK_MANUAL) {
1576 new_label = _("Remove Row Page Break");
1577 new_tip = _("Remove the page break above the current row");
1578 } else {
1579 new_label = _("Add Row Page Break");
1580 new_tip = _("Add a page break above current row");
1582 wbc_gtk_set_action_label (wbcg, "FilePrintAreaToggleRowPageBreak",
1583 NULL, new_label, new_tip);
1584 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaToggleRowPageBreak",
1585 row != 0);
1586 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaToggleColPageBreak",
1587 col != 0);
1588 wbc_gtk_set_action_sensitivity (wbcg, "FilePrintAreaClearAllPageBreak",
1589 print_info_has_manual_breaks (sheet->print_info));
1591 if (MS_SELECT_OBJECT & flags) {
1592 wbc_gtk_set_action_sensitivity (wbcg, "EditSelectObject",
1593 sheet->sheet_objects != NULL);
1596 if (MS_FREEZE_VS_THAW & flags) {
1597 /* Cheat and use the same accelerator for both states because
1598 * we don't reset it when the label changes */
1599 char const* label = gnm_sheet_view_is_frozen (sv)
1600 ? _("Un_freeze Panes")
1601 : _("_Freeze Panes");
1602 char const *new_tip = gnm_sheet_view_is_frozen (sv)
1603 ? _("Unfreeze the top left of the sheet")
1604 : _("Freeze the top left of the sheet");
1605 wbc_gtk_set_action_label (wbcg, "ViewFreezeThawPanes", NULL, label, new_tip);
1608 if (MS_ADD_VS_REMOVE_FILTER & flags) {
1609 gboolean const has_filter = (NULL != gnm_sheet_view_editpos_in_filter (sv));
1610 GnmFilter *f = gnm_sheet_view_selection_intersects_filter_rows (sv);
1611 char const* label;
1612 char const *new_tip;
1613 gboolean active = TRUE;
1614 GnmRange *r = NULL;
1616 if ((!has_filter) && (NULL != f)) {
1617 gchar *nlabel = NULL;
1618 if (NULL != (r = gnm_sheet_view_selection_extends_filter (sv, f))) {
1619 active = TRUE;
1620 nlabel = g_strdup_printf
1621 (_("Extend _Auto Filter to %s"),
1622 range_as_string (r));
1623 new_tip = _("Extend the existing filter.");
1624 wbc_gtk_set_action_label
1625 (wbcg, "DataAutoFilter", NULL,
1626 nlabel, new_tip);
1627 g_free (r);
1628 } else {
1629 active = FALSE;
1630 nlabel = g_strdup_printf
1631 (_("Auto Filter blocked by %s"),
1632 range_as_string (&f->r));
1633 new_tip = _("The selection intersects an "
1634 "existing auto filter.");
1635 wbc_gtk_set_action_label
1636 (wbcg, "DataAutoFilter", NULL,
1637 nlabel, new_tip);
1639 g_free (nlabel);
1640 } else {
1641 label = has_filter
1642 ? _("Remove _Auto Filter")
1643 : _("Add _Auto Filter");
1644 new_tip = has_filter
1645 ? _("Remove a filter")
1646 : _("Add a filter");
1647 wbc_gtk_set_action_label (wbcg, "DataAutoFilter", NULL, label, new_tip);
1650 wbc_gtk_set_action_sensitivity (wbcg, "DataAutoFilter", active);
1652 if (MS_COMMENT_LINKS & flags) {
1653 gboolean has_comment
1654 = (sheet_get_comment (sheet, &sv->edit_pos) != NULL);
1655 gboolean has_link;
1656 GnmRange rge;
1657 range_init_cellpos (&rge, &sv->edit_pos);
1658 has_link = (NULL !=
1659 sheet_style_region_contains_link (sheet, &rge));
1660 wbc_gtk_set_action_sensitivity
1661 (wbcg, "EditComment", has_comment);
1662 wbc_gtk_set_action_sensitivity
1663 (wbcg, "EditHyperlink", has_link);
1666 if (MS_COMMENT_LINKS_RANGE & flags) {
1667 GSList *l;
1668 int count = 0;
1669 gboolean has_links = FALSE, has_comments = FALSE;
1670 gboolean sel_is_vector = FALSE;
1671 SheetView *sv = scg_view (scg);
1672 for (l = sv->selections;
1673 l != NULL; l = l->next) {
1674 GnmRange const *r = l->data;
1675 GSList *objs;
1676 GnmStyleList *styles;
1677 if (!has_links) {
1678 styles = sheet_style_collect_hlinks
1679 (sheet, r);
1680 has_links = (styles != NULL);
1681 style_list_free (styles);
1683 if (!has_comments) {
1684 objs = sheet_objects_get
1685 (sheet, r, GNM_CELL_COMMENT_TYPE);
1686 has_comments = (objs != NULL);
1687 g_slist_free (objs);
1689 if((count++ > 1) && has_comments && has_links)
1690 break;
1692 wbc_gtk_set_action_sensitivity
1693 (wbcg, "EditClearHyperlinks", has_links);
1694 wbc_gtk_set_action_sensitivity
1695 (wbcg, "EditClearComments", has_comments);
1696 if (count == 1) {
1697 GnmRange const *r = sv->selections->data;
1698 sel_is_vector = (range_width (r) == 1 ||
1699 range_height (r) == 1) &&
1700 !range_is_singleton (r);
1702 wbc_gtk_set_action_sensitivity
1703 (wbcg, "InsertSortDecreasing", sel_is_vector);
1704 wbc_gtk_set_action_sensitivity
1705 (wbcg, "InsertSortIncreasing", sel_is_vector);
1707 if (MS_FILE_EXPORT_IMPORT & flags) {
1708 Workbook *wb = wb_control_get_workbook (wbc);
1709 gboolean has_export_info = workbook_get_file_exporter (wb) &&
1710 workbook_get_last_export_uri (wb);
1711 wbc_gtk_set_action_sensitivity (wbcg, "DataExportRepeat", has_export_info);
1712 if (has_export_info) {
1713 gchar *base = go_basename_from_uri (workbook_get_last_export_uri (wb));
1714 gchar *new_label = g_strdup_printf (_("Repeat Export to %s"),
1715 base);
1716 g_free (base);
1717 wbc_gtk_set_action_label (wbcg, "DataExportRepeat", NULL,
1718 new_label, N_("Repeat the last data export"));
1719 g_free (new_label);
1720 } else
1721 wbc_gtk_set_action_label (wbcg, "DataExportRepeat", NULL,
1722 N_("Repeat Export"), N_("Repeat the last data export"));
1725 gboolean const has_slicer = (NULL != gnm_sheet_view_editpos_in_slicer (sv));
1726 char const* label = has_slicer
1727 ? _("Remove _Data Slicer")
1728 : _("Create _Data Slicer");
1729 char const *new_tip = has_slicer
1730 ? _("Remove a Data Slicer")
1731 : _("Create a Data Slicer");
1732 wbc_gtk_set_action_label (wbcg, "DataSlicer", NULL, label, new_tip);
1733 wbc_gtk_set_action_sensitivity (wbcg, "DataSlicerRefresh", has_slicer);
1734 wbc_gtk_set_action_sensitivity (wbcg, "DataSlicerEdit", has_slicer);
1738 static void
1739 wbcg_undo_redo_labels (WorkbookControl *wbc, char const *undo, char const *redo)
1741 WBCGtk *wbcg = (WBCGtk *)wbc;
1742 g_return_if_fail (wbcg != NULL);
1744 wbc_gtk_set_action_label (wbcg, "Redo", _("_Redo"), redo, NULL);
1745 wbc_gtk_set_action_label (wbcg, "Undo", _("_Undo"), undo, NULL);
1746 wbc_gtk_set_action_sensitivity (wbcg, "Repeat", undo != NULL);
1749 static void
1750 wbcg_paste_from_selection (WorkbookControl *wbc, GnmPasteTarget const *pt)
1752 gnm_x_request_clipboard ((WBCGtk *)wbc, pt);
1755 static gboolean
1756 wbcg_claim_selection (WorkbookControl *wbc)
1758 WBCGtk *wbcg = (WBCGtk *)wbc;
1759 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (wbcg_toplevel (wbcg)));
1760 return gnm_x_claim_clipboard (display);
1763 static int
1764 wbcg_show_save_dialog (WBCGtk *wbcg, Workbook *wb)
1766 GtkWidget *d;
1767 char *msg;
1768 char const *wb_uri = go_doc_get_uri (GO_DOC (wb));
1769 int ret = 0;
1771 if (wb_uri) {
1772 char *base = go_basename_from_uri (wb_uri);
1773 char *display = g_markup_escape_text (base, -1);
1774 msg = g_strdup_printf (
1775 _("Save changes to workbook '%s' before closing?"),
1776 display);
1777 g_free (base);
1778 g_free (display);
1779 } else {
1780 msg = g_strdup (_("Save changes to workbook before closing?"));
1783 d = gnm_message_dialog_create (wbcg_toplevel (wbcg),
1784 GTK_DIALOG_DESTROY_WITH_PARENT,
1785 GTK_MESSAGE_WARNING,
1786 msg,
1787 _("If you close without saving, changes will be discarded."));
1788 atk_object_set_role (gtk_widget_get_accessible (d), ATK_ROLE_ALERT);
1790 go_gtk_dialog_add_button (GTK_DIALOG(d), _("Discard"),
1791 GTK_STOCK_DELETE, GTK_RESPONSE_NO);
1792 go_gtk_dialog_add_button (GTK_DIALOG(d), _("Don't close"),
1793 GNM_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1795 gtk_dialog_add_button (GTK_DIALOG(d), GNM_STOCK_SAVE, GTK_RESPONSE_YES);
1796 gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_YES);
1797 ret = go_gtk_dialog_run (GTK_DIALOG (d), wbcg_toplevel (wbcg));
1798 g_free (msg);
1800 return ret;
1804 * wbcg_close_if_user_permits : If the workbook is dirty the user is
1805 * prompted to see if they should exit.
1807 * Returns:
1808 * 0) canceled
1809 * 1) closed
1810 * 2) -
1811 * 3) save any future dirty
1812 * 4) do not save any future dirty
1814 static int
1815 wbcg_close_if_user_permits (WBCGtk *wbcg, WorkbookView *wb_view)
1817 gboolean can_close = TRUE;
1818 gboolean done = FALSE;
1819 int iteration = 0;
1820 int button = 0;
1821 Workbook *wb = wb_view_get_workbook (wb_view);
1822 static int in_can_close;
1824 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), 0);
1826 if (in_can_close)
1827 return 0;
1828 in_can_close = TRUE;
1830 while (go_doc_is_dirty (GO_DOC (wb)) && !done) {
1831 iteration++;
1832 button = wbcg_show_save_dialog(wbcg, wb);
1834 switch (button) {
1835 case GTK_RESPONSE_YES:
1836 done = gui_file_save (wbcg, wb_view);
1837 break;
1839 case GNM_RESPONSE_SAVE_ALL:
1840 done = gui_file_save (wbcg, wb_view);
1841 break;
1843 case GTK_RESPONSE_NO:
1844 done = TRUE;
1845 go_doc_set_dirty (GO_DOC (wb), FALSE);
1846 break;
1848 case GNM_RESPONSE_DISCARD_ALL:
1849 done = TRUE;
1850 go_doc_set_dirty (GO_DOC (wb), FALSE);
1851 break;
1853 default: /* CANCEL */
1854 can_close = FALSE;
1855 done = TRUE;
1856 break;
1860 in_can_close = FALSE;
1862 if (can_close) {
1863 gnm_x_store_clipboard_if_needed (wb);
1864 g_object_unref (wb);
1865 switch (button) {
1866 case GNM_RESPONSE_SAVE_ALL:
1867 return 3;
1868 case GNM_RESPONSE_DISCARD_ALL:
1869 return 4;
1870 default:
1871 return 1;
1873 } else
1874 return 0;
1878 * wbc_gtk_close:
1879 * @wbcg: #WBCGtk
1881 * Returns: %TRUE if the control should NOT be closed.
1883 gboolean
1884 wbc_gtk_close (WBCGtk *wbcg)
1886 WorkbookView *wb_view = wb_control_view (GNM_WBC (wbcg));
1888 g_return_val_if_fail (GNM_IS_WORKBOOK_VIEW (wb_view), TRUE);
1889 g_return_val_if_fail (wb_view->wb_controls != NULL, TRUE);
1891 /* If we were editing when the quit request came make sure we don't
1892 * lose any entered text
1894 if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
1895 return TRUE;
1897 /* If something is still using the control
1898 * eg progress meter for a new book */
1899 if (G_OBJECT (wbcg)->ref_count > 1)
1900 return TRUE;
1902 /* This is the last control */
1903 if (wb_view->wb_controls->len <= 1) {
1904 Workbook *wb = wb_view_get_workbook (wb_view);
1906 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), TRUE);
1907 g_return_val_if_fail (wb->wb_views != NULL, TRUE);
1909 /* This is the last view */
1910 if (wb->wb_views->len <= 1) {
1911 if (wbcg_close_if_user_permits (wbcg, wb_view) == 0)
1912 return TRUE;
1913 return FALSE;
1916 g_object_unref (wb_view);
1917 } else
1918 g_object_unref (wbcg);
1920 gnm_app_flag_windows_changed_ ();
1922 return FALSE;
1925 static void
1926 cb_cancel_input (WBCGtk *wbcg)
1928 wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);
1931 static void
1932 cb_accept_input (WBCGtk *wbcg)
1934 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL);
1937 static void
1938 cb_accept_input_wo_ac (WBCGtk *wbcg)
1940 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_WO_AC, NULL);
1943 static void
1944 cb_accept_input_array (WBCGtk *wbcg)
1946 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_ARRAY, NULL);
1949 static void
1950 cb_accept_input_selected_cells (WBCGtk *wbcg)
1952 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT_RANGE, NULL);
1955 static void
1956 cb_accept_input_selected_merged (WBCGtk *wbcg)
1958 Sheet *sheet = wbcg->editing_sheet;
1960 #warning FIXME: this creates 2 undo items!
1961 if (wbcg_is_editing (wbcg) &&
1962 wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL)) {
1963 WorkbookControl *wbc = GNM_WBC (wbcg);
1964 WorkbookView *wbv = wb_control_view (wbc);
1965 SheetView *sv = sheet_get_view (sheet, wbv);
1966 GnmRange sel = *(selection_first_range (sv, NULL, NULL));
1967 GSList *selection = g_slist_prepend (NULL, &sel);
1969 cmd_merge_cells (wbc, sheet, selection, FALSE);
1970 g_slist_free (selection);
1974 /* static void */
1975 /* cb_accept_input_sheets_collector (Sheet *sheet, GSList **n) */
1976 /* { */
1977 /* if (sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE) */
1978 /* (*n) = g_slist_prepend (*n, sheet); */
1979 /* } */
1981 /* static void */
1982 /* cb_accept_input_sheets (WBCGtk *wbcg) */
1983 /* { */
1984 /* GSList *sheets = workbook_sheets */
1985 /* (wb_control_get_workbook (GNM_WBC (wbcg))); */
1986 /* GSList *vis_sheets = NULL; */
1988 /* g_slist_foreach (sheets, */
1989 /* (GFunc) cb_accept_input_sheets_collector, */
1990 /* &vis_sheets); */
1992 /* wbcg_edit_multisheet_finish (wbcg, WBC_EDIT_ACCEPT, NULL, vis_sheets); */
1994 /* g_slist_free (sheets); */
1995 /* g_slist_free (vis_sheets); */
1996 /* } */
1998 /* static void */
1999 /* cb_accept_input_menu_sensitive_sheets_counter (Sheet *sheet, gint *n) */
2000 /* { */
2001 /* if (sheet->visibility == GNM_SHEET_VISIBILITY_VISIBLE) */
2002 /* (*n)++; */
2003 /* } */
2005 /* static gboolean */
2006 /* cb_accept_input_menu_sensitive_sheets (WBCGtk *wbcg) */
2007 /* { */
2008 /* GSList *sheets = workbook_sheets */
2009 /* (wb_control_get_workbook (GNM_WBC (wbcg))); */
2010 /* gint n = 0; */
2012 /* g_slist_foreach (sheets, */
2013 /* (GFunc) cb_accept_input_menu_sensitive_sheets_counter, */
2014 /* &n); */
2015 /* g_slist_free (sheets); */
2016 /* return (n > 1); */
2017 /* } */
2019 /* static gboolean */
2020 /* cb_accept_input_menu_sensitive_selected_sheets (WBCGtk *wbcg) */
2021 /* { */
2022 /* GSList *sheets = workbook_sheets */
2023 /* (wb_control_get_workbook (GNM_WBC (wbcg))); */
2024 /* gint n = 0; */
2026 /* g_slist_foreach (sheets, */
2027 /* (GFunc) cb_accept_input_menu_sensitive_sheets_counter, */
2028 /* &n); */
2029 /* g_slist_free (sheets); */
2030 /* return (n > 2); */
2031 /* } */
2033 static gboolean
2034 cb_accept_input_menu_sensitive_selected_cells (WBCGtk *wbcg)
2036 WorkbookControl *wbc = GNM_WBC (wbcg);
2037 WorkbookView *wbv = wb_control_view (wbc);
2038 SheetView *sv = sheet_get_view (wbcg->editing_sheet, wbv);
2039 gboolean result = TRUE;
2040 GSList *selection = selection_get_ranges (sv, FALSE), *l;
2042 for (l = selection; l != NULL; l = l->next) {
2043 GnmRange const *sel = l->data;
2044 if (sheet_range_splits_array
2045 (wbcg->editing_sheet, sel, NULL, NULL, NULL)) {
2046 result = FALSE;
2047 break;
2050 range_fragment_free (selection);
2051 return result;
2054 static gboolean
2055 cb_accept_input_menu_sensitive_selected_merged (WBCGtk *wbcg)
2057 WorkbookControl *wbc = GNM_WBC (wbcg);
2058 WorkbookView *wbv = wb_control_view (wbc);
2059 SheetView *sv = sheet_get_view (wbcg->editing_sheet, wbv);
2060 GnmRange const *sel = selection_first_range (sv, NULL, NULL);
2062 return (sel && !range_is_singleton (sel) &&
2063 sv->edit_pos.col == sel->start.col &&
2064 sv->edit_pos.row == sel->start.row &&
2065 !sheet_range_splits_array
2066 (wbcg->editing_sheet, sel, NULL, NULL, NULL));
2069 static void
2070 cb_accept_input_menu (GtkMenuToolButton *button, WBCGtk *wbcg)
2072 GtkWidget *menu = gtk_menu_tool_button_get_menu (button);
2073 GList *l, *children = gtk_container_get_children (GTK_CONTAINER (menu));
2075 struct AcceptInputMenu {
2076 gchar const *text;
2077 void (*function) (WBCGtk *wbcg);
2078 gboolean (*sensitive) (WBCGtk *wbcg);
2079 } const accept_input_actions [] = {
2080 { N_("Enter in current cell"), cb_accept_input,
2081 NULL },
2082 { N_("Enter in current cell without autocorrection"), cb_accept_input_wo_ac,
2083 NULL },
2084 /* { N_("Enter on all non-hidden sheets"), cb_accept_input_sheets, */
2085 /* cb_accept_input_menu_sensitive_sheets}, */
2086 /* { N_("Enter on multiple sheets..."), cb_accept_input_selected_sheets, */
2087 /* cb_accept_input_menu_sensitive_selected_sheets }, */
2088 { NULL, NULL, NULL },
2089 { N_("Enter in current range merged"), cb_accept_input_selected_merged,
2090 cb_accept_input_menu_sensitive_selected_merged },
2091 { NULL, NULL, NULL },
2092 { N_("Enter in selected ranges"), cb_accept_input_selected_cells,
2093 cb_accept_input_menu_sensitive_selected_cells },
2094 { N_("Enter in selected ranges as array"), cb_accept_input_array,
2095 cb_accept_input_menu_sensitive_selected_cells },
2097 unsigned int ui;
2098 GtkWidget *item;
2099 const struct AcceptInputMenu *it;
2101 if (children == NULL)
2102 for (ui = 0; ui < G_N_ELEMENTS (accept_input_actions); ui++) {
2103 it = accept_input_actions + ui;
2105 if (it->text) {
2106 item = gtk_image_menu_item_new_with_label
2107 (_(it->text));
2108 if (it->function)
2109 g_signal_connect_swapped
2110 (G_OBJECT (item), "activate",
2111 G_CALLBACK (it->function),
2112 wbcg);
2113 if (wbcg->editing_sheet) {
2114 if (it->sensitive)
2115 gtk_widget_set_sensitive
2116 (item, (it->sensitive) (wbcg));
2117 else
2118 gtk_widget_set_sensitive (item, TRUE);
2119 } else
2120 gtk_widget_set_sensitive (item, FALSE);
2121 } else
2122 item = gtk_separator_menu_item_new ();
2123 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2124 gtk_widget_show (item);
2126 else
2127 for (ui = 0, l = children;
2128 ui < G_N_ELEMENTS (accept_input_actions) && l != NULL;
2129 ui++, l = l->next) {
2130 it = accept_input_actions + ui;
2131 if (wbcg->editing_sheet) {
2132 if (it->sensitive)
2133 gtk_widget_set_sensitive
2134 (GTK_WIDGET (l->data),
2135 (it->sensitive) (wbcg));
2136 else
2137 gtk_widget_set_sensitive
2138 (GTK_WIDGET (l->data), TRUE);
2139 } else
2140 gtk_widget_set_sensitive (l->data, FALSE);
2144 g_list_free (children);
2147 static gboolean
2148 cb_editline_focus_in (GtkWidget *w, GdkEventFocus *event,
2149 WBCGtk *wbcg)
2151 if (!wbcg_is_editing (wbcg))
2152 if (!wbcg_edit_start (wbcg, FALSE, TRUE)) {
2153 #if 0
2154 GtkEntry *entry = GTK_ENTRY (w);
2155 #endif
2156 wbcg_focus_cur_scg (wbcg);
2157 #warning GTK3: what can we do there for gtk3?
2158 #if 0
2159 entry->in_drag = FALSE;
2161 * ->button is private, ugh. Since the text area
2162 * never gets a release event, there seems to be
2163 * no official way of returning the widget to its
2164 * correct state.
2166 entry->button = 0;
2167 #endif
2168 return TRUE;
2171 return FALSE;
2174 static void
2175 cb_statusbox_activate (GtkEntry *entry, WBCGtk *wbcg)
2177 WorkbookControl *wbc = GNM_WBC (wbcg);
2178 wb_control_parse_and_jump (wbc, gtk_entry_get_text (entry));
2179 wbcg_focus_cur_scg (wbcg);
2180 wb_view_selection_desc (wb_control_view (wbc), TRUE, wbc);
2183 static gboolean
2184 cb_statusbox_focus (GtkEntry *entry, GdkEventFocus *event,
2185 WBCGtk *wbcg)
2187 gtk_editable_select_region (GTK_EDITABLE (entry), 0, 0);
2188 return FALSE;
2191 /******************************************************************************/
2193 static void
2194 dump_size_tree (GtkWidget *w, gpointer indent_)
2196 int indent = GPOINTER_TO_INT (indent_);
2197 int h1, h2;
2198 GtkAllocation a;
2200 g_printerr ("%*s", indent, "");
2201 if (gtk_widget_get_name (w))
2202 g_printerr ("\"%s\" ", gtk_widget_get_name (w));
2204 gtk_widget_get_preferred_height (w, &h1, &h2);
2205 gtk_widget_get_allocation (w, &a);
2207 g_printerr ("%s %p viz=%d act=%dx%d minheight=%d natheight=%d\n",
2208 g_type_name_from_instance ((GTypeInstance *)w), w,
2209 gtk_widget_get_visible (w),
2210 a.width, a.height,
2211 h1, h2);
2213 if (GTK_IS_CONTAINER (w)) {
2214 gtk_container_foreach (GTK_CONTAINER (w),
2215 dump_size_tree,
2216 GINT_TO_POINTER (indent + 2));
2221 static void
2222 cb_workbook_debug_info (WBCGtk *wbcg)
2224 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
2226 if (gnm_debug_flag ("notebook-size"))
2227 dump_size_tree (GTK_WIDGET (wbcg_toplevel (wbcg)), GINT_TO_POINTER (0));
2229 if (gnm_debug_flag ("deps")) {
2230 dependents_dump (wb);
2233 if (gnm_debug_flag ("expr-sharer")) {
2234 GnmExprSharer *es = workbook_share_expressions (wb, FALSE);
2235 gnm_expr_sharer_report (es);
2236 gnm_expr_sharer_destroy (es);
2239 if (gnm_debug_flag ("style-optimize")) {
2240 workbook_optimize_style (wb);
2243 if (gnm_debug_flag ("name-collections")) {
2244 gnm_named_expr_collection_dump (wb->names, "workbook");
2245 WORKBOOK_FOREACH_SHEET(wb, sheet, {
2246 gnm_named_expr_collection_dump (sheet->names,
2247 sheet->name_unquoted);
2252 static void
2253 cb_autofunction (WBCGtk *wbcg)
2255 GtkEntry *entry;
2256 gchar const *txt;
2258 if (wbcg_is_editing (wbcg))
2259 return;
2261 entry = wbcg_get_entry (wbcg);
2262 txt = gtk_entry_get_text (entry);
2263 if (strncmp (txt, "=", 1)) {
2264 if (!wbcg_edit_start (wbcg, TRUE, TRUE))
2265 return; /* attempt to edit failed */
2266 gtk_entry_set_text (entry, "=");
2267 gtk_editable_set_position (GTK_EDITABLE (entry), 1);
2268 } else {
2269 if (!wbcg_edit_start (wbcg, FALSE, TRUE))
2270 return; /* attempt to edit failed */
2272 /* FIXME : This is crap!
2273 * When the function druid is more complete use that.
2275 gtk_editable_set_position (GTK_EDITABLE (entry),
2276 gtk_entry_get_text_length (entry)-1);
2281 * We must not crash on focus=NULL. We're called like that as a result of
2282 * gtk_window_set_focus (toplevel, NULL) if the first sheet view is destroyed
2283 * just after being created. This happens e.g when we cancel a file import or
2284 * the import fails.
2286 static void
2287 cb_set_focus (GtkWindow *window, GtkWidget *focus, WBCGtk *wbcg)
2289 if (focus && !gtk_window_get_focus (window))
2290 wbcg_focus_cur_scg (wbcg);
2293 /***************************************************************************/
2295 static gboolean
2296 cb_scroll_wheel (GtkWidget *w, GdkEventScroll *event,
2297 WBCGtk *wbcg)
2299 SheetControlGUI *scg = wbcg_get_scg (wbcg, wbcg_focus_cur_scg (wbcg));
2300 Sheet *sheet = scg_sheet (scg);
2301 /* scroll always operates on pane 0 */
2302 GnmPane *pane = scg_pane (scg, 0);
2303 gboolean go_horiz = (event->direction == GDK_SCROLL_LEFT ||
2304 event->direction == GDK_SCROLL_RIGHT);
2305 gboolean go_back = (event->direction == GDK_SCROLL_UP ||
2306 event->direction == GDK_SCROLL_LEFT);
2308 if (!pane ||
2309 !gtk_widget_get_realized (w) ||
2310 event->direction == GDK_SCROLL_SMOOTH)
2311 return FALSE;
2313 if ((event->state & GDK_SHIFT_MASK))
2314 go_horiz = !go_horiz;
2316 if ((event->state & GDK_CONTROL_MASK)) { /* zoom */
2317 int zoom = (int)(sheet->last_zoom_factor_used * 100. + .5) - 10;
2319 if ((zoom % 15) != 0) {
2320 zoom = 15 * (int)(zoom/15);
2321 if (go_back)
2322 zoom += 15;
2323 } else {
2324 if (go_back)
2325 zoom += 15;
2326 else
2327 zoom -= 15;
2330 if (0 <= zoom && zoom <= 390)
2331 cmd_zoom (GNM_WBC (wbcg), g_slist_append (NULL, sheet),
2332 (double) (zoom + 10) / 100);
2333 } else if (go_horiz) {
2334 int col = (pane->last_full.col - pane->first.col) / 4;
2335 if (col < 1)
2336 col = 1;
2337 if (go_back)
2338 col = pane->first.col - col;
2339 else
2340 col = pane->first.col + col;
2341 scg_set_left_col (pane->simple.scg, col);
2342 } else {
2343 int row = (pane->last_full.row - pane->first.row) / 4;
2344 if (row < 1)
2345 row = 1;
2346 if (go_back)
2347 row = pane->first.row - row;
2348 else
2349 row = pane->first.row + row;
2350 scg_set_top_row (pane->simple.scg, row);
2352 return TRUE;
2356 * Make current control size the default. Toplevel would resize
2357 * spontaneously. This makes it stay the same size until user resizes.
2359 static void
2360 cb_realize (GtkWindow *toplevel, WBCGtk *wbcg)
2362 GtkAllocation ta;
2364 g_return_if_fail (GTK_IS_WINDOW (toplevel));
2366 gtk_widget_get_allocation (GTK_WIDGET (toplevel), &ta);
2367 gtk_window_set_default_size (toplevel, ta.width, ta.height);
2369 /* if we are already initialized set the focus. Without this loading a
2370 * multpage book sometimes leaves focus on the last book rather than
2371 * the current book. Which leads to a slew of errors for keystrokes
2372 * until focus is corrected.
2374 if (wbcg->snotebook) {
2375 wbcg_focus_cur_scg (wbcg);
2376 wbcg_update_menu_feedback (wbcg, wbcg_cur_sheet (wbcg));
2380 static void
2381 cb_css_parse_error (GtkCssProvider *css, GtkCssSection *section, GError *err)
2383 if (g_error_matches (err, GTK_CSS_PROVIDER_ERROR,
2384 GTK_CSS_PROVIDER_ERROR_DEPRECATED) &&
2385 !gnm_debug_flag ("css"))
2386 return;
2388 g_warning ("Theme parsing error: %s", err->message);
2391 struct css_provider_data {
2392 GtkCssProvider *css;
2393 GSList *screens;
2396 static void
2397 cb_unload_providers (gpointer data_)
2399 struct css_provider_data *data = data_;
2400 GSList *l;
2402 for (l = data->screens; l; l = l->next) {
2403 GdkScreen *screen = l->data;
2404 gtk_style_context_remove_provider_for_screen
2405 (screen, GTK_STYLE_PROVIDER (data->css));
2407 g_slist_free (data->screens);
2408 g_object_unref (data->css);
2409 g_free (data);
2412 static void
2413 cb_screen_changed (GtkWidget *widget)
2415 GdkScreen *screen = gtk_widget_get_screen (widget);
2416 GObject *app = gnm_app_get_app ();
2417 const char *app_key = "css-provider";
2418 struct css_provider_data *data;
2420 data = g_object_get_data (app, app_key);
2421 if (!data) {
2422 const char *resource = "/org/gnumeric/gnumeric/ui/gnumeric.css";
2423 GBytes *cssbytes = g_resources_lookup_data (resource, 0, NULL);
2424 const char *csstext = g_bytes_get_data (cssbytes, NULL);
2425 gboolean debug = gnm_debug_flag ("css");
2427 data = g_new (struct css_provider_data, 1);
2428 data->css = gtk_css_provider_new ();
2429 data->screens = NULL;
2431 if (debug)
2432 g_printerr ("Loading style from %s\n", resource);
2433 else
2434 g_signal_connect (data->css, "parsing-error",
2435 G_CALLBACK (cb_css_parse_error),
2436 NULL);
2438 gtk_css_provider_load_from_data (data->css, csstext, -1, NULL);
2439 g_object_set_data_full (app, app_key, data, cb_unload_providers);
2440 g_bytes_unref (cssbytes);
2443 if (screen && !g_slist_find (data->screens, screen)) {
2444 gtk_style_context_add_provider_for_screen
2445 (screen,
2446 GTK_STYLE_PROVIDER (data->css),
2447 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2448 data->screens = g_slist_prepend (data->screens, screen);
2452 void
2453 wbcg_set_status_text (WBCGtk *wbcg, char const *text)
2455 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2456 gtk_statusbar_pop (GTK_STATUSBAR (wbcg->status_text), 0);
2457 gtk_statusbar_push (GTK_STATUSBAR (wbcg->status_text), 0, text);
2460 static void
2461 set_visibility (WBCGtk *wbcg,
2462 char const *action_name,
2463 gboolean visible)
2465 GtkWidget *w = g_hash_table_lookup (wbcg->visibility_widgets, action_name);
2466 if (w)
2467 gtk_widget_set_visible (w, visible);
2468 wbc_gtk_set_toggle_action_state (wbcg, action_name, visible);
2472 void
2473 wbcg_toggle_visibility (WBCGtk *wbcg, GtkToggleAction *action)
2475 if (!wbcg->updating_ui && wbcg_ui_update_begin (wbcg)) {
2476 char const *name = gtk_action_get_name (GTK_ACTION (action));
2477 set_visibility (wbcg, name,
2478 gtk_toggle_action_get_active (action));
2479 wbcg_ui_update_end (wbcg);
2483 static void
2484 cb_visibility (char const *action, GtkWidget *orig_widget, WBCGtk *new_wbcg)
2486 set_visibility (new_wbcg, action, gtk_widget_get_visible (orig_widget));
2489 void
2490 wbcg_copy_toolbar_visibility (WBCGtk *new_wbcg,
2491 WBCGtk *wbcg)
2493 g_hash_table_foreach (wbcg->visibility_widgets,
2494 (GHFunc)cb_visibility, new_wbcg);
2498 void
2499 wbcg_set_end_mode (WBCGtk *wbcg, gboolean flag)
2501 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2503 if (!wbcg->last_key_was_end != !flag) {
2504 const char *txt = flag ? _("END") : "";
2505 wbcg_set_status_text (wbcg, txt);
2506 wbcg->last_key_was_end = flag;
2510 static PangoFontDescription *
2511 settings_get_font_desc (GtkSettings *settings)
2513 PangoFontDescription *font_desc;
2514 char *font_str;
2516 g_object_get (settings, "gtk-font-name", &font_str, NULL);
2517 font_desc = pango_font_description_from_string (
2518 font_str ? font_str : "sans 10");
2519 g_free (font_str);
2521 return font_desc;
2524 static void
2525 cb_update_item_bar_font (GtkWidget *w)
2527 SheetControlGUI *scg = get_scg (w);
2528 sc_resize ((SheetControl *)scg, TRUE);
2531 static void
2532 cb_desktop_font_changed (GtkSettings *settings, GParamSpec *pspec,
2533 WBCGtk *wbcg)
2535 if (wbcg->font_desc)
2536 pango_font_description_free (wbcg->font_desc);
2537 wbcg->font_desc = settings_get_font_desc (settings);
2538 gtk_container_foreach (GTK_CONTAINER (wbcg->snotebook),
2539 (GtkCallback)cb_update_item_bar_font, NULL);
2542 static GdkScreen *
2543 wbcg_get_screen (WBCGtk *wbcg)
2545 return gtk_widget_get_screen (wbcg->notebook_area);
2548 static GtkSettings *
2549 wbcg_get_gtk_settings (WBCGtk *wbcg)
2551 return gtk_settings_get_for_screen (wbcg_get_screen (wbcg));
2554 /* ------------------------------------------------------------------------- */
2556 static int
2557 show_gui (WBCGtk *wbcg)
2559 SheetControlGUI *scg;
2560 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
2561 int sx, sy;
2562 gdouble fx, fy;
2563 GdkRectangle rect;
2564 GdkScreen *screen = wbcg_get_screen (wbcg);
2566 /* In a Xinerama setup, we want the geometry of the actual display
2567 * unit, if available. See bug 59902. */
2568 gdk_screen_get_monitor_geometry (screen, 0, &rect);
2569 sx = MAX (rect.width, 600);
2570 sy = MAX (rect.height, 200);
2572 fx = gnm_conf_get_core_gui_window_x ();
2573 fy = gnm_conf_get_core_gui_window_y ();
2575 /* Successfully parsed geometry string and urged WM to comply */
2576 if (NULL != wbcg->preferred_geometry && NULL != wbcg->toplevel &&
2577 gtk_window_parse_geometry (GTK_WINDOW (wbcg->toplevel),
2578 wbcg->preferred_geometry)) {
2579 g_free (wbcg->preferred_geometry);
2580 wbcg->preferred_geometry = NULL;
2581 } else if (wbcg->snotebook != NULL &&
2582 wbv != NULL &&
2583 (wbv->preferred_width > 0 || wbv->preferred_height > 0)) {
2584 /* Set grid size to preferred width */
2585 int pwidth = MIN(wbv->preferred_width, gdk_screen_get_width (screen));
2586 int pheight = MIN(wbv->preferred_height, gdk_screen_get_height (screen));
2587 GtkRequisition requisition;
2589 pwidth = pwidth > 0 ? pwidth : -1;
2590 pheight = pheight > 0 ? pheight : -1;
2591 gtk_widget_set_size_request (GTK_WIDGET (wbcg->notebook_area),
2592 pwidth, pheight);
2593 gtk_widget_get_preferred_size (GTK_WIDGET (wbcg->toplevel),
2594 &requisition, NULL);
2595 /* We want to test if toplevel is bigger than screen.
2596 * gtk_widget_size_request tells us the space
2597 * allocated to the toplevel proper, but not how much is
2598 * need for WM decorations or a possible panel.
2600 * The test below should very rarely maximize when there is
2601 * actually room on the screen.
2603 * We maximize instead of resizing for two reasons:
2604 * - The preferred width / height is restored with one click on
2605 * unmaximize.
2606 * - We don't have to guess what size we should resize to.
2608 if (requisition.height + 20 > rect.height ||
2609 requisition.width > rect.width) {
2610 gtk_window_maximize (GTK_WINDOW (wbcg->toplevel));
2611 } else {
2612 gtk_window_set_default_size
2613 (wbcg_toplevel (wbcg),
2614 requisition.width, requisition.height);
2616 } else {
2617 /* Use default */
2618 gtk_window_set_default_size (wbcg_toplevel (wbcg), sx * fx, sy * fy);
2621 scg = wbcg_cur_scg (wbcg);
2622 if (scg)
2623 wbcg_set_direction (scg);
2625 gtk_widget_show (GTK_WIDGET (wbcg_toplevel (wbcg)));
2627 /* rehide headers if necessary */
2628 if (NULL != scg && wbcg_cur_sheet (wbcg))
2629 scg_adjust_preferences (scg);
2631 gtk_widget_set_size_request (GTK_WIDGET (wbcg->notebook_area),
2632 -1, -1);
2633 return FALSE;
2636 static GtkWidget *
2637 wbcg_get_label_for_position (WBCGtk *wbcg, GtkWidget *source,
2638 gint x)
2640 guint n, i;
2641 GtkWidget *last_visible = NULL;
2643 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
2645 n = wbcg_get_n_scg (wbcg);
2646 for (i = 0; i < n; i++) {
2647 GtkWidget *label = gnm_notebook_get_nth_label (wbcg->bnotebook, i);
2648 int x0, x1;
2649 GtkAllocation la;
2651 if (!gtk_widget_get_visible (label))
2652 continue;
2654 gtk_widget_get_allocation (label, &la);
2655 x0 = la.x;
2656 x1 = x0 + la.width;
2658 if (x <= x1) {
2660 * We are left of this label's right edge. Use it
2661 * even if we are far left of the label.
2663 return label;
2666 last_visible = label;
2669 return last_visible;
2672 static gboolean
2673 wbcg_is_local_drag (WBCGtk *wbcg, GtkWidget *source_widget)
2675 GtkWidget *top = (GtkWidget *)wbcg_toplevel (wbcg);
2676 return GNM_IS_PANE (source_widget) &&
2677 gtk_widget_get_toplevel (source_widget) == top;
2679 static gboolean
2680 cb_wbcg_drag_motion (GtkWidget *widget, GdkDragContext *context,
2681 gint x, gint y, guint time, WBCGtk *wbcg)
2683 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2685 if (GNM_IS_NOTEBOOK (gtk_widget_get_parent (source_widget))) {
2686 /* The user wants to reorder sheets. We simulate a
2687 * drag motion over a label.
2689 GtkWidget *label = wbcg_get_label_for_position (wbcg, source_widget, x);
2690 return cb_sheet_label_drag_motion (label, context, x, y,
2691 time, wbcg);
2692 } else if (wbcg_is_local_drag (wbcg, source_widget))
2693 gnm_pane_object_autoscroll (GNM_PANE (source_widget),
2694 context, x, y, time);
2696 return TRUE;
2699 static void
2700 cb_wbcg_drag_leave (GtkWidget *widget, GdkDragContext *context,
2701 guint time, WBCGtk *wbcg)
2703 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2705 g_return_if_fail (GNM_IS_WBC_GTK (wbcg));
2707 if (GNM_IS_NOTEBOOK (gtk_widget_get_parent (source_widget)))
2708 gtk_widget_hide (
2709 g_object_get_data (G_OBJECT (source_widget), "arrow"));
2710 else if (wbcg_is_local_drag (wbcg, source_widget))
2711 gnm_pane_slide_stop (GNM_PANE (source_widget));
2714 static void
2715 cb_wbcg_drag_data_received (GtkWidget *widget, GdkDragContext *context,
2716 gint x, gint y, GtkSelectionData *selection_data,
2717 guint info, guint time, WBCGtk *wbcg)
2719 gchar *target_type = gdk_atom_name (gtk_selection_data_get_target (selection_data));
2721 if (!strcmp (target_type, "text/uri-list")) { /* filenames from nautilus */
2722 scg_drag_data_received (wbcg_cur_scg (wbcg),
2723 gtk_drag_get_source_widget (context), 0, 0,
2724 selection_data);
2725 } else if (!strcmp (target_type, "GNUMERIC_SHEET")) {
2726 /* The user wants to reorder the sheets but hasn't dropped
2727 * the sheet onto a label. Never mind. We figure out
2728 * where the arrow is currently located and simulate a drop
2729 * on that label. */
2730 GtkWidget *label = wbcg_get_label_for_position (wbcg,
2731 gtk_drag_get_source_widget (context), x);
2732 cb_sheet_label_drag_data_received (label, context, x, y,
2733 selection_data, info, time, wbcg);
2734 } else {
2735 GtkWidget *source_widget = gtk_drag_get_source_widget (context);
2736 if (wbcg_is_local_drag (wbcg, source_widget))
2737 g_printerr ("autoscroll complete - stop it\n");
2738 else
2739 scg_drag_data_received (wbcg_cur_scg (wbcg),
2740 source_widget, 0, 0, selection_data);
2742 g_free (target_type);
2745 static void cb_cs_go_up (WBCGtk *wbcg)
2746 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_top); }
2747 static void cb_cs_go_down (WBCGtk *wbcg)
2748 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_bottom); }
2749 static void cb_cs_go_left (WBCGtk *wbcg)
2750 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_first); }
2751 static void cb_cs_go_right (WBCGtk *wbcg)
2752 { wb_control_navigate_to_cell (GNM_WBC (wbcg), navigator_last); }
2753 static void cb_cs_go_to_cell (WBCGtk *wbcg) { dialog_goto_cell (wbcg); }
2755 static void
2756 wbc_gtk_cell_selector_popup (G_GNUC_UNUSED GtkEntry *entry,
2757 G_GNUC_UNUSED GtkEntryIconPosition icon_pos,
2758 G_GNUC_UNUSED GdkEvent *event,
2759 gpointer data)
2761 if (event->type == GDK_BUTTON_PRESS) {
2762 WBCGtk *wbcg = data;
2764 struct CellSelectorMenu {
2765 gchar const *text;
2766 void (*function) (WBCGtk *wbcg);
2767 } const cell_selector_actions [] = {
2768 { N_("Go to Top"), &cb_cs_go_up },
2769 { N_("Go to Bottom"), &cb_cs_go_down },
2770 { N_("Go to First"), &cb_cs_go_left },
2771 { N_("Go to Last"), &cb_cs_go_right },
2772 { NULL, NULL },
2773 { N_("Go to Cell..."), &cb_cs_go_to_cell }
2775 unsigned int ui;
2776 GtkWidget *item, *menu = gtk_menu_new ();
2777 gboolean active = (!wbcg_is_editing (wbcg) &&
2778 NULL == wbc_gtk_get_guru (wbcg));
2780 for (ui = 0; ui < G_N_ELEMENTS (cell_selector_actions); ui++) {
2781 const struct CellSelectorMenu *it =
2782 cell_selector_actions + ui;
2783 if (it->text)
2784 item = gtk_image_menu_item_new_with_label
2785 (_(it->text));
2786 else
2787 item = gtk_separator_menu_item_new ();
2789 if (it->function)
2790 g_signal_connect_swapped
2791 (G_OBJECT (item), "activate",
2792 G_CALLBACK (it->function), wbcg);
2793 gtk_widget_set_sensitive (item, active);
2794 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2795 gtk_widget_show (item);
2798 gnumeric_popup_menu (GTK_MENU (menu), event);
2802 static void
2803 wbc_gtk_create_edit_area (WBCGtk *wbcg)
2805 GtkToolItem *item;
2806 GtkEntry *entry;
2807 int len;
2808 GtkWidget *debug_button;
2810 wbc_gtk_init_editline (wbcg);
2811 entry = wbcg_get_entry (wbcg);
2813 /* Set a reasonable width for the selection box. */
2814 len = gnm_widget_measure_string
2815 (GTK_WIDGET (wbcg_toplevel (wbcg)),
2816 cell_coord_name (GNM_MAX_COLS - 1, GNM_MAX_ROWS - 1));
2818 * Add a little extra since font might be proportional and since
2819 * we also put user defined names there.
2821 len = len * 3 / 2;
2822 gtk_widget_set_size_request (wbcg->selection_descriptor, len, -1);
2824 g_signal_connect_swapped (wbcg->cancel_button,
2825 "clicked", G_CALLBACK (cb_cancel_input),
2826 wbcg);
2828 g_signal_connect_swapped (wbcg->ok_button,
2829 "clicked", G_CALLBACK (cb_accept_input),
2830 wbcg);
2831 gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (wbcg->ok_button),
2832 gtk_menu_new ());
2833 gtk_menu_tool_button_set_arrow_tooltip_text
2834 (GTK_MENU_TOOL_BUTTON (wbcg->ok_button),
2835 _("Accept change in multiple cells"));
2836 g_signal_connect (wbcg->ok_button,
2837 "show-menu", G_CALLBACK (cb_accept_input_menu),
2838 wbcg);
2840 g_signal_connect_swapped (wbcg->func_button,
2841 "clicked", G_CALLBACK (cb_autofunction),
2842 wbcg);
2844 /* Dependency debugger */
2845 debug_button = GET_GUI_ITEM ("debug_button");
2846 if (gnm_debug_flag ("notebook-size") ||
2847 gnm_debug_flag ("deps") ||
2848 gnm_debug_flag ("expr-sharer") ||
2849 gnm_debug_flag ("style-optimize") ||
2850 gnm_debug_flag ("name-collections")) {
2851 g_signal_connect_swapped (debug_button,
2852 "clicked", G_CALLBACK (cb_workbook_debug_info),
2853 wbcg);
2854 } else {
2855 gtk_widget_destroy (debug_button);
2858 item = GET_GUI_ITEM ("edit_line_entry_item");
2859 gtk_container_add (GTK_CONTAINER (item),
2860 GTK_WIDGET (wbcg->edit_line.entry));
2861 gtk_widget_show_all (GTK_WIDGET (item));
2863 /* Do signal setup for the editing input line */
2864 g_signal_connect (G_OBJECT (entry),
2865 "focus-in-event",
2866 G_CALLBACK (cb_editline_focus_in), wbcg);
2868 /* status box */
2869 g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2870 "activate",
2871 G_CALLBACK (cb_statusbox_activate), wbcg);
2872 g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2873 "focus-out-event",
2874 G_CALLBACK (cb_statusbox_focus), wbcg);
2876 gtk_entry_set_icon_from_icon_name
2877 (GTK_ENTRY (wbcg->selection_descriptor),
2878 GTK_ENTRY_ICON_SECONDARY, "go-jump");
2879 gtk_entry_set_icon_sensitive
2880 (GTK_ENTRY (wbcg->selection_descriptor),
2881 GTK_ENTRY_ICON_SECONDARY, TRUE);
2882 gtk_entry_set_icon_activatable
2883 (GTK_ENTRY (wbcg->selection_descriptor),
2884 GTK_ENTRY_ICON_SECONDARY, TRUE);
2886 g_signal_connect (G_OBJECT (wbcg->selection_descriptor),
2887 "icon-press",
2888 G_CALLBACK
2889 (wbc_gtk_cell_selector_popup),
2890 wbcg);
2893 static int
2894 wbcg_validation_msg (WorkbookControl *wbc, ValidationStyle v,
2895 char const *title, char const *msg)
2897 WBCGtk *wbcg = (WBCGtk *)wbc;
2898 ValidationStatus res0, res1 = GNM_VALIDATION_STATUS_VALID; /* supress warning */
2899 char const *btn0, *btn1;
2900 GtkMessageType type;
2901 GtkWidget *dialog;
2902 int response;
2904 switch (v) {
2905 case GNM_VALIDATION_STYLE_STOP:
2906 res0 = GNM_VALIDATION_STATUS_INVALID_EDIT;
2907 btn0 = _("_Re-Edit");
2908 res1 = GNM_VALIDATION_STATUS_INVALID_DISCARD;
2909 btn1 = _("_Discard");
2910 type = GTK_MESSAGE_ERROR;
2911 break;
2912 case GNM_VALIDATION_STYLE_WARNING:
2913 res0 = GNM_VALIDATION_STATUS_VALID;
2914 btn0 = _("_Accept");
2915 res1 = GNM_VALIDATION_STATUS_INVALID_DISCARD;
2916 btn1 = _("_Discard");
2917 type = GTK_MESSAGE_WARNING;
2918 break;
2919 case GNM_VALIDATION_STYLE_INFO:
2920 res0 = GNM_VALIDATION_STATUS_VALID;
2921 btn0 = GNM_STOCK_OK;
2922 btn1 = NULL;
2923 type = GTK_MESSAGE_INFO;
2924 break;
2925 case GNM_VALIDATION_STYLE_PARSE_ERROR:
2926 res0 = GNM_VALIDATION_STATUS_INVALID_EDIT;
2927 btn0 = _("_Re-Edit");
2928 res1 = GNM_VALIDATION_STATUS_VALID;
2929 btn1 = _("_Accept");
2930 type = GTK_MESSAGE_ERROR;
2931 break;
2933 default:
2934 g_assert_not_reached ();
2937 dialog = gtk_message_dialog_new (wbcg_toplevel (wbcg),
2938 GTK_DIALOG_DESTROY_WITH_PARENT,
2939 type, GTK_BUTTONS_NONE, "%s", msg);
2940 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2941 btn0, GTK_RESPONSE_YES,
2942 btn1, GTK_RESPONSE_NO,
2943 NULL);
2944 /* TODO : what to use if nothing is specified ? */
2945 /* TODO : do we want the document name here too ? */
2946 if (title)
2947 gtk_window_set_title (GTK_WINDOW (dialog), title);
2948 gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_NO);
2949 response = go_gtk_dialog_run (GTK_DIALOG (dialog),
2950 wbcg_toplevel (wbcg));
2951 return ((response == GTK_RESPONSE_NO || response == GTK_RESPONSE_CANCEL) ? res1 : res0);
2954 #define DISCONNECT(obj,field) \
2955 if (wbcg->field) { \
2956 if (obj) \
2957 g_signal_handler_disconnect (obj, wbcg->field); \
2958 wbcg->field = 0; \
2961 static void
2962 wbcg_view_changed (WBCGtk *wbcg,
2963 G_GNUC_UNUSED GParamSpec *pspec,
2964 Workbook *old_wb)
2966 WorkbookControl *wbc = GNM_WBC (wbcg);
2967 Workbook *wb = wb_control_get_workbook (wbc);
2968 WorkbookView *wbv = wb_control_view (wbc);
2970 /* Reconnect self because we need to change data. */
2971 DISCONNECT (wbc, sig_view_changed);
2972 wbcg->sig_view_changed =
2973 g_signal_connect_object
2974 (G_OBJECT (wbc),
2975 "notify::view",
2976 G_CALLBACK (wbcg_view_changed),
2980 DISCONNECT (wbcg->sig_wbv, sig_auto_expr_text);
2981 DISCONNECT (wbcg->sig_wbv, sig_auto_expr_attrs);
2982 DISCONNECT (wbcg->sig_wbv, sig_show_horizontal_scrollbar);
2983 DISCONNECT (wbcg->sig_wbv, sig_show_vertical_scrollbar);
2984 DISCONNECT (wbcg->sig_wbv, sig_show_notebook_tabs);
2985 if (wbcg->sig_wbv)
2986 g_object_remove_weak_pointer (wbcg->sig_wbv,
2987 &wbcg->sig_wbv);
2988 wbcg->sig_wbv = wbv;
2989 if (wbv) {
2990 g_object_add_weak_pointer (wbcg->sig_wbv,
2991 &wbcg->sig_wbv);
2992 wbcg->sig_auto_expr_text =
2993 g_signal_connect_object
2994 (G_OBJECT (wbv),
2995 "notify::auto-expr-value",
2996 G_CALLBACK (wbcg_auto_expr_value_changed),
2997 wbcg,
2999 wbcg_auto_expr_value_changed (wbv, NULL, wbcg);
3001 wbcg->sig_show_horizontal_scrollbar =
3002 g_signal_connect_object
3003 (G_OBJECT (wbv),
3004 "notify::show-horizontal-scrollbar",
3005 G_CALLBACK (wbcg_scrollbar_visibility),
3006 wbcg,
3008 wbcg->sig_show_vertical_scrollbar =
3009 g_signal_connect_object
3010 (G_OBJECT (wbv),
3011 "notify::show-vertical-scrollbar",
3012 G_CALLBACK (wbcg_scrollbar_visibility),
3013 wbcg,
3015 wbcg->sig_show_notebook_tabs =
3016 g_signal_connect_object
3017 (G_OBJECT (wbv),
3018 "notify::show-notebook-tabs",
3019 G_CALLBACK (wbcg_notebook_tabs_visibility),
3020 wbcg,
3022 wbcg_notebook_tabs_visibility (wbv, NULL, wbcg);
3025 DISCONNECT (old_wb, sig_sheet_order);
3026 DISCONNECT (old_wb, sig_notify_uri);
3027 DISCONNECT (old_wb, sig_notify_dirty);
3029 if (wb) {
3030 wbcg->sig_sheet_order =
3031 g_signal_connect_object
3032 (G_OBJECT (wb),
3033 "sheet-order-changed",
3034 G_CALLBACK (wbcg_sheet_order_changed),
3035 wbcg, G_CONNECT_SWAPPED);
3037 wbcg->sig_notify_uri =
3038 g_signal_connect_object
3039 (G_OBJECT (wb),
3040 "notify::uri",
3041 G_CALLBACK (wbcg_update_title),
3042 wbcg, G_CONNECT_SWAPPED);
3044 wbcg->sig_notify_dirty =
3045 g_signal_connect_object
3046 (G_OBJECT (wb),
3047 "notify::dirty",
3048 G_CALLBACK (wbcg_update_title),
3049 wbcg, G_CONNECT_SWAPPED);
3051 wbcg_update_title (wbcg);
3055 #undef DISCONNECT
3057 /***************************************************************************/
3059 static GOActionComboStack *
3060 ur_stack (WorkbookControl *wbc, gboolean is_undo)
3062 WBCGtk *wbcg = (WBCGtk *)wbc;
3063 return is_undo ? wbcg->undo_haction : wbcg->redo_haction;
3066 static void
3067 wbc_gtk_undo_redo_truncate (WorkbookControl *wbc, int n, gboolean is_undo)
3069 go_action_combo_stack_truncate (ur_stack (wbc, is_undo), n);
3072 static void
3073 wbc_gtk_undo_redo_pop (WorkbookControl *wbc, gboolean is_undo)
3075 go_action_combo_stack_pop (ur_stack (wbc, is_undo), 1);
3078 static void
3079 wbc_gtk_undo_redo_push (WorkbookControl *wbc, gboolean is_undo,
3080 char const *text, gpointer key)
3082 go_action_combo_stack_push (ur_stack (wbc, is_undo), text, key);
3085 /****************************************************************************/
3087 static void
3088 set_font_name_feedback (GtkAction *act, const char *family)
3090 PangoFontDescription *desc = pango_font_description_new ();
3091 pango_font_description_set_family (desc, family);
3092 wbcg_font_action_set_font_desc (act, desc);
3093 pango_font_description_free (desc);
3096 static void
3097 set_font_size_feedback (GtkAction *act, double size)
3099 PangoFontDescription *desc = pango_font_description_new ();
3100 pango_font_description_set_size (desc, size * PANGO_SCALE);
3101 wbcg_font_action_set_font_desc (act, desc);
3102 pango_font_description_free (desc);
3105 /****************************************************************************/
3107 static WorkbookControl *
3108 wbc_gtk_control_new (G_GNUC_UNUSED WorkbookControl *wbc,
3109 WorkbookView *wbv,
3110 Workbook *wb,
3111 gpointer extra)
3113 return (WorkbookControl *)wbc_gtk_new (wbv, wb,
3114 extra ? GDK_SCREEN (extra) : NULL, NULL);
3117 static void
3118 wbc_gtk_init_state (WorkbookControl *wbc)
3120 WorkbookView *wbv = wb_control_view (wbc);
3121 WBCGtk *wbcg = WBC_GTK (wbc);
3123 /* Share a colour history for all a view's controls */
3124 go_action_combo_color_set_group (wbcg->back_color, wbv);
3125 go_action_combo_color_set_group (wbcg->fore_color, wbv);
3128 static void
3129 wbc_gtk_style_feedback_real (WorkbookControl *wbc, GnmStyle const *changes)
3131 WorkbookView *wb_view = wb_control_view (wbc);
3132 WBCGtk *wbcg = (WBCGtk *)wbc;
3134 g_return_if_fail (wb_view != NULL);
3136 if (!wbcg_ui_update_begin (WBC_GTK (wbc)))
3137 return;
3139 if (changes == NULL)
3140 changes = wb_view->current_style;
3142 if (gnm_style_is_element_set (changes, MSTYLE_FONT_BOLD))
3143 gtk_toggle_action_set_active (wbcg->font.bold,
3144 gnm_style_get_font_bold (changes));
3145 if (gnm_style_is_element_set (changes, MSTYLE_FONT_ITALIC))
3146 gtk_toggle_action_set_active (wbcg->font.italic,
3147 gnm_style_get_font_italic (changes));
3148 if (gnm_style_is_element_set (changes, MSTYLE_FONT_UNDERLINE)) {
3149 gtk_toggle_action_set_active (wbcg->font.underline,
3150 gnm_style_get_font_uline (changes) == UNDERLINE_SINGLE);
3151 gtk_toggle_action_set_active (wbcg->font.d_underline,
3152 gnm_style_get_font_uline (changes) == UNDERLINE_DOUBLE);
3153 gtk_toggle_action_set_active (wbcg->font.sl_underline,
3154 gnm_style_get_font_uline (changes) == UNDERLINE_SINGLE_LOW);
3155 gtk_toggle_action_set_active (wbcg->font.dl_underline,
3156 gnm_style_get_font_uline (changes) == UNDERLINE_DOUBLE_LOW);
3158 if (gnm_style_is_element_set (changes, MSTYLE_FONT_STRIKETHROUGH))
3159 gtk_toggle_action_set_active (wbcg->font.strikethrough,
3160 gnm_style_get_font_strike (changes));
3162 if (gnm_style_is_element_set (changes, MSTYLE_FONT_SCRIPT)) {
3163 gtk_toggle_action_set_active (wbcg->font.superscript,
3164 gnm_style_get_font_script (changes) == GO_FONT_SCRIPT_SUPER);
3165 gtk_toggle_action_set_active (wbcg->font.subscript,
3166 gnm_style_get_font_script (changes) == GO_FONT_SCRIPT_SUB);
3167 } else {
3168 gtk_toggle_action_set_active (wbcg->font.superscript, FALSE);
3169 gtk_toggle_action_set_active (wbcg->font.subscript, FALSE);
3172 if (gnm_style_is_element_set (changes, MSTYLE_ALIGN_H)) {
3173 GnmHAlign align = gnm_style_get_align_h (changes);
3174 gtk_toggle_action_set_active (wbcg->h_align.left,
3175 align == GNM_HALIGN_LEFT);
3176 gtk_toggle_action_set_active (wbcg->h_align.center,
3177 align == GNM_HALIGN_CENTER);
3178 gtk_toggle_action_set_active (wbcg->h_align.right,
3179 align == GNM_HALIGN_RIGHT);
3180 gtk_toggle_action_set_active (wbcg->h_align.center_across_selection,
3181 align == GNM_HALIGN_CENTER_ACROSS_SELECTION);
3182 go_action_combo_pixmaps_select_id (wbcg->halignment, align);
3184 if (gnm_style_is_element_set (changes, MSTYLE_ALIGN_V)) {
3185 GnmVAlign align = gnm_style_get_align_v (changes);
3186 gtk_toggle_action_set_active (wbcg->v_align.top,
3187 align == GNM_VALIGN_TOP);
3188 gtk_toggle_action_set_active (wbcg->v_align.bottom,
3189 align == GNM_VALIGN_BOTTOM);
3190 gtk_toggle_action_set_active (wbcg->v_align.center,
3191 align == GNM_VALIGN_CENTER);
3192 go_action_combo_pixmaps_select_id (wbcg->valignment, align);
3195 if (gnm_style_is_element_set (changes, MSTYLE_FONT_SIZE)) {
3196 set_font_size_feedback (wbcg->font_name_haction,
3197 gnm_style_get_font_size (changes));
3198 set_font_size_feedback (wbcg->font_name_vaction,
3199 gnm_style_get_font_size (changes));
3202 if (gnm_style_is_element_set (changes, MSTYLE_FONT_NAME)) {
3203 set_font_name_feedback (wbcg->font_name_haction,
3204 gnm_style_get_font_name (changes));
3205 set_font_name_feedback (wbcg->font_name_vaction,
3206 gnm_style_get_font_name (changes));
3209 wbcg_ui_update_end (WBC_GTK (wbc));
3212 static gint
3213 cb_wbc_gtk_style_feedback (WBCGtk *gtk)
3215 wbc_gtk_style_feedback_real ((WorkbookControl *)gtk, NULL);
3216 gtk->idle_update_style_feedback = 0;
3217 return FALSE;
3219 static void
3220 wbc_gtk_style_feedback (WorkbookControl *wbc, GnmStyle const *changes)
3222 WBCGtk *wbcg = (WBCGtk *)wbc;
3224 if (changes)
3225 wbc_gtk_style_feedback_real (wbc, changes);
3226 else if (0 == wbcg->idle_update_style_feedback)
3227 wbcg->idle_update_style_feedback = g_timeout_add (200,
3228 (GSourceFunc) cb_wbc_gtk_style_feedback, wbc);
3231 static void
3232 cb_handlebox_dock_status (GtkHandleBox *hb,
3233 GtkToolbar *toolbar, gpointer pattached)
3235 gboolean attached = GPOINTER_TO_INT (pattached);
3236 gtk_toolbar_set_show_arrow (toolbar, attached);
3239 static char const *
3240 get_accel_label (GtkMenuItem *item, guint *key)
3242 GList *children = gtk_container_get_children (GTK_CONTAINER (item));
3243 GList *l;
3244 char const *res = NULL;
3246 *key = GDK_KEY_VoidSymbol;
3247 for (l = children; l; l = l->next) {
3248 GtkWidget *w = l->data;
3250 if (GTK_IS_ACCEL_LABEL (w)) {
3251 *key = gtk_label_get_mnemonic_keyval (GTK_LABEL (w));
3252 res = gtk_label_get_label (GTK_LABEL (w));
3253 break;
3257 g_list_free (children);
3258 return res;
3261 static void
3262 check_underlines (GtkWidget *w, char const *path)
3264 GList *children = gtk_container_get_children (GTK_CONTAINER (w));
3265 GHashTable *used = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
3266 GList *l;
3268 for (l = children; l; l = l->next) {
3269 GtkMenuItem *item = GTK_MENU_ITEM (l->data);
3270 GtkWidget *sub = gtk_menu_item_get_submenu (item);
3271 guint key;
3272 char const *label = get_accel_label (item, &key);
3274 if (sub) {
3275 char *newpath = g_strconcat (path, *path ? "->" : "", label, NULL);
3276 check_underlines (sub, newpath);
3277 g_free (newpath);
3280 if (key != GDK_KEY_VoidSymbol) {
3281 char const *prev = g_hash_table_lookup (used, GUINT_TO_POINTER (key));
3282 if (prev) {
3283 /* xgettext: Translators: if this warning shows up when
3284 * running Gnumeric in your locale, the underlines need
3285 * to be moved in strings representing menu entries.
3286 * One slightly tricky point here is that in certain cases,
3287 * the same menu entry shows up in more than one menu.
3289 g_warning (_("In the `%s' menu, the key `%s' is used for both `%s' and `%s'."),
3290 path, gdk_keyval_name (key), prev, label);
3291 } else
3292 g_hash_table_insert (used, GUINT_TO_POINTER (key), g_strdup (label));
3296 g_list_free (children);
3297 g_hash_table_destroy (used);
3300 /****************************************************************************/
3301 /* window list menu */
3303 static void
3304 cb_window_menu_activate (GObject *action, WBCGtk *wbcg)
3306 gtk_window_present (wbcg_toplevel (wbcg));
3309 static unsigned
3310 regenerate_window_menu (WBCGtk *gtk, Workbook *wb, unsigned i)
3312 int k, count;
3313 char *basename = GO_DOC (wb)->uri
3314 ? go_basename_from_uri (GO_DOC (wb)->uri)
3315 : NULL;
3317 /* How many controls are there? */
3318 count = 0;
3319 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc, {
3320 if (GNM_IS_WBC_GTK (wbc))
3321 count++;
3324 k = 1;
3325 WORKBOOK_FOREACH_CONTROL (wb, wbv, wbc, {
3326 if (i >= 20)
3327 return i;
3328 if (GNM_IS_WBC_GTK (wbc) && basename) {
3329 GString *label = g_string_new (NULL);
3330 char *name;
3331 char const *s;
3332 GtkActionEntry entry;
3334 if (i < 10) g_string_append_c (label, '_');
3335 g_string_append_printf (label, "%d ", i);
3337 for (s = basename; *s; s++) {
3338 if (*s == '_')
3339 g_string_append_c (label, '_');
3340 g_string_append_c (label, *s);
3343 if (count > 1)
3344 g_string_append_printf (label, " #%d", k++);
3346 entry.name = name = g_strdup_printf ("WindowListEntry%d", i);
3347 entry.stock_id = NULL;
3348 entry.label = label->str;
3349 entry.accelerator = NULL;
3350 entry.tooltip = NULL;
3351 entry.callback = G_CALLBACK (cb_window_menu_activate);
3353 gtk_action_group_add_actions (gtk->windows.actions,
3354 &entry, 1, wbc);
3356 g_string_free (label, TRUE);
3357 g_free (name);
3358 i++;
3359 }});
3360 g_free (basename);
3361 return i;
3364 static void
3365 cb_regenerate_window_menu (WBCGtk *gtk)
3367 Workbook *wb = wb_control_get_workbook (GNM_WBC (gtk));
3368 GList const *ptr;
3369 unsigned i;
3371 /* This can happen during exit. */
3372 if (!wb)
3373 return;
3375 if (gtk->windows.merge_id != 0)
3376 gtk_ui_manager_remove_ui (gtk->ui, gtk->windows.merge_id);
3377 gtk->windows.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
3379 if (gtk->windows.actions != NULL) {
3380 gtk_ui_manager_remove_action_group (gtk->ui,
3381 gtk->windows.actions);
3382 g_object_unref (gtk->windows.actions);
3384 gtk->windows.actions = gtk_action_group_new ("WindowList");
3386 gtk_ui_manager_insert_action_group (gtk->ui, gtk->windows.actions, 0);
3388 /* create the actions */
3389 i = regenerate_window_menu (gtk, wb, 1); /* current wb first */
3390 for (ptr = gnm_app_workbook_list (); ptr != NULL ; ptr = ptr->next)
3391 if (ptr->data != wb)
3392 i = regenerate_window_menu (gtk, ptr->data, i);
3394 /* merge them in */
3395 while (i-- > 1) {
3396 char *name = g_strdup_printf ("WindowListEntry%d", i);
3397 gtk_ui_manager_add_ui (gtk->ui, gtk->windows.merge_id,
3398 "/menubar/View/Windows", name, name,
3399 GTK_UI_MANAGER_AUTO, TRUE);
3400 g_free (name);
3404 typedef struct {
3405 GtkActionGroup *actions;
3406 guint merge_id;
3407 } CustomUIHandle;
3409 static void
3410 cb_custom_ui_handler (GObject *gtk_action, WorkbookControl *wbc)
3412 GnmAction *action = g_object_get_data (gtk_action, "GnmAction");
3414 g_return_if_fail (action != NULL);
3415 g_return_if_fail (action->handler != NULL);
3417 action->handler (action, wbc, action->data);
3420 static void
3421 cb_add_custom_ui (G_GNUC_UNUSED GnmApp *app,
3422 GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3424 CustomUIHandle *details;
3425 GSList *ptr;
3426 GError *error = NULL;
3427 const char *ui_substr;
3429 details = g_new0 (CustomUIHandle, 1);
3430 details->actions = gtk_action_group_new (extra_ui->group_name);
3432 for (ptr = extra_ui->actions; ptr != NULL ; ptr = ptr->next) {
3433 GnmAction *action = ptr->data;
3434 GtkAction *res;
3435 GtkActionEntry entry;
3437 entry.name = action->id;
3438 entry.stock_id = action->icon_name;
3439 entry.label = action->label;
3440 entry.accelerator = NULL;
3441 entry.tooltip = NULL;
3442 entry.callback = G_CALLBACK (cb_custom_ui_handler);
3443 gtk_action_group_add_actions (details->actions, &entry, 1, gtk);
3444 res = gtk_action_group_get_action (details->actions, action->id);
3445 g_object_set_data (G_OBJECT (res), "GnmAction", action);
3447 gtk_ui_manager_insert_action_group (gtk->ui, details->actions, 0);
3449 ui_substr = strstr (extra_ui->layout, "<ui>");
3450 if (ui_substr == extra_ui->layout)
3451 ui_substr = NULL;
3453 details->merge_id = gtk_ui_manager_add_ui_from_string
3454 (gtk->ui, extra_ui->layout, -1, ui_substr ? NULL : &error);
3455 if (details->merge_id == 0 && ui_substr) {
3456 /* Work around bug 569724. */
3457 details->merge_id = gtk_ui_manager_add_ui_from_string
3458 (gtk->ui, ui_substr, -1, &error);
3461 if (error) {
3462 g_message ("building menus failed: %s", error->message);
3463 g_error_free (error);
3464 gtk_ui_manager_remove_action_group (gtk->ui, details->actions);
3465 g_object_unref (details->actions);
3466 g_free (details);
3467 } else {
3468 g_hash_table_insert (gtk->custom_uis, extra_ui, details);
3471 static void
3472 cb_remove_custom_ui (G_GNUC_UNUSED GnmApp *app,
3473 GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3475 CustomUIHandle *details = g_hash_table_lookup (gtk->custom_uis, extra_ui);
3476 if (NULL != details) {
3477 gtk_ui_manager_remove_ui (gtk->ui, details->merge_id);
3478 gtk_ui_manager_remove_action_group (gtk->ui, details->actions);
3479 g_object_unref (details->actions);
3480 g_hash_table_remove (gtk->custom_uis, extra_ui);
3484 static void
3485 cb_init_extra_ui (GnmAppExtraUI *extra_ui, WBCGtk *gtk)
3487 cb_add_custom_ui (NULL, extra_ui, gtk);
3490 /****************************************************************************/
3491 /* Toolbar menu */
3493 static void
3494 set_toolbar_style_for_position (GtkToolbar *tb, GtkPositionType pos)
3496 GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3498 static const GtkOrientation orientations[] = {
3499 GTK_ORIENTATION_VERTICAL, GTK_ORIENTATION_VERTICAL,
3500 GTK_ORIENTATION_HORIZONTAL, GTK_ORIENTATION_HORIZONTAL
3503 gtk_orientable_set_orientation (GTK_ORIENTABLE (tb),
3504 orientations[pos]);
3506 if (GTK_IS_HANDLE_BOX (box)) {
3507 static const GtkPositionType hdlpos[] = {
3508 GTK_POS_TOP, GTK_POS_TOP,
3509 GTK_POS_LEFT, GTK_POS_LEFT
3512 gtk_handle_box_set_handle_position (GTK_HANDLE_BOX (box),
3513 hdlpos[pos]);
3515 if (pos == GTK_POS_TOP || pos == GTK_POS_BOTTOM)
3516 g_object_set (G_OBJECT (tb), "hexpand", TRUE, "vexpand", FALSE, NULL);
3517 else
3518 g_object_set (G_OBJECT (tb), "vexpand", TRUE, "hexpand", FALSE, NULL);
3521 static void
3522 set_toolbar_position (GtkToolbar *tb, GtkPositionType pos, WBCGtk *gtk)
3524 GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3525 GtkContainer *zone = GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (box)));
3526 GtkContainer *new_zone = GTK_CONTAINER (gtk->toolbar_zones[pos]);
3527 char const *name = g_object_get_data (G_OBJECT (box), "name");
3528 const char *key = "toolbar-order";
3529 int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), key));
3530 GList *children, *l;
3531 int cpos = 0;
3533 if (zone == new_zone)
3534 return;
3536 g_object_ref (box);
3537 if (zone)
3538 gtk_container_remove (zone, box);
3539 set_toolbar_style_for_position (tb, pos);
3541 children = gtk_container_get_children (new_zone);
3542 for (l = children; l; l = l->next) {
3543 GObject *child = l->data;
3544 int nc = GPOINTER_TO_INT (g_object_get_data (child, key));
3545 if (nc < n) cpos++;
3547 g_list_free (children);
3549 gtk_container_add (new_zone, box);
3550 gtk_container_child_set (new_zone, box, "position", cpos, NULL);
3552 g_object_unref (box);
3554 if (zone && name)
3555 gnm_conf_set_toolbar_position (name, pos);
3558 static void
3559 cb_set_toolbar_position (GtkMenuItem *item, WBCGtk *gtk)
3561 GtkToolbar *tb = g_object_get_data (G_OBJECT (item), "toolbar");
3562 GtkPositionType side = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "side"));
3564 if (gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
3565 set_toolbar_position (tb, side, gtk);
3568 static void
3569 cb_tcm_hide (GtkWidget *widget, GtkWidget *box)
3571 gtk_widget_hide (box);
3574 static void
3575 toolbar_context_menu (GtkToolbar *tb, WBCGtk *gtk, GdkEvent *event)
3577 GtkWidget *box = gtk_widget_get_parent (GTK_WIDGET (tb));
3578 GtkWidget *zone = gtk_widget_get_parent (GTK_WIDGET (box));
3579 GtkWidget *menu = gtk_menu_new ();
3580 GtkWidget *item;
3581 GSList *group = NULL;
3582 size_t ui;
3584 static const struct {
3585 char const *text;
3586 GtkPositionType pos;
3587 } pos_items[] = {
3588 { N_("Display toolbar above sheets"), GTK_POS_TOP },
3589 { N_("Display toolbar to the left of sheets"), GTK_POS_LEFT },
3590 { N_("Display toolbar to the right of sheets"), GTK_POS_RIGHT }
3593 if (gnm_debug_flag ("toolbar-size"))
3594 dump_size_tree (GTK_WIDGET (tb), GINT_TO_POINTER (0));
3596 for (ui = 0; ui < G_N_ELEMENTS (pos_items); ui++) {
3597 char const *text = _(pos_items[ui].text);
3598 GtkPositionType pos = pos_items[ui].pos;
3600 item = gtk_radio_menu_item_new_with_label (group, text);
3601 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
3603 gtk_check_menu_item_set_active
3604 (GTK_CHECK_MENU_ITEM (item),
3605 (zone == gtk->toolbar_zones[pos]));
3607 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3608 g_object_set_data (G_OBJECT (item), "toolbar", tb);
3609 g_object_set_data (G_OBJECT (item), "side", GINT_TO_POINTER (pos));
3610 g_signal_connect (G_OBJECT (item), "activate",
3611 G_CALLBACK (cb_set_toolbar_position),
3612 gtk);
3615 item = gtk_separator_menu_item_new ();
3616 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3618 item = gtk_menu_item_new_with_label (_("Hide"));
3619 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
3620 g_signal_connect (G_OBJECT (item), "activate",
3621 G_CALLBACK (cb_tcm_hide),
3622 box);
3624 gtk_widget_show_all (menu);
3625 gnumeric_popup_menu (GTK_MENU (menu), event);
3628 static gboolean
3629 cb_toolbar_button_press (GtkToolbar *tb, GdkEvent *event, WBCGtk *gtk)
3631 if (event->type == GDK_BUTTON_PRESS &&
3632 event->button.button == 3) {
3633 toolbar_context_menu (tb, gtk, event);
3634 return TRUE;
3637 return FALSE;
3640 static gboolean
3641 cb_handlebox_button_press (GtkHandleBox *hdlbox, GdkEvent *event, WBCGtk *gtk)
3643 if (event->type == GDK_BUTTON_PRESS &&
3644 event->button.button == 3) {
3645 GtkToolbar *tb = GTK_TOOLBAR (gtk_bin_get_child (GTK_BIN (hdlbox)));
3646 toolbar_context_menu (tb, gtk, event);
3647 return TRUE;
3650 return FALSE;
3654 static void
3655 cb_toolbar_activate (GtkToggleAction *action, WBCGtk *wbcg)
3657 wbcg_toggle_visibility (wbcg, action);
3660 static void
3661 cb_toolbar_box_visible (GtkWidget *box, G_GNUC_UNUSED GParamSpec *pspec,
3662 WBCGtk *wbcg)
3664 GtkToggleAction *toggle_action = g_object_get_data (
3665 G_OBJECT (box), "toggle_action");
3666 char const *name = g_object_get_data (G_OBJECT (box), "name");
3667 gboolean visible = gtk_widget_get_visible (box);
3669 gtk_toggle_action_set_active (toggle_action, visible);
3670 if (!wbcg->is_fullscreen) {
3672 * We do not persist changes made going-to/while-in/leaving
3673 * fullscreen mode.
3675 gnm_conf_set_toolbar_visible (name, visible);
3679 static struct ToolbarInfo {
3680 const char *name;
3681 const char *menu_text;
3682 const char *accel;
3683 } toolbar_info[] = {
3684 { "StandardToolbar", N_("Standard Toolbar"), "<control>7" },
3685 { "FormatToolbar", N_("Format Toolbar"), NULL },
3686 { "ObjectToolbar", N_("Object Toolbar"), NULL },
3687 { NULL, NULL, NULL }
3691 static void
3692 cb_add_menus_toolbars (G_GNUC_UNUSED GtkUIManager *ui,
3693 GtkWidget *w, WBCGtk *gtk)
3695 if (GTK_IS_TOOLBAR (w)) {
3696 WBCGtk *wbcg = (WBCGtk *)gtk;
3697 char const *name = gtk_widget_get_name (w);
3698 GtkToggleActionEntry entry;
3699 char *toggle_name = g_strconcat ("ViewMenuToolbar", name, NULL);
3700 char *tooltip = g_strdup_printf (_("Show/Hide toolbar %s"), _(name));
3701 gboolean visible = gnm_conf_get_toolbar_visible (name);
3702 int n = g_hash_table_size (wbcg->visibility_widgets);
3703 GtkWidget *vw;
3704 const struct ToolbarInfo *ti;
3705 GtkWidget *box;
3706 GtkPositionType pos = gnm_conf_get_toolbar_position (name);
3708 // See bug 761142. This isn't supposed to be necessary.
3709 gtk_style_context_invalidate (gtk_widget_get_style_context (w));
3711 if (gnm_conf_get_detachable_toolbars ()) {
3712 box = gtk_handle_box_new ();
3713 g_object_connect (box,
3714 "signal::child_attached", G_CALLBACK (cb_handlebox_dock_status), GINT_TO_POINTER (TRUE),
3715 "signal::child_detached", G_CALLBACK (cb_handlebox_dock_status), GINT_TO_POINTER (FALSE),
3716 NULL);
3717 } else
3718 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
3719 g_signal_connect (G_OBJECT (w),
3720 "button_press_event",
3721 G_CALLBACK (cb_toolbar_button_press),
3722 gtk);
3723 g_signal_connect (G_OBJECT (box),
3724 "button_press_event",
3725 G_CALLBACK (cb_handlebox_button_press),
3726 gtk);
3728 gtk_container_add (GTK_CONTAINER (box), w);
3729 gtk_widget_show_all (box);
3730 if (!visible)
3731 gtk_widget_hide (box);
3732 g_object_set_data (G_OBJECT (box), "toolbar-order",
3733 GINT_TO_POINTER (n));
3734 set_toolbar_position (GTK_TOOLBAR (w), pos, gtk);
3736 g_signal_connect (box,
3737 "notify::visible",
3738 G_CALLBACK (cb_toolbar_box_visible),
3739 gtk);
3740 g_object_set_data_full (G_OBJECT (box), "name",
3741 g_strdup (name),
3742 (GDestroyNotify)g_free);
3744 vw = box;
3745 g_hash_table_insert (wbcg->visibility_widgets,
3746 g_strdup (toggle_name),
3747 g_object_ref (vw));
3749 gtk_toolbar_set_show_arrow (GTK_TOOLBAR (w), TRUE);
3750 gtk_toolbar_set_style (GTK_TOOLBAR (w), GTK_TOOLBAR_ICONS);
3751 gtk_toolbar_set_icon_size (GTK_TOOLBAR (w), GTK_ICON_SIZE_SMALL_TOOLBAR);
3753 entry.name = toggle_name;
3754 entry.stock_id = NULL;
3755 entry.label = name;
3756 entry.accelerator = NULL;
3757 entry.tooltip = tooltip;
3758 entry.callback = G_CALLBACK (cb_toolbar_activate);
3759 entry.is_active = visible;
3761 for (ti = toolbar_info; ti->name; ti++) {
3762 if (strcmp (name, ti->name) == 0) {
3763 entry.label = _(ti->menu_text);
3764 entry.accelerator = ti->accel;
3765 break;
3769 gtk_action_group_add_toggle_actions (gtk->toolbar.actions,
3770 &entry, 1, wbcg);
3771 g_object_set_data (G_OBJECT (box), "toggle_action",
3772 gtk_action_group_get_action (gtk->toolbar.actions, toggle_name));
3773 gtk_ui_manager_add_ui (gtk->ui, gtk->toolbar.merge_id,
3774 "/menubar/View/Toolbars", toggle_name, toggle_name,
3775 GTK_UI_MANAGER_AUTO, FALSE);
3776 wbcg->hide_for_fullscreen =
3777 g_slist_prepend (wbcg->hide_for_fullscreen,
3778 gtk_action_group_get_action (gtk->toolbar.actions,
3779 toggle_name));
3781 g_free (tooltip);
3782 g_free (toggle_name);
3783 } else {
3784 gtk_box_pack_start (GTK_BOX (gtk->menu_zone), w, FALSE, TRUE, 0);
3785 gtk_widget_show_all (w);
3789 static void
3790 cb_clear_menu_tip (GOCmdContext *cc)
3792 go_cmd_context_progress_message_set (cc, " ");
3795 static void
3796 cb_show_menu_tip (GtkWidget *proxy, GOCmdContext *cc)
3798 GtkAction *action = g_object_get_data (G_OBJECT (proxy), "GtkAction");
3799 char *tip = NULL;
3800 g_object_get (action, "tooltip", &tip, NULL);
3801 if (tip) {
3802 go_cmd_context_progress_message_set (cc, _(tip));
3803 g_free (tip);
3804 } else
3805 cb_clear_menu_tip (cc);
3808 static void
3809 cb_connect_proxy (G_GNUC_UNUSED GtkUIManager *ui,
3810 GtkAction *action,
3811 GtkWidget *proxy,
3812 GOCmdContext *cc)
3814 /* connect whether there is a tip or not it may change later */
3815 if (GTK_IS_MENU_ITEM (proxy)) {
3816 g_object_set_data (G_OBJECT (proxy), "GtkAction", action);
3817 g_object_connect (proxy,
3818 "signal::select", G_CALLBACK (cb_show_menu_tip), cc,
3819 "swapped_signal::deselect", G_CALLBACK (cb_clear_menu_tip), cc,
3820 NULL);
3824 static void
3825 cb_disconnect_proxy (G_GNUC_UNUSED GtkUIManager *ui,
3826 G_GNUC_UNUSED GtkAction *action,
3827 GtkWidget *proxy,
3828 GOCmdContext *cc)
3830 if (GTK_IS_MENU_ITEM (proxy)) {
3831 g_object_set_data (G_OBJECT (proxy), "GtkAction", NULL);
3832 g_object_disconnect (proxy,
3833 "any_signal::select", G_CALLBACK (cb_show_menu_tip), cc,
3834 "any_signal::deselect", G_CALLBACK (cb_clear_menu_tip), cc,
3835 NULL);
3839 static void
3840 cb_post_activate (G_GNUC_UNUSED GtkUIManager *manager, GtkAction *action, WBCGtk *wbcg)
3842 if (!wbcg_is_editing (wbcg) && strcmp(gtk_action_get_name (action), "EditGotoCellIndicator") != 0)
3843 wbcg_focus_cur_scg (wbcg);
3846 static void
3847 cb_wbcg_window_state_event (GtkWidget *widget,
3848 GdkEventWindowState *event,
3849 WBCGtk *wbcg)
3851 gboolean new_val = (event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) != 0;
3852 if (!(event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) ||
3853 new_val == wbcg->is_fullscreen ||
3854 wbcg->updating_ui)
3855 return;
3857 wbc_gtk_set_toggle_action_state (wbcg, "ViewFullScreen", new_val);
3859 if (new_val) {
3860 GSList *l;
3862 wbcg->is_fullscreen = TRUE;
3863 for (l = wbcg->hide_for_fullscreen; l; l = l->next) {
3864 GtkToggleAction *ta = l->data;
3865 GOUndo *u;
3866 gboolean active = gtk_toggle_action_get_active (ta);
3867 u = go_undo_binary_new
3868 (ta, GUINT_TO_POINTER (active),
3869 (GOUndoBinaryFunc)gtk_toggle_action_set_active,
3870 NULL, NULL);
3871 wbcg->undo_for_fullscreen =
3872 go_undo_combine (wbcg->undo_for_fullscreen, u);
3873 gtk_toggle_action_set_active (ta, FALSE);
3875 } else {
3876 if (wbcg->undo_for_fullscreen) {
3877 go_undo_undo (wbcg->undo_for_fullscreen);
3878 g_object_unref (wbcg->undo_for_fullscreen);
3879 wbcg->undo_for_fullscreen = NULL;
3881 wbcg->is_fullscreen = FALSE;
3885 /****************************************************************************/
3887 static void
3888 cb_auto_expr_cell_changed (GtkWidget *item, WBCGtk *wbcg)
3890 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3891 const GnmEvalPos *ep;
3892 GnmExprTop const *texpr;
3893 GnmValue const *v;
3895 if (wbcg->updating_ui)
3896 return;
3898 ep = g_object_get_data (G_OBJECT (item), "evalpos");
3900 g_object_set (wbv,
3901 "auto-expr-func", NULL,
3902 "auto-expr-descr", NULL,
3903 "auto-expr-eval-pos", ep,
3904 NULL);
3906 /* Now we have the expression set. */
3907 texpr = wbv->auto_expr.dep.texpr;
3908 v = gnm_expr_top_get_constant (texpr);
3909 if (v)
3910 g_object_set (wbv,
3911 "auto-expr-descr", value_peek_string (v),
3912 NULL);
3915 static void
3916 cb_auto_expr_changed (GtkWidget *item, WBCGtk *wbcg)
3918 const GnmFunc *func;
3919 const char *descr;
3920 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3922 if (wbcg->updating_ui)
3923 return;
3925 func = g_object_get_data (G_OBJECT (item), "func");
3926 descr = g_object_get_data (G_OBJECT (item), "descr");
3928 g_object_set (wbv,
3929 "auto-expr-func", func,
3930 "auto-expr-descr", descr,
3931 "auto-expr-eval-pos", NULL,
3932 NULL);
3935 static void
3936 cb_auto_expr_precision_toggled (GtkWidget *item, WBCGtk *wbcg)
3938 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
3939 if (wbcg->updating_ui)
3940 return;
3942 go_object_toggle (wbv, "auto-expr-max-precision");
3945 static void
3946 cb_auto_expr_insert_formula (WBCGtk *wbcg, gboolean below)
3948 SheetControlGUI *scg = wbcg_cur_scg (wbcg);
3949 GnmRange const *selection = selection_first_range (scg_view (scg), NULL, NULL);
3950 GnmRange output;
3951 GnmRange *input;
3952 gboolean multiple, use_last_cr;
3953 data_analysis_output_t *dao;
3954 analysis_tools_data_auto_expression_t *specs;
3956 g_return_if_fail (selection != NULL);
3958 if (below) {
3959 multiple = (range_width (selection) > 1);
3960 output = *selection;
3961 range_normalize (&output);
3962 output.start.row = output.end.row;
3963 use_last_cr = (range_height (selection) > 1) && sheet_is_region_empty (scg_sheet (scg), &output);
3964 if (!use_last_cr) {
3965 if (range_translate (&output, scg_sheet (scg), 0, 1))
3966 return;
3967 if (multiple && gnm_sheet_get_last_col (scg_sheet (scg)) > output.end.col)
3968 output.end.col++;
3970 input = gnm_range_dup (selection);
3971 range_normalize (input);
3972 if (use_last_cr)
3973 input->end.row--;
3974 } else {
3975 multiple = (range_height (selection) > 1);
3976 output = *selection;
3977 range_normalize (&output);
3978 output.start.col = output.end.col;
3979 use_last_cr = (range_width (selection) > 1) && sheet_is_region_empty (scg_sheet (scg), &output);
3980 if (!use_last_cr) {
3981 if (range_translate (&output, scg_sheet (scg), 1, 0))
3982 return;
3983 if (multiple && gnm_sheet_get_last_row (scg_sheet (scg)) > output.end.row)
3984 output.end.row++;
3986 input = gnm_range_dup (selection);
3987 range_normalize (input);
3988 if (use_last_cr)
3989 input->end.col--;
3993 dao = dao_init (NULL, RangeOutput);
3994 dao->start_col = output.start.col;
3995 dao->start_row = output.start.row;
3996 dao->cols = range_width (&output);
3997 dao->rows = range_height (&output);
3998 dao->sheet = scg_sheet (scg);
3999 dao->autofit_flag = FALSE;
4000 dao->put_formulas = TRUE;
4002 specs = g_new0 (analysis_tools_data_auto_expression_t, 1);
4003 specs->base.wbc = GNM_WBC (wbcg);
4004 specs->base.input = g_slist_prepend (NULL, value_new_cellrange_r (scg_sheet (scg), input));
4005 g_free (input);
4006 specs->base.group_by = below ? GROUPED_BY_COL : GROUPED_BY_ROW;
4007 specs->base.labels = FALSE;
4008 specs->multiple = multiple;
4009 specs->below = below;
4010 specs->func = NULL;
4011 g_object_get (G_OBJECT (wb_control_view (GNM_WBC (wbcg))),
4012 "auto-expr-func", &(specs->func), NULL);
4013 if (specs->func == NULL) {
4014 specs->func = gnm_func_lookup_or_add_placeholder ("sum");
4015 gnm_func_inc_usage (specs->func);
4018 cmd_analysis_tool (GNM_WBC (wbcg), scg_sheet (scg),
4019 dao, specs, analysis_tool_auto_expression_engine,
4020 TRUE);
4023 static void
4024 cb_auto_expr_insert_formula_below (G_GNUC_UNUSED GtkWidget *item, WBCGtk *wbcg)
4026 cb_auto_expr_insert_formula (wbcg, TRUE);
4029 static void
4030 cb_auto_expr_insert_formula_to_side (G_GNUC_UNUSED GtkWidget *item, WBCGtk *wbcg)
4032 cb_auto_expr_insert_formula (wbcg, FALSE);
4036 static gboolean
4037 cb_select_auto_expr (GtkWidget *widget, GdkEvent *event, WBCGtk *wbcg)
4040 * WARNING * WARNING * WARNING
4042 * Keep the functions in lower case.
4043 * We currently register the functions in lower case and some locales
4044 * (notably tr_TR) do not have the same encoding for tolower that
4045 * locale C does.
4047 * eg tolower ('I') != 'i'
4048 * Which would break function lookup when looking up for function 'selectIon'
4049 * when it was registered as 'selection'
4051 * WARNING * WARNING * WARNING
4053 static struct {
4054 char const * const displayed_name;
4055 char const * const function;
4056 } const quick_compute_routines [] = {
4057 { N_("Sum"), "sum" },
4058 { N_("Min"), "min" },
4059 { N_("Max"), "max" },
4060 { N_("Average"), "average" },
4061 { N_("Count"), "count" },
4062 { NULL, NULL }
4065 WorkbookView *wbv = wb_control_view (GNM_WBC (wbcg));
4066 Sheet *sheet = wb_view_cur_sheet (wbv);
4067 GtkWidget *item, *menu;
4068 int i;
4069 char *cell_item;
4070 GnmCellPos const *pos;
4071 GnmEvalPos ep;
4073 if (event->button.button != 3)
4074 return FALSE;
4076 menu = gtk_menu_new ();
4078 for (i = 0; quick_compute_routines[i].displayed_name; i++) {
4079 GnmParsePos pp;
4080 char const *fname = quick_compute_routines[i].function;
4081 char const *dispname =
4082 _(quick_compute_routines[i].displayed_name);
4083 GnmExprTop const *new_auto_expr;
4084 GtkWidget *item;
4085 char *expr_txt;
4087 /* Test the expression... */
4088 parse_pos_init (&pp, sheet->workbook, sheet, 0, 0);
4089 expr_txt = g_strconcat (fname, "(",
4090 parsepos_as_string (&pp),
4091 ")", NULL);
4092 new_auto_expr = gnm_expr_parse_str
4093 (expr_txt, &pp, GNM_EXPR_PARSE_DEFAULT,
4094 sheet_get_conventions (sheet), NULL);
4095 g_free (expr_txt);
4096 if (!new_auto_expr)
4097 continue;
4098 gnm_expr_top_unref (new_auto_expr);
4100 item = gtk_menu_item_new_with_label (dispname);
4101 g_object_set_data (G_OBJECT (item),
4102 "func", gnm_func_lookup (fname, NULL));
4103 g_object_set_data (G_OBJECT (item),
4104 "descr", (gpointer)dispname);
4105 g_signal_connect (G_OBJECT (item),
4106 "activate",
4107 G_CALLBACK (cb_auto_expr_changed), wbcg);
4108 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4109 gtk_widget_show (item);
4112 item = gtk_separator_menu_item_new ();
4113 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4114 gtk_widget_show (item);
4116 pos = &(scg_view (wbcg_cur_scg (wbcg)))->edit_pos;
4117 eval_pos_init_pos (&ep, sheet, pos);
4118 cell_item = g_strdup_printf (_("Content of %s"), cellpos_as_string (pos));
4119 item = gtk_menu_item_new_with_label (cell_item);
4120 g_free (cell_item);
4121 g_object_set_data_full (G_OBJECT (item),
4122 "evalpos", g_memdup (&ep, sizeof (ep)),
4123 (GDestroyNotify)g_free);
4124 g_signal_connect (G_OBJECT (item), "activate",
4125 G_CALLBACK (cb_auto_expr_cell_changed), wbcg);
4126 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4127 gtk_widget_show (item);
4129 item = gtk_separator_menu_item_new ();
4130 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4131 gtk_widget_show (item);
4133 item = gtk_check_menu_item_new_with_label (_("Use Maximum Precision"));
4134 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
4135 wbv->auto_expr.use_max_precision);
4136 g_signal_connect (G_OBJECT (item), "activate",
4137 G_CALLBACK (cb_auto_expr_precision_toggled), wbcg);
4138 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4139 gtk_widget_show (item);
4141 item = gtk_separator_menu_item_new ();
4142 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4143 gtk_widget_show (item);
4145 item = gtk_menu_item_new_with_label (_("Insert Formula Below"));
4146 g_signal_connect (G_OBJECT (item), "activate",
4147 G_CALLBACK (cb_auto_expr_insert_formula_below), wbcg);
4148 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4149 gtk_widget_show (item);
4151 item = gtk_menu_item_new_with_label (_("Insert Formula to Side"));
4152 g_signal_connect (G_OBJECT (item), "activate",
4153 G_CALLBACK (cb_auto_expr_insert_formula_to_side), wbcg);
4154 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
4155 gtk_widget_show (item);
4157 gnumeric_popup_menu (GTK_MENU (menu), event);
4158 return TRUE;
4161 static void
4162 wbc_gtk_create_status_area (WBCGtk *wbcg)
4164 GtkWidget *ebox;
4166 g_object_ref (wbcg->auto_expr_label);
4167 gtk_label_set_max_width_chars (GTK_LABEL (wbcg->auto_expr_label),
4168 strlen (AUTO_EXPR_SAMPLE));
4169 gtk_widget_set_size_request
4170 (wbcg->auto_expr_label,
4171 gnm_widget_measure_string (GTK_WIDGET (wbcg->toplevel),
4172 AUTO_EXPR_SAMPLE),
4173 -1);
4175 gtk_widget_set_size_request
4176 (wbcg->status_text,
4177 gnm_widget_measure_string (GTK_WIDGET (wbcg->toplevel),
4178 "W") * 5,
4179 -1);
4180 ebox = GET_GUI_ITEM ("auto_expr_event_box");
4181 gtk_style_context_add_class (gtk_widget_get_style_context (ebox),
4182 "auto-expr");
4183 g_signal_connect (G_OBJECT (ebox),
4184 "button_press_event",
4185 G_CALLBACK (cb_select_auto_expr), wbcg);
4187 g_hash_table_insert (wbcg->visibility_widgets,
4188 g_strdup ("ViewStatusbar"),
4189 g_object_ref (wbcg->status_area));
4191 /* disable statusbar by default going to fullscreen */
4192 wbcg->hide_for_fullscreen =
4193 g_slist_prepend (wbcg->hide_for_fullscreen,
4194 wbcg_find_action (wbcg, "ViewStatusbar"));
4195 g_assert (wbcg->hide_for_fullscreen->data);
4198 /****************************************************************************/
4200 static void
4201 cb_file_history_activate (GObject *action, WBCGtk *wbcg)
4203 gui_file_read (wbcg, g_object_get_data (action, "uri"), NULL, NULL);
4206 static void
4207 wbc_gtk_reload_recent_file_menu (WBCGtk *wbcg)
4209 WBCGtk *gtk = (WBCGtk *)wbcg;
4210 GSList *history, *ptr;
4211 unsigned i;
4212 gboolean any_history;
4213 GtkAction *full_history;
4215 if (gtk->file_history.merge_id != 0)
4216 gtk_ui_manager_remove_ui (gtk->ui, gtk->file_history.merge_id);
4217 gtk->file_history.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
4219 if (gtk->file_history.actions != NULL) {
4220 gtk_ui_manager_remove_action_group (gtk->ui,
4221 gtk->file_history.actions);
4222 g_object_unref (gtk->file_history.actions);
4224 gtk->file_history.actions = gtk_action_group_new ("FileHistory");
4226 /* create the actions */
4227 history = gnm_app_history_get_list (3);
4228 any_history = (history != NULL);
4229 for (i = 1, ptr = history; ptr != NULL ; ptr = ptr->next, i++) {
4230 GtkActionEntry entry;
4231 GtkAction *action;
4232 char const *uri = ptr->data;
4233 char *name = g_strdup_printf ("FileHistoryEntry%d", i);
4234 char *label = gnm_history_item_label (uri, i);
4235 char *filename = go_filename_from_uri (uri);
4236 char *filename_utf8 = filename ? g_filename_to_utf8 (filename, -1, NULL, NULL, NULL) : NULL;
4237 char *tooltip = g_strdup_printf (_("Open %s"), filename_utf8 ? filename_utf8 : uri);
4239 entry.name = name;
4240 entry.stock_id = NULL;
4241 entry.label = label;
4242 entry.accelerator = NULL;
4243 entry.tooltip = tooltip;
4244 entry.callback = G_CALLBACK (cb_file_history_activate);
4245 gtk_action_group_add_actions (gtk->file_history.actions,
4246 &entry, 1, (WBCGtk *)wbcg);
4247 action = gtk_action_group_get_action (gtk->file_history.actions,
4248 name);
4249 g_object_set_data_full (G_OBJECT (action), "uri",
4250 g_strdup (uri), (GDestroyNotify)g_free);
4252 g_free (name);
4253 g_free (label);
4254 g_free (filename);
4255 g_free (filename_utf8);
4256 g_free (tooltip);
4258 g_slist_free_full (history, (GDestroyNotify)g_free);
4260 gtk_ui_manager_insert_action_group (gtk->ui, gtk->file_history.actions, 0);
4262 /* merge them in */
4263 while (i-- > 1) {
4264 char *name = g_strdup_printf ("FileHistoryEntry%d", i);
4265 gtk_ui_manager_add_ui (gtk->ui, gtk->file_history.merge_id,
4266 "/menubar/File/FileHistory", name, name,
4267 GTK_UI_MANAGER_AUTO, TRUE);
4268 g_free (name);
4271 full_history = wbcg_find_action (wbcg, "FileHistoryFull");
4272 g_object_set (G_OBJECT (full_history), "sensitive", any_history, NULL);
4275 static void
4276 cb_new_from_template (GObject *action, WBCGtk *wbcg)
4278 const char *uri = g_object_get_data (action, "uri");
4279 gnm_gui_file_template (wbcg, uri);
4282 static void
4283 add_template_dir (const char *path, GHashTable *h)
4285 GDir *dir;
4286 const char *name;
4288 dir = g_dir_open (path, 0, NULL);
4289 if (!dir)
4290 return;
4292 while ((name = g_dir_read_name (dir))) {
4293 char *fullname = g_build_filename (path, name, NULL);
4296 * Unconditionally remove, so we can link to /dev/null
4297 * and cause a system file to be hidden.
4299 g_hash_table_remove (h, name);
4301 if (g_file_test (fullname, G_FILE_TEST_IS_REGULAR)) {
4302 char *uri = go_filename_to_uri (fullname);
4303 g_hash_table_insert (h, g_strdup (name), uri);
4305 g_free (fullname);
4307 g_dir_close (dir);
4310 static void
4311 wbc_gtk_reload_templates (WBCGtk *gtk)
4313 unsigned i;
4314 GSList *l, *names;
4315 char *path;
4316 GHashTable *h;
4318 if (gtk->templates.merge_id != 0)
4319 gtk_ui_manager_remove_ui (gtk->ui, gtk->templates.merge_id);
4320 gtk->templates.merge_id = gtk_ui_manager_new_merge_id (gtk->ui);
4322 if (gtk->templates.actions != NULL) {
4323 gtk_ui_manager_remove_action_group (gtk->ui,
4324 gtk->templates.actions);
4325 g_object_unref (gtk->templates.actions);
4327 gtk->templates.actions = gtk_action_group_new ("TemplateList");
4329 gtk_ui_manager_insert_action_group (gtk->ui, gtk->templates.actions, 0);
4331 h = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
4333 path = g_build_filename (gnm_sys_data_dir (), "templates", NULL);
4334 add_template_dir (path, h);
4335 g_free (path);
4337 /* Possibly override the above with user templates without version. */
4338 path = g_build_filename (gnm_usr_dir (FALSE), "templates", NULL);
4339 add_template_dir (path, h);
4340 g_free (path);
4342 /* Possibly override the above with user templates with version. */
4343 path = g_build_filename (gnm_usr_dir (TRUE), "templates", NULL);
4344 add_template_dir (path, h);
4345 g_free (path);
4347 names = g_slist_sort (go_hash_keys (h), (GCompareFunc)g_utf8_collate);
4349 for (i = 1, l = names; l; l = l->next) {
4350 const char *uri = g_hash_table_lookup (h, l->data);
4351 GString *label = g_string_new (NULL);
4352 GtkActionEntry entry;
4353 char *gname;
4354 const char *gpath;
4355 char *basename = go_basename_from_uri (uri);
4356 const char *s;
4357 GtkAction *action;
4359 if (i < 10) g_string_append_c (label, '_');
4360 g_string_append_printf (label, "%d ", i);
4362 for (s = basename; *s; s++) {
4363 if (*s == '_') g_string_append_c (label, '_');
4364 g_string_append_c (label, *s);
4367 entry.name = gname = g_strdup_printf ("Template%d", i);
4368 entry.stock_id = NULL;
4369 entry.label = label->str;
4370 entry.accelerator = NULL;
4371 entry.tooltip = NULL;
4372 entry.callback = G_CALLBACK (cb_new_from_template);
4374 gtk_action_group_add_actions (gtk->templates.actions,
4375 &entry, 1, gtk);
4377 action = gtk_action_group_get_action (gtk->templates.actions,
4378 entry.name);
4380 g_object_set_data_full (G_OBJECT (action), "uri",
4381 g_strdup (uri), (GDestroyNotify)g_free);
4384 gpath = "/menubar/File/Templates";
4385 gtk_ui_manager_add_ui (gtk->ui, gtk->templates.merge_id,
4386 gpath, gname, gname,
4387 GTK_UI_MANAGER_AUTO, FALSE);
4389 g_string_free (label, TRUE);
4390 g_free (gname);
4391 g_free (basename);
4392 i++;
4395 g_slist_free (names);
4396 g_hash_table_destroy (h);
4399 gboolean
4400 wbc_gtk_load_templates (WBCGtk *wbcg)
4402 if (wbcg->templates.merge_id == 0) {
4403 wbc_gtk_reload_templates (wbcg);
4406 wbcg->template_loader_handler = 0;
4407 return FALSE;
4410 static void
4411 wbcg_set_toplevel (WBCGtk *wbcg, GtkWidget *w)
4413 static GtkTargetEntry const drag_types[] = {
4414 { (char *) "text/uri-list", 0, TARGET_URI_LIST },
4415 { (char *) "GNUMERIC_SHEET", 0, TARGET_SHEET },
4416 { (char *) "GNUMERIC_SAME_PROC", GTK_TARGET_SAME_APP, 0 }
4419 g_return_if_fail (wbcg->toplevel == NULL);
4421 wbcg->toplevel = w;
4422 w = GTK_WIDGET (wbcg_toplevel (wbcg));
4423 g_return_if_fail (GTK_IS_WINDOW (w));
4425 g_object_set (G_OBJECT (w),
4426 "resizable", TRUE,
4427 NULL);
4429 g_signal_connect_data (w, "delete_event",
4430 G_CALLBACK (wbc_gtk_close), wbcg, NULL,
4431 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
4432 g_signal_connect_after (w, "set_focus",
4433 G_CALLBACK (cb_set_focus), wbcg);
4434 g_signal_connect (w, "scroll-event",
4435 G_CALLBACK (cb_scroll_wheel), wbcg);
4436 g_signal_connect (w, "realize",
4437 G_CALLBACK (cb_realize), wbcg);
4438 g_signal_connect (w, "screen-changed",
4439 G_CALLBACK (cb_screen_changed), NULL);
4440 cb_screen_changed (w);
4442 /* Setup a test of Drag and Drop */
4443 gtk_drag_dest_set (GTK_WIDGET (w),
4444 GTK_DEST_DEFAULT_ALL, drag_types, G_N_ELEMENTS (drag_types),
4445 GDK_ACTION_COPY | GDK_ACTION_MOVE);
4446 gtk_drag_dest_add_image_targets (GTK_WIDGET (w));
4447 gtk_drag_dest_add_text_targets (GTK_WIDGET (w));
4448 g_object_connect (G_OBJECT (w),
4449 "signal::drag-leave", G_CALLBACK (cb_wbcg_drag_leave), wbcg,
4450 "signal::drag-data-received", G_CALLBACK (cb_wbcg_drag_data_received), wbcg,
4451 "signal::drag-motion", G_CALLBACK (cb_wbcg_drag_motion), wbcg,
4452 #if 0
4453 "signal::drag-data-get", G_CALLBACK (wbcg_drag_data_get), wbc,
4454 #endif
4455 NULL);
4458 /***************************************************************************/
4460 static void
4461 wbc_gtk_get_property (GObject *object, guint property_id,
4462 GValue *value, GParamSpec *pspec)
4464 WBCGtk *wbcg = (WBCGtk *)object;
4466 switch (property_id) {
4467 case WBG_GTK_PROP_AUTOSAVE_PROMPT:
4468 g_value_set_boolean (value, wbcg->autosave_prompt);
4469 break;
4470 case WBG_GTK_PROP_AUTOSAVE_TIME:
4471 g_value_set_int (value, wbcg->autosave_time);
4472 break;
4473 default:
4474 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
4475 break;
4479 static void
4480 wbc_gtk_set_property (GObject *object, guint property_id,
4481 const GValue *value, GParamSpec *pspec)
4483 WBCGtk *wbcg = (WBCGtk *)object;
4485 switch (property_id) {
4486 case WBG_GTK_PROP_AUTOSAVE_PROMPT:
4487 wbcg->autosave_prompt = g_value_get_boolean (value);
4488 break;
4489 case WBG_GTK_PROP_AUTOSAVE_TIME:
4490 wbcg_set_autosave_time (wbcg, g_value_get_int (value));
4491 break;
4492 default:
4493 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
4494 break;
4498 static void
4499 wbc_gtk_finalize (GObject *obj)
4501 WBCGtk *wbcg = WBC_GTK (obj);
4503 if (wbcg->idle_update_style_feedback != 0)
4504 g_source_remove (wbcg->idle_update_style_feedback);
4506 if (wbcg->template_loader_handler != 0) {
4507 g_source_remove (wbcg->template_loader_handler);
4508 wbcg->template_loader_handler = 0;
4511 if (wbcg->file_history.merge_id != 0)
4512 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->file_history.merge_id);
4513 g_clear_object (&wbcg->file_history.actions);
4515 if (wbcg->toolbar.merge_id != 0)
4516 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->toolbar.merge_id);
4517 g_clear_object (&wbcg->toolbar.actions);
4519 if (wbcg->windows.merge_id != 0)
4520 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->windows.merge_id);
4521 g_clear_object (&wbcg->windows.actions);
4523 if (wbcg->templates.merge_id != 0)
4524 gtk_ui_manager_remove_ui (wbcg->ui, wbcg->templates.merge_id);
4525 g_clear_object (&wbcg->templates.actions);
4528 GSList *l, *uis = go_hash_keys (wbcg->custom_uis);
4529 for (l = uis; l; l = l->next) {
4530 GnmAppExtraUI *extra_ui = l->data;
4531 cb_remove_custom_ui (NULL, extra_ui, wbcg);
4533 g_slist_free (uis);
4536 g_hash_table_destroy (wbcg->custom_uis);
4537 wbcg->custom_uis = NULL;
4539 g_clear_object (&wbcg->zoom_vaction);
4540 g_clear_object (&wbcg->zoom_haction);
4541 g_clear_object (&wbcg->borders);
4542 g_clear_object (&wbcg->fore_color);
4543 g_clear_object (&wbcg->back_color);
4544 g_clear_object (&wbcg->font_name_haction);
4545 g_clear_object (&wbcg->font_name_vaction);
4546 g_clear_object (&wbcg->redo_haction);
4547 g_clear_object (&wbcg->redo_vaction);
4548 g_clear_object (&wbcg->undo_haction);
4549 g_clear_object (&wbcg->undo_vaction);
4550 g_clear_object (&wbcg->halignment);
4551 g_clear_object (&wbcg->valignment);
4552 g_clear_object (&wbcg->actions);
4553 g_clear_object (&wbcg->permanent_actions);
4554 g_clear_object (&wbcg->font_actions);
4555 g_clear_object (&wbcg->data_only_actions);
4556 g_clear_object (&wbcg->semi_permanent_actions);
4557 g_clear_object (&wbcg->ui);
4559 /* Disconnect signals that would attempt to change things during
4560 * destruction.
4563 wbcg_autosave_cancel (wbcg);
4565 if (wbcg->bnotebook != NULL)
4566 g_signal_handlers_disconnect_by_func (
4567 G_OBJECT (wbcg->bnotebook),
4568 G_CALLBACK (cb_notebook_switch_page), wbcg);
4569 g_clear_object (&wbcg->bnotebook);
4571 g_signal_handlers_disconnect_by_func (
4572 G_OBJECT (wbcg->toplevel),
4573 G_CALLBACK (cb_set_focus), wbcg);
4575 wbcg_auto_complete_destroy (wbcg);
4577 gtk_window_set_focus (wbcg_toplevel (wbcg), NULL);
4579 if (wbcg->toplevel != NULL) {
4580 gtk_widget_destroy (wbcg->toplevel);
4581 wbcg->toplevel = NULL;
4584 if (wbcg->font_desc) {
4585 pango_font_description_free (wbcg->font_desc);
4586 wbcg->font_desc = NULL;
4589 g_clear_object (&wbcg->auto_expr_label);
4591 g_hash_table_destroy (wbcg->visibility_widgets);
4592 g_clear_object (&wbcg->undo_for_fullscreen);
4594 g_slist_free (wbcg->hide_for_fullscreen);
4595 wbcg->hide_for_fullscreen = NULL;
4598 g_free (wbcg->preferred_geometry);
4599 wbcg->preferred_geometry = NULL;
4601 g_clear_object (&wbcg->gui);
4603 parent_class->finalize (obj);
4606 /***************************************************************************/
4608 typedef struct {
4609 GnmExprEntry *entry;
4610 GogDataset *dataset;
4611 int dim_i;
4612 gboolean suppress_update;
4613 GogDataType data_type;
4614 gboolean changed;
4616 gulong dataset_changed_handler;
4617 gulong entry_update_handler;
4618 guint idle;
4619 } GraphDimEditor;
4621 static void
4622 cb_graph_dim_editor_update (GnmExprEntry *gee,
4623 G_GNUC_UNUSED gboolean user_requested,
4624 GraphDimEditor *editor)
4626 GOData *data = NULL;
4627 Sheet *sheet;
4628 SheetControlGUI *scg;
4629 editor->changed = FALSE;
4631 /* Ignore changes while we are insensitive. useful for displaying
4632 * values, without storing them as Data. Also ignore updates if the
4633 * dataset has been cleared via the weakref handler */
4634 if (!gtk_widget_is_sensitive (GTK_WIDGET (gee)) ||
4635 editor->dataset == NULL)
4636 return;
4638 scg = gnm_expr_entry_get_scg (gee);
4639 sheet = scg_sheet (scg);
4641 /* If we are setting something */
4642 if (!gnm_expr_entry_is_blank (editor->entry)) {
4643 GnmParsePos pos;
4644 GnmParseError perr;
4645 GnmExprTop const *texpr;
4646 GnmExprParseFlags flags =
4647 (editor->data_type == GOG_DATA_VECTOR)?
4648 GNM_EXPR_PARSE_PERMIT_MULTIPLE_EXPRESSIONS |
4649 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS:
4650 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS;
4652 parse_error_init (&perr);
4653 /* Setting start_sel=FALSE to avoid */
4654 /* https://bugzilla.gnome.org/show_bug.cgi?id=658223 */
4655 texpr = gnm_expr_entry_parse (editor->entry,
4656 parse_pos_init_sheet (&pos, sheet),
4657 &perr, FALSE, flags);
4659 /* TODO : add some error dialogs split out
4660 * the code in workbook_edit to add parens. */
4661 if (texpr == NULL) {
4662 if (editor->data_type == GOG_DATA_SCALAR)
4663 texpr = gnm_expr_top_new_constant (
4664 value_new_string (
4665 gnm_expr_entry_get_text (editor->entry)));
4666 else {
4667 g_return_if_fail (perr.err != NULL);
4669 wb_control_validation_msg (GNM_WBC (scg_wbcg (scg)),
4670 GNM_VALIDATION_STYLE_INFO, NULL, perr.err->message);
4671 parse_error_free (&perr);
4672 gtk_editable_select_region (GTK_EDITABLE (gnm_expr_entry_get_entry (editor->entry)), 0, G_MAXINT);
4673 editor->changed = TRUE;
4674 return;
4678 switch (editor->data_type) {
4679 case GOG_DATA_SCALAR:
4680 data = gnm_go_data_scalar_new_expr (sheet, texpr);
4681 break;
4682 case GOG_DATA_VECTOR:
4683 data = gnm_go_data_vector_new_expr (sheet, texpr);
4684 break;
4685 case GOG_DATA_MATRIX:
4686 data = gnm_go_data_matrix_new_expr (sheet, texpr);
4690 /* The SheetObjectGraph does the magic to link things in */
4691 editor->suppress_update = TRUE;
4692 gog_dataset_set_dim (editor->dataset, editor->dim_i, data, NULL);
4693 editor->suppress_update = FALSE;
4696 static gboolean
4697 cb_update_idle (GraphDimEditor *editor)
4699 cb_graph_dim_editor_update (editor->entry, FALSE, editor);
4700 editor->idle = 0;
4701 return FALSE;
4704 static void
4705 graph_dim_cancel_idle (GraphDimEditor *editor)
4707 if (editor->idle) {
4708 g_source_remove (editor->idle);
4709 editor->idle = 0;
4713 static gboolean
4714 cb_graph_dim_entry_focus_out_event (G_GNUC_UNUSED GtkEntry *ignored,
4715 G_GNUC_UNUSED GdkEventFocus *event,
4716 GraphDimEditor *editor)
4718 if (!editor->changed)
4719 return FALSE;
4720 graph_dim_cancel_idle (editor);
4721 editor->idle = g_idle_add ((GSourceFunc) cb_update_idle, editor);
4723 return FALSE;
4726 static void
4727 cb_graph_dim_entry_changed (GraphDimEditor *editor)
4729 editor->changed = TRUE;
4732 static void
4733 set_entry_contents (GnmExprEntry *entry, GOData *val)
4735 if (GNM_IS_GO_DATA_SCALAR (val)) {
4736 GnmValue const *v = gnm_expr_top_get_constant (gnm_go_data_get_expr (val));
4737 if (v && VALUE_IS_NUMBER (v)) {
4738 double d = go_data_get_scalar_value (val);
4739 GODateConventions const *date_conv = go_data_date_conv (val);
4740 gog_data_editor_set_value_double (GOG_DATA_EDITOR (entry),
4741 d, date_conv);
4742 return;
4746 if (GO_IS_DATA_SCALAR (val) && go_data_has_value (val)) {
4747 double d = go_data_get_scalar_value (val);
4748 GODateConventions const *date_conv = go_data_date_conv (val);
4749 gog_data_editor_set_value_double (GOG_DATA_EDITOR (entry),
4750 d, date_conv);
4751 return;
4755 SheetControlGUI *scg = gnm_expr_entry_get_scg (entry);
4756 Sheet const *sheet = scg_sheet (scg);
4757 char *txt = go_data_serialize (val, (gpointer)sheet->convs);
4758 gnm_expr_entry_load_from_text (entry, txt);
4759 g_free (txt);
4763 static void
4764 cb_dataset_changed (GogDataset *dataset,
4765 gboolean resize,
4766 GraphDimEditor *editor)
4768 GOData *val = gog_dataset_get_dim (dataset, editor->dim_i);
4769 if (val != NULL && !editor->suppress_update) {
4770 g_signal_handler_block (editor->entry,
4771 editor->entry_update_handler);
4772 set_entry_contents (editor->entry, val);
4773 g_signal_handler_unblock (editor->entry,
4774 editor->entry_update_handler);
4778 static void
4779 cb_dim_editor_weakref_notify (GraphDimEditor *editor, GogDataset *dataset)
4781 g_return_if_fail (editor->dataset == dataset);
4782 editor->dataset = NULL;
4785 static void
4786 graph_dim_editor_free (GraphDimEditor *editor)
4788 graph_dim_cancel_idle (editor);
4789 if (editor->dataset) {
4790 g_signal_handler_disconnect (editor->dataset, editor->dataset_changed_handler);
4791 g_object_weak_unref (G_OBJECT (editor->dataset),
4792 (GWeakNotify) cb_dim_editor_weakref_notify, editor);
4794 g_free (editor);
4797 static GogDataEditor *
4798 wbcg_data_allocator_editor (GogDataAllocator *dalloc,
4799 GogDataset *dataset, int dim_i, GogDataType data_type)
4801 WBCGtk *wbcg = WBC_GTK (dalloc);
4802 GraphDimEditor *editor;
4803 GOData *val;
4805 editor = g_new (GraphDimEditor, 1);
4806 editor->dataset = dataset;
4807 editor->dim_i = dim_i;
4808 editor->suppress_update = FALSE;
4809 editor->data_type = data_type;
4810 editor->entry = gnm_expr_entry_new (wbcg, TRUE);
4811 editor->idle = 0;
4812 editor->changed = FALSE;
4813 g_object_weak_ref (G_OBJECT (editor->dataset),
4814 (GWeakNotify) cb_dim_editor_weakref_notify, editor);
4816 gnm_expr_entry_set_update_policy (editor->entry,
4817 GNM_UPDATE_DISCONTINUOUS);
4819 val = gog_dataset_get_dim (dataset, dim_i);
4820 if (val != NULL) {
4821 set_entry_contents (editor->entry, val);
4824 gnm_expr_entry_set_flags (editor->entry, GNM_EE_FORCE_ABS_REF, GNM_EE_MASK);
4826 editor->entry_update_handler = g_signal_connect (G_OBJECT (editor->entry),
4827 "update",
4828 G_CALLBACK (cb_graph_dim_editor_update), editor);
4829 g_signal_connect (G_OBJECT (gnm_expr_entry_get_entry (editor->entry)),
4830 "focus-out-event",
4831 G_CALLBACK (cb_graph_dim_entry_focus_out_event), editor);
4832 g_signal_connect_swapped (G_OBJECT (gnm_expr_entry_get_entry (editor->entry)),
4833 "changed",
4834 G_CALLBACK (cb_graph_dim_entry_changed), editor);
4835 editor->dataset_changed_handler = g_signal_connect (G_OBJECT (editor->dataset),
4836 "changed", G_CALLBACK (cb_dataset_changed), editor);
4837 g_object_set_data_full (G_OBJECT (editor->entry),
4838 "editor", editor, (GDestroyNotify) graph_dim_editor_free);
4840 return GOG_DATA_EDITOR (editor->entry);
4843 static void
4844 wbcg_data_allocator_allocate (GogDataAllocator *dalloc, GogPlot *plot)
4846 SheetControlGUI *scg = wbcg_cur_scg (WBC_GTK (dalloc));
4847 sv_selection_to_plot (scg_view (scg), plot);
4851 static void
4852 wbcg_go_plot_data_allocator_init (GogDataAllocatorClass *iface)
4854 iface->editor = wbcg_data_allocator_editor;
4855 iface->allocate = wbcg_data_allocator_allocate;
4858 /*************************************************************************/
4859 static char *
4860 wbcg_get_password (GOCmdContext *cc, char const* filename)
4862 WBCGtk *wbcg = WBC_GTK (cc);
4864 return dialog_get_password (wbcg_toplevel (wbcg), filename);
4866 static void
4867 wbcg_set_sensitive (GOCmdContext *cc, gboolean sensitive)
4869 GtkWindow *toplevel = wbcg_toplevel (WBC_GTK (cc));
4870 if (toplevel != NULL)
4871 gtk_widget_set_sensitive (GTK_WIDGET (toplevel), sensitive);
4873 static void
4874 wbcg_error_error (GOCmdContext *cc, GError *err)
4876 go_gtk_notice_dialog (wbcg_toplevel (WBC_GTK (cc)),
4877 GTK_MESSAGE_ERROR,
4878 "%s", err->message);
4881 static void
4882 wbcg_error_error_info (GOCmdContext *cc, GOErrorInfo *error)
4884 gnm_go_error_info_dialog_show (
4885 wbcg_toplevel (WBC_GTK (cc)), error);
4888 static void
4889 wbcg_error_error_info_list (GOCmdContext *cc, GSList *errs)
4891 gnm_go_error_info_list_dialog_show
4892 (wbcg_toplevel (WBC_GTK (cc)), errs);
4895 static void
4896 wbcg_progress_set (GOCmdContext *cc, double val)
4898 WBCGtk *wbcg = WBC_GTK (cc);
4899 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (wbcg->progress_bar), val);
4901 static void
4902 wbcg_progress_message_set (GOCmdContext *cc, gchar const *msg)
4904 WBCGtk *wbcg = WBC_GTK (cc);
4905 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (wbcg->progress_bar), msg);
4907 static void
4908 wbcg_gnm_cmd_context_init (GOCmdContextClass *iface)
4910 iface->get_password = wbcg_get_password;
4911 iface->set_sensitive = wbcg_set_sensitive;
4912 iface->error.error = wbcg_error_error;
4913 iface->error.error_info = wbcg_error_error_info;
4914 iface->error.error_info_list = wbcg_error_error_info_list;
4915 iface->progress_set = wbcg_progress_set;
4916 iface->progress_message_set = wbcg_progress_message_set;
4919 /*************************************************************************/
4921 static void
4922 wbc_gtk_class_init (GObjectClass *gobject_class)
4924 WorkbookControlClass *wbc_class =
4925 GNM_WBC_CLASS (gobject_class);
4927 g_return_if_fail (wbc_class != NULL);
4929 debug_tab_order = gnm_debug_flag ("tab-order");
4931 parent_class = g_type_class_peek_parent (gobject_class);
4932 gobject_class->get_property = wbc_gtk_get_property;
4933 gobject_class->set_property = wbc_gtk_set_property;
4934 gobject_class->finalize = wbc_gtk_finalize;
4936 wbc_class->edit_line_set = wbcg_edit_line_set;
4937 wbc_class->selection_descr_set = wbcg_edit_selection_descr_set;
4938 wbc_class->update_action_sensitivity = wbcg_update_action_sensitivity;
4940 wbc_class->sheet.add = wbcg_sheet_add;
4941 wbc_class->sheet.remove = wbcg_sheet_remove;
4942 wbc_class->sheet.focus = wbcg_sheet_focus;
4943 wbc_class->sheet.remove_all = wbcg_sheet_remove_all;
4945 wbc_class->undo_redo.labels = wbcg_undo_redo_labels;
4946 wbc_class->undo_redo.truncate = wbc_gtk_undo_redo_truncate;
4947 wbc_class->undo_redo.pop = wbc_gtk_undo_redo_pop;
4948 wbc_class->undo_redo.push = wbc_gtk_undo_redo_push;
4950 wbc_class->menu_state.update = wbcg_menu_state_update;
4952 wbc_class->claim_selection = wbcg_claim_selection;
4953 wbc_class->paste_from_selection = wbcg_paste_from_selection;
4954 wbc_class->validation_msg = wbcg_validation_msg;
4956 wbc_class->control_new = wbc_gtk_control_new;
4957 wbc_class->init_state = wbc_gtk_init_state;
4958 wbc_class->style_feedback = wbc_gtk_style_feedback;
4960 g_object_class_install_property (gobject_class,
4961 WBG_GTK_PROP_AUTOSAVE_PROMPT,
4962 g_param_spec_boolean ("autosave-prompt",
4963 P_("Autosave prompt"),
4964 P_("Ask about autosave?"),
4965 FALSE,
4966 GSF_PARAM_STATIC | G_PARAM_READWRITE));
4967 g_object_class_install_property (gobject_class,
4968 WBG_GTK_PROP_AUTOSAVE_TIME,
4969 g_param_spec_int ("autosave-time",
4970 P_("Autosave time in seconds"),
4971 P_("Seconds before autosave"),
4972 0, G_MAXINT, 0,
4973 GSF_PARAM_STATIC | G_PARAM_READWRITE));
4975 wbc_gtk_signals [WBC_GTK_MARKUP_CHANGED] = g_signal_new ("markup-changed",
4976 GNM_WBC_GTK_TYPE,
4977 G_SIGNAL_RUN_LAST,
4978 G_STRUCT_OFFSET (WBCGtkClass, markup_changed),
4979 NULL, NULL,
4980 g_cclosure_marshal_VOID__VOID,
4981 G_TYPE_NONE,
4982 0, G_TYPE_NONE);
4984 gtk_window_set_default_icon_name ("gnumeric");
4987 static void
4988 wbc_gtk_init (GObject *obj)
4990 WBCGtk *wbcg = (WBCGtk *)obj;
4991 GError *error = NULL;
4992 char *uifile;
4993 unsigned i;
4994 GEnumClass *posclass;
4995 GtkStyleContext *ctxt;
4996 guint merge_id;
4998 wbcg->gui = gnm_gtk_builder_load ("res:ui/wbcg.ui", NULL, NULL);
4999 wbcg->cancel_button = GET_GUI_ITEM ("cancel_button");
5000 wbcg->ok_button = GET_GUI_ITEM ("ok_button");
5001 wbcg->func_button = GET_GUI_ITEM ("func_button");
5002 wbcg->progress_bar = GET_GUI_ITEM ("progress_bar");
5003 wbcg->auto_expr_label = GET_GUI_ITEM ("auto_expr_label");
5004 wbcg->status_text = GET_GUI_ITEM ("status_text");
5005 wbcg->tabs_paned = GET_GUI_ITEM ("tabs_paned");
5006 wbcg->status_area = GET_GUI_ITEM ("status_area");
5007 wbcg->notebook_area = GET_GUI_ITEM ("notebook_area");
5008 wbcg->snotebook = GET_GUI_ITEM ("snotebook");
5009 wbcg->selection_descriptor = GET_GUI_ITEM ("selection_descriptor");
5010 wbcg->menu_zone = GET_GUI_ITEM ("menu_zone");
5011 wbcg->toolbar_zones[GTK_POS_TOP] = GET_GUI_ITEM ("toolbar_zone_top");
5012 wbcg->toolbar_zones[GTK_POS_BOTTOM] = NULL;
5013 wbcg->toolbar_zones[GTK_POS_LEFT] = GET_GUI_ITEM ("toolbar_zone_left");
5014 wbcg->toolbar_zones[GTK_POS_RIGHT] = GET_GUI_ITEM ("toolbar_zone_right");
5015 wbcg->updating_ui = FALSE;
5017 posclass = G_ENUM_CLASS (g_type_class_peek (gtk_position_type_get_type ()));
5018 for (i = 0; i < posclass->n_values; i++) {
5019 GEnumValue const *ev = posclass->values + i;
5020 GtkWidget *zone = wbcg->toolbar_zones[ev->value];
5021 GtkStyleContext *ctxt;
5022 if (!zone)
5023 continue;
5024 ctxt = gtk_widget_get_style_context (zone);
5025 gtk_style_context_add_class (ctxt, "toolbarzone");
5026 gtk_style_context_add_class (ctxt, ev->value_nick);
5029 wbcg->visibility_widgets = g_hash_table_new_full (g_str_hash,
5030 g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_object_unref);
5031 wbcg->undo_for_fullscreen = NULL;
5032 wbcg->hide_for_fullscreen = NULL;
5034 wbcg->autosave_prompt = FALSE;
5035 wbcg->autosave_time = 0;
5036 wbcg->autosave_timer = 0;
5038 /* We are not in edit mode */
5039 wbcg->editing = FALSE;
5040 wbcg->editing_sheet = NULL;
5041 wbcg->editing_cell = NULL;
5043 wbcg->new_object = NULL;
5045 wbcg->idle_update_style_feedback = 0;
5047 wbcg_set_toplevel (wbcg, GET_GUI_ITEM ("toplevel"));
5048 ctxt = gtk_widget_get_style_context (GTK_WIDGET (wbcg_toplevel (wbcg)));
5049 gtk_style_context_add_class (ctxt, "gnumeric");
5051 g_signal_connect (wbcg_toplevel (wbcg), "window_state_event",
5052 G_CALLBACK (cb_wbcg_window_state_event),
5053 wbcg);
5055 wbc_gtk_init_actions (wbcg);
5056 wbcg->ui = gtk_ui_manager_new ();
5057 g_object_connect (wbcg->ui,
5058 "signal::add_widget", G_CALLBACK (cb_add_menus_toolbars), wbcg,
5059 "signal::connect_proxy", G_CALLBACK (cb_connect_proxy), wbcg,
5060 "signal::disconnect_proxy", G_CALLBACK (cb_disconnect_proxy), wbcg,
5061 "signal::post_activate", G_CALLBACK (cb_post_activate), wbcg,
5062 NULL);
5063 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->permanent_actions, 0);
5064 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->actions, 0);
5065 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->font_actions, 0);
5066 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->data_only_actions, 0);
5067 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->semi_permanent_actions, 0);
5068 gtk_window_add_accel_group (wbcg_toplevel (wbcg),
5069 gtk_ui_manager_get_accel_group (wbcg->ui));
5071 if (extra_actions)
5072 gtk_action_group_add_actions (wbcg->actions, extra_actions,
5073 extra_actions_nb, wbcg);
5075 if (uifilename) {
5076 if (strncmp (uifilename, "res:", 4) == 0)
5077 uifile = g_strdup (uifilename);
5078 else
5079 uifile = g_build_filename (gnm_sys_data_dir (),
5080 uifilename,
5081 NULL);
5082 } else
5083 uifile = g_strdup ("res:/org/gnumeric/gnumeric/ui/GNOME_Gnumeric-gtk.xml");
5085 if (strncmp (uifile, "res:", 4) == 0)
5086 merge_id = gtk_ui_manager_add_ui_from_resource
5087 (wbcg->ui, uifile + 4, &error);
5088 else
5089 merge_id = gtk_ui_manager_add_ui_from_file
5090 (wbcg->ui, uifile, &error);
5091 if (!merge_id) {
5092 g_message ("building menus failed: %s", error->message);
5093 g_error_free (error);
5096 g_free (uifile);
5098 wbcg->custom_uis = g_hash_table_new_full (g_direct_hash, g_direct_equal,
5099 NULL, g_free);
5101 wbcg->file_history.actions = NULL;
5102 wbcg->file_history.merge_id = 0;
5104 wbcg->toolbar.merge_id = gtk_ui_manager_new_merge_id (wbcg->ui);
5105 wbcg->toolbar.actions = gtk_action_group_new ("Toolbars");
5106 gtk_ui_manager_insert_action_group (wbcg->ui, wbcg->toolbar.actions, 0);
5108 wbcg->windows.actions = NULL;
5109 wbcg->windows.merge_id = 0;
5111 wbcg->templates.actions = NULL;
5112 wbcg->templates.merge_id = 0;
5114 gnm_app_foreach_extra_ui ((GFunc) cb_init_extra_ui, wbcg);
5115 g_object_connect ((GObject *) gnm_app_get_app (),
5116 "swapped-object-signal::window-list-changed",
5117 G_CALLBACK (cb_regenerate_window_menu), wbcg,
5118 "object-signal::custom-ui-added",
5119 G_CALLBACK (cb_add_custom_ui), wbcg,
5120 "object-signal::custom-ui-removed",
5121 G_CALLBACK (cb_remove_custom_ui), wbcg,
5122 NULL);
5124 gtk_ui_manager_ensure_update (wbcg->ui);
5126 /* updates the undo/redo menu labels before check_underlines
5127 * to avoid problems like #324692. */
5128 wb_control_undo_redo_labels (GNM_WBC (wbcg), NULL, NULL);
5129 if (GNM_VERSION_MAJOR % 2 != 0 ||
5130 gnm_debug_flag ("underlines")) {
5131 gtk_container_foreach (GTK_CONTAINER (wbcg->menu_zone),
5132 (GtkCallback)check_underlines,
5133 (gpointer)"");
5136 wbcg_set_autosave_time (wbcg, gnm_conf_get_core_workbook_autosave_time ());
5139 GSF_CLASS_FULL (WBCGtk, wbc_gtk, NULL, NULL, wbc_gtk_class_init, NULL,
5140 wbc_gtk_init, GNM_WBC_TYPE, 0,
5141 GSF_INTERFACE (wbcg_go_plot_data_allocator_init, GOG_TYPE_DATA_ALLOCATOR);
5142 GSF_INTERFACE (wbcg_gnm_cmd_context_init, GO_TYPE_CMD_CONTEXT))
5144 /******************************************************************************/
5146 void
5147 wbc_gtk_markup_changer (WBCGtk *wbcg)
5149 g_signal_emit (G_OBJECT (wbcg), wbc_gtk_signals [WBC_GTK_MARKUP_CHANGED], 0);
5152 /******************************************************************************/
5154 * wbc_gtk_new:
5155 * @optional_view: (allow-none): #WorkbookView
5156 * @optional_wb: (allow-none) (transfer full): #Workbook
5157 * @optional_screen: (allow-none): #GdkScreen.
5158 * @optional_geometry: (allow-none): string.
5160 * Returns: (transfer none): (allow-none): the new #WBCGtk or %NULL.
5162 WBCGtk *
5163 wbc_gtk_new (WorkbookView *optional_view,
5164 Workbook *optional_wb,
5165 GdkScreen *optional_screen,
5166 gchar *optional_geometry)
5168 Sheet *sheet;
5169 WorkbookView *wbv;
5170 WBCGtk *wbcg = g_object_new (wbc_gtk_get_type (), NULL);
5171 WorkbookControl *wbc = (WorkbookControl *)wbcg;
5173 wbcg->preferred_geometry = g_strdup (optional_geometry);
5175 wbc_gtk_create_edit_area (wbcg);
5176 wbc_gtk_create_status_area (wbcg);
5177 wbc_gtk_reload_recent_file_menu (wbcg);
5179 g_signal_connect_object (gnm_app_get_app (),
5180 "notify::file-history-list",
5181 G_CALLBACK (wbc_gtk_reload_recent_file_menu), wbcg, G_CONNECT_SWAPPED);
5183 wb_control_set_view (wbc, optional_view, optional_wb);
5184 wbv = wb_control_view (wbc);
5185 sheet = wbv->current_sheet;
5186 if (sheet != NULL) {
5187 wb_control_menu_state_update (wbc, MS_ALL);
5188 wb_control_update_action_sensitivity (wbc);
5189 wb_control_style_feedback (wbc, NULL);
5190 cb_zoom_change (sheet, NULL, wbcg);
5193 wbc_gtk_create_notebook_area (wbcg);
5195 wbcg_view_changed (wbcg, NULL, NULL);
5197 if (optional_screen)
5198 gtk_window_set_screen (wbcg_toplevel (wbcg), optional_screen);
5200 /* Postpone showing the GUI, so that we may resize it freely. */
5201 g_idle_add ((GSourceFunc)show_gui, wbcg);
5203 /* Load this later when thing have calmed down. If this does not
5204 trigger by the time the file menu is activated, then the UI is
5205 updated right then -- and that looks sub-optimal because the
5206 "Templates" menu is empty (and thus not shown) until the
5207 update is done. */
5208 wbcg->template_loader_handler =
5209 g_timeout_add (1000, (GSourceFunc)wbc_gtk_load_templates, wbcg);
5211 wb_control_init_state (wbc);
5212 return wbcg;
5216 * wbcg_toplevel:
5217 * @wbcg: #WBCGtk
5219 * Returns: (transfer none): the toplevel #GtkWindow.
5221 GtkWindow *
5222 wbcg_toplevel (WBCGtk *wbcg)
5224 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5225 return GTK_WINDOW (wbcg->toplevel);
5228 void
5229 wbcg_set_transient (WBCGtk *wbcg, GtkWindow *window)
5231 go_gtk_window_set_transient (wbcg_toplevel (wbcg), window);
5235 wbcg_get_n_scg (WBCGtk const *wbcg)
5237 return (GTK_IS_NOTEBOOK (wbcg->snotebook))?
5238 gtk_notebook_get_n_pages (wbcg->snotebook): -1;
5242 * wbcg_get_nth_scg:
5243 * @wbcg: #WBCGtk
5244 * @i:
5246 * Returns: (transfer none): the scg associated with the @i-th tab in
5247 * @wbcg's notebook.
5248 * NOTE : @i != scg->sv->sheet->index_in_wb
5250 SheetControlGUI *
5251 wbcg_get_nth_scg (WBCGtk *wbcg, int i)
5253 SheetControlGUI *scg;
5254 GtkWidget *w;
5256 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5258 if (NULL != wbcg->snotebook &&
5259 NULL != (w = gtk_notebook_get_nth_page (wbcg->snotebook, i)) &&
5260 NULL != (scg = get_scg (w)) &&
5261 NULL != scg->grid &&
5262 NULL != scg_sheet (scg) &&
5263 NULL != scg_view (scg))
5264 return scg;
5266 return NULL;
5269 #warning merge these and clarfy whether we want the visible scg, or the logical (view) scg
5271 * wbcg_focus_cur_scg:
5272 * @wbcg: The workbook control to operate on.
5274 * A utility routine to safely ensure that the keyboard focus
5275 * is attached to the item-grid. This is required when a user
5276 * edits a combo-box or and entry-line which grab focus.
5278 * It is called for zoom, font name/size, and accept/cancel for the editline.
5279 * Returns: (transfer none): the sheet.
5281 Sheet *
5282 wbcg_focus_cur_scg (WBCGtk *wbcg)
5284 SheetControlGUI *scg;
5286 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5288 if (wbcg->snotebook == NULL)
5289 return NULL;
5291 scg = wbcg_get_nth_scg (wbcg,
5292 gtk_notebook_get_current_page (wbcg->snotebook));
5294 g_return_val_if_fail (scg != NULL, NULL);
5296 scg_take_focus (scg);
5297 return scg_sheet (scg);
5301 * wbcg_cur_scg:
5302 * @wbcg: #WBCGtk
5304 * Returns: (transfer none): the current #ShetControlGUI.
5306 SheetControlGUI *
5307 wbcg_cur_scg (WBCGtk *wbcg)
5309 return wbcg_get_scg (wbcg, wbcg_cur_sheet (wbcg));
5313 * wbcg_cur_sheet:
5314 * @wbcg: #WBCGtk
5316 * Returns: (transfer none): the current #Sheet.
5318 Sheet *
5319 wbcg_cur_sheet (WBCGtk *wbcg)
5321 return wb_control_cur_sheet (GNM_WBC (wbcg));
5324 PangoFontDescription *
5325 wbcg_get_font_desc (WBCGtk *wbcg)
5327 g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), NULL);
5329 if (!wbcg->font_desc) {
5330 GtkSettings *settings = wbcg_get_gtk_settings (wbcg);
5331 wbcg->font_desc = settings_get_font_desc (settings);
5332 g_signal_connect_object (settings, "notify::gtk-font-name",
5333 G_CALLBACK (cb_desktop_font_changed),
5334 wbcg, 0);
5336 return wbcg->font_desc;
5340 * wbcg_find_for_workbook:
5341 * @wb: #Workbook
5342 * @candidate: a candidate #WBCGtk
5343 * @pref_screen: the preferred screen.
5344 * @pref_display: the preferred display.
5346 * Returns: (transfer none): the found #WBCGtk or %NULL.
5348 WBCGtk *
5349 wbcg_find_for_workbook (Workbook *wb,
5350 WBCGtk *candidate,
5351 GdkScreen *pref_screen,
5352 GdkDisplay *pref_display)
5354 gboolean has_screen, has_display;
5356 g_return_val_if_fail (GNM_IS_WORKBOOK (wb), NULL);
5357 g_return_val_if_fail (candidate == NULL || GNM_IS_WBC_GTK (candidate), NULL);
5359 if (candidate && wb_control_get_workbook (GNM_WBC (candidate)) == wb)
5360 return candidate;
5362 if (!pref_screen && candidate)
5363 pref_screen = wbcg_get_screen (candidate);
5365 if (!pref_display && pref_screen)
5366 pref_display = gdk_screen_get_display (pref_screen);
5368 candidate = NULL;
5369 has_screen = FALSE;
5370 has_display = FALSE;
5371 WORKBOOK_FOREACH_CONTROL(wb, wbv, wbc, {
5372 if (GNM_IS_WBC_GTK (wbc)) {
5373 WBCGtk *wbcg = WBC_GTK (wbc);
5374 GdkScreen *screen = wbcg_get_screen (wbcg);
5375 GdkDisplay *display = gdk_screen_get_display (screen);
5377 if (pref_screen == screen && !has_screen) {
5378 has_screen = has_display = TRUE;
5379 candidate = wbcg;
5380 } else if (pref_display == display && !has_display) {
5381 has_display = TRUE;
5382 candidate = wbcg;
5383 } else if (!candidate)
5384 candidate = wbcg;
5388 return candidate;
5391 void
5392 wbcg_focus_current_cell_indicator (WBCGtk const *wbcg)
5394 gtk_widget_grab_focus (GTK_WIDGET (wbcg->selection_descriptor));
5395 gtk_editable_select_region (GTK_EDITABLE (wbcg->selection_descriptor), 0, -1);