wbc-gtk.c 171 KB
Newer Older
1 2 3
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
Jody Goldberg's avatar
Jody Goldberg committed
4
 * wbc-gtk.c: A gtk based WorkbookControl
5
 *
Jody Goldberg's avatar
Jody Goldberg committed
6
 * Copyright (C) 2000-2007 Jody Goldberg (jody@gnome.org)
7
 * Copyright (C) 2006-2012 Morten Welinder (terra@gnome.org)
8 9
 *
 * This program is free software; you can redistribute it and/or
10 11 12
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) version 3.
13 14 15 16 17 18 19 20
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
22 23 24 25
 * USA
 */
#include <gnumeric-config.h>
#include "gnumeric.h"
26
#include "wbc-gtk-impl.h"
27
#include "workbook-view.h"
Jody Goldberg's avatar
Jody Goldberg committed
28
#include "workbook-priv.h"
29
#include "gui-util.h"
30
#include "gutils.h"
31
#include "gui-file.h"
32
#include "sheet-control-gui-priv.h"
33
#include "sheet.h"
34 35
#include "sheet-private.h"
#include "sheet-view.h"
36
#include "sheet-style.h"
37
#include "sheet-filter.h"
38
#include "commands.h"
39
#include "dependent.h"
40 41
#include "application.h"
#include "history.h"
42 43
#include "func.h"
#include "value.h"
44 45
#include "style-font.h"
#include "gnm-format.h"
46 47
#include "expr.h"
#include "expr-impl.h"
48
#include "style-color.h"
49
#include "style-border.h"
50
#include "gnumeric-conf.h"
51
#include "dialogs/dialogs.h"
52
#include "widgets/gnm-fontbutton.h"
53 54 55 56 57
#include "gui-clipboard.h"
#include "libgnumeric.h"
#include "gnm-pane-impl.h"
#include "graph.h"
#include "selection.h"
58
#include "file-autoft.h"
59 60
#include "ranges.h"
#include "tools/analysis-auto-expression.h"
61
#include "sheet-object-cell-comment.h"
62
#include "print-info.h"
63
#include "expr-name.h"
64

65
#include <goffice/goffice.h>
66
#include <gsf/gsf-impl-utils.h>
67
#include <gsf/gsf-doc-meta-data.h>
68
#include <gtk/gtk.h>
69
#include "gdk/gdkkeysyms-compat.h"
70
#include "gnm-i18n.h"
71
#include <errno.h>
72
#include <string.h>
73

74 75
#define GET_GUI_ITEM(i_) (gpointer)(gtk_builder_get_object(wbcg->gui, (i_)))

76 77
#define	SHEET_CONTROL_KEY "SheetControl"

78 79
#define AUTO_EXPR_SAMPLE "Sumerage = -012345678901234"

80

81 82 83
enum {
	WBG_GTK_PROP_0,
	WBG_GTK_PROP_AUTOSAVE_PROMPT,
84
	WBG_GTK_PROP_AUTOSAVE_TIME
85
};
86 87

enum {
88 89
	WBC_GTK_MARKUP_CHANGED,
	WBC_GTK_LAST_SIGNAL
90 91 92 93 94 95 96
};

enum {
	TARGET_URI_LIST,
	TARGET_SHEET
};

97 98

static gboolean debug_tab_order;
99 100 101
static char const *uifilename = NULL;
static GtkActionEntry const *extra_actions = NULL;
static int extra_actions_nb;
102 103 104
static guint wbc_gtk_signals[WBC_GTK_LAST_SIGNAL];
static GObjectClass *parent_class = NULL;

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
static gboolean
wbcg_ui_update_begin (WBCGtk *wbcg)
{
	g_return_val_if_fail (IS_WBC_GTK (wbcg), FALSE);
	g_return_val_if_fail (!wbcg->updating_ui, FALSE);

	return (wbcg->updating_ui = TRUE);
}

static void
wbcg_ui_update_end (WBCGtk *wbcg)
{
	g_return_if_fail (IS_WBC_GTK (wbcg));
	g_return_if_fail (wbcg->updating_ui);

	wbcg->updating_ui = FALSE;
}

123
/****************************************************************************/
124

125
G_MODULE_EXPORT void
126
set_uifilename (char const *name, GtkActionEntry const *actions, int nb)
127 128
{
	uifilename = name;
129 130
	extra_actions = actions;
	extra_actions_nb = nb;
131 132
}

133 134 135
static void
wbc_gtk_set_action_sensitivity (WBCGtk const *wbcg,
				char const *action, gboolean sensitive)
136
{
137 138 139
	GtkAction *a = gtk_action_group_get_action (wbcg->actions, action);
	if (a == NULL)
		a = gtk_action_group_get_action (wbcg->permanent_actions, action);
140 141 142 143
	if (a == NULL)
		a = gtk_action_group_get_action (wbcg->semi_permanent_actions, action);
	if (a == NULL)
		a = gtk_action_group_get_action (wbcg->data_only_actions, action);
144
	g_object_set (G_OBJECT (a), "sensitive", sensitive, NULL);
145 146
}

147 148 149 150 151 152 153 154 155
/* NOTE : The semantics of prefix and suffix seem contrived.  Why are we
 * handling it at this end ?  That stuff should be done in the undo/redo code
 **/
static void
wbc_gtk_set_action_label (WBCGtk const *wbcg,
			  char const *action,
			  char const *prefix,
			  char const *suffix,
			  char const *new_tip)
156
{
157
	GtkAction *a = gtk_action_group_get_action (wbcg->actions, action);
158

159 160 161
	if (!a)
		a = gtk_action_group_get_action (wbcg->semi_permanent_actions, action);

162 163 164 165 166
	if (prefix != NULL) {
		char *text;
		gboolean is_suffix = (suffix != NULL);

		text = is_suffix ? g_strdup_printf ("%s: %s", prefix, suffix) : (char *) prefix;
Morten Welinder's avatar
Morten Welinder committed
167 168 169 170
		g_object_set (G_OBJECT (a),
			      "label",	   text,
			      "sensitive", is_suffix,
			      NULL);
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
		if (is_suffix)
			g_free (text);
	} else
		g_object_set (G_OBJECT (a), "label", suffix, NULL);

	if (new_tip != NULL)
		g_object_set (G_OBJECT (a), "tooltip", new_tip, NULL);
}

static void
wbc_gtk_set_toggle_action_state (WBCGtk const *wbcg,
				 char const *action, gboolean state)
{
	GtkAction *a = gtk_action_group_get_action (wbcg->actions, action);
	if (a == NULL)
		a = gtk_action_group_get_action (wbcg->font_actions, action);
	if (a == NULL)
		a = gtk_action_group_get_action (wbcg->toolbar.actions, action);
189 190
	if (a == NULL)
		a = gtk_action_group_get_action (wbcg->semi_permanent_actions, action);
191
	gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (a), state);
192 193
}

194 195
/****************************************************************************/

196 197 198
static SheetControlGUI *
wbcg_get_scg (WBCGtk *wbcg, Sheet *sheet)
{
199
	SheetControlGUI *scg;
200 201
	int i, npages;

202
	if (sheet == NULL || wbcg->snotebook == NULL)
203 204
		return NULL;

205 206 207 208 209 210 211 212 213
	npages = wbcg_get_n_scg (wbcg);
	if (npages == 0) {
		/*
		 * This can happen during construction when the clipboard is
		 * being cleared.  Ctrl-C Ctrl-Q.
		 */
		return NULL;
	}

214 215 216
	g_return_val_if_fail (IS_SHEET (sheet), NULL);
	g_return_val_if_fail (sheet->index_in_wb >= 0, NULL);

217 218 219
	scg = wbcg_get_nth_scg (wbcg, sheet->index_in_wb);
	if (NULL != scg && scg_sheet (scg) == sheet)
		return scg;
220 221 222 223 224 225

	/*
	 * index_in_wb is probably not accurate because we are in the
	 * middle of removing or adding a sheet.
	 */
	for (i = 0; i < npages; i++) {
226 227
		scg = wbcg_get_nth_scg (wbcg, i);
		if (NULL != scg && scg_sheet (scg) == sheet)
228 229 230 231 232 233 234
			return scg;
	}

	g_warning ("Failed to find scg for sheet %s", sheet->name_quoted);
	return NULL;
}

235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
static SheetControlGUI *
get_scg (const GtkWidget *w)
{
	return g_object_get_data (G_OBJECT (w), SHEET_CONTROL_KEY);
}

static GSList *
get_all_scgs (WBCGtk *wbcg)
{
	int i, n = gtk_notebook_get_n_pages (wbcg->snotebook);
	GSList *l = NULL;

	for (i = 0; i < n; i++) {
		GtkWidget *w = gtk_notebook_get_nth_page (wbcg->snotebook, i);
		SheetControlGUI *scg = get_scg (w);
		l = g_slist_prepend (l, scg);
	}

	return g_slist_reverse (l);
}

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
/* Autosave */

static gboolean
cb_autosave (WBCGtk *wbcg)
{
	WorkbookView *wb_view;

	g_return_val_if_fail (IS_WBC_GTK (wbcg), FALSE);

	wb_view = wb_control_view (WORKBOOK_CONTROL (wbcg));

	if (wb_view == NULL)
		return FALSE;

	if (wbcg->autosave_time > 0 &&
	    go_doc_is_dirty (wb_view_get_doc (wb_view))) {
272
		if (wbcg->autosave_prompt && !dialog_autosave_prompt (wbcg))
273 274 275 276 277 278 279
			return TRUE;
		gui_file_save (wbcg, wb_view);
	}
	return TRUE;
}

/**
280 281
 * wbcg_rangesel_possible:
 * @wbcg: the workbook control gui
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
 *
 * Returns true if the cursor keys should be used to select
 * a cell range (if the cursor is in a spot in the expression
 * where it makes sense to have a cell reference), false if not.
 **/
gboolean
wbcg_rangesel_possible (WBCGtk const *wbcg)
{
	g_return_val_if_fail (IS_WBC_GTK (wbcg), FALSE);

	/* Already range selecting */
	if (wbcg->rangesel != NULL)
		return TRUE;

	/* Rangesel requires that we be editing somthing */
	if (!wbcg_is_editing (wbcg) && !wbcg_entry_has_logical (wbcg))
		return FALSE;

	return gnm_expr_entry_can_rangesel (wbcg_get_entry_logical (wbcg));
}

gboolean
wbcg_is_editing (WBCGtk const *wbcg)
{
	g_return_val_if_fail (IS_WBC_GTK (wbcg), FALSE);
307
	return wbcg->editing;
308 309 310 311 312 313 314 315 316 317 318 319 320 321
}

static void
wbcg_autosave_cancel (WBCGtk *wbcg)
{
	if (wbcg->autosave_timer != 0) {
		g_source_remove (wbcg->autosave_timer);
		wbcg->autosave_timer = 0;
	}
}

static void
wbcg_autosave_activate (WBCGtk *wbcg)
{
322
	wbcg_autosave_cancel (wbcg);
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364

	if (wbcg->autosave_time > 0) {
		int secs = MIN (wbcg->autosave_time, G_MAXINT / 1000);
		wbcg->autosave_timer =
			g_timeout_add (secs * 1000,
				       (GSourceFunc) cb_autosave,
				       wbcg);
	}
}

static void
wbcg_set_autosave_time (WBCGtk *wbcg, int secs)
{
	if (secs == wbcg->autosave_time)
		return;

	wbcg->autosave_time = secs;
	wbcg_autosave_activate (wbcg);
}

/****************************************************************************/

static void
wbcg_edit_line_set (WorkbookControl *wbc, char const *text)
{
	GtkEntry *entry = wbcg_get_entry ((WBCGtk*)wbc);
	gtk_entry_set_text (entry, text);
}

static void
wbcg_edit_selection_descr_set (WorkbookControl *wbc, char const *text)
{
	WBCGtk *wbcg = (WBCGtk *)wbc;
	gtk_entry_set_text (GTK_ENTRY (wbcg->selection_descriptor), text);
}

static void
wbcg_update_action_sensitivity (WorkbookControl *wbc)
{
	WBCGtk *wbcg = WBC_GTK (wbc);
	SheetControlGUI	   *scg = wbcg_cur_scg (wbcg);
	gboolean edit_object = scg != NULL &&
365 366
		(scg->selected_objects != NULL || wbcg->new_object != NULL ||
		 scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT);
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
	gboolean enable_actions = TRUE;
	gboolean enable_edit_ok_cancel = FALSE;

	if (edit_object || wbcg->edit_line.guru != NULL)
		enable_actions = FALSE;
	else if (wbcg_is_editing (wbcg)) {
		enable_actions = FALSE;
		enable_edit_ok_cancel = TRUE;
	}

	/* These are only sensitive while editing */
	gtk_widget_set_sensitive (wbcg->ok_button, enable_edit_ok_cancel);
	gtk_widget_set_sensitive (wbcg->cancel_button, enable_edit_ok_cancel);
	gtk_widget_set_sensitive (wbcg->func_button, enable_actions);

382
	if (wbcg->snotebook) {
383 384 385
		gboolean tab_context_menu =
			enable_actions ||
			scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT;
Morten Welinder's avatar
Morten Welinder committed
386
		int i, N = wbcg_get_n_scg (wbcg);
387 388 389
		for (i = 0; i < N; i++) {
			GtkWidget *label =
				gnm_notebook_get_nth_label (wbcg->bnotebook, i);
390
			g_object_set_data (G_OBJECT (label), "editable",
391
					   GINT_TO_POINTER (tab_context_menu));
392 393 394
		}
	}

395 396 397 398 399 400
	g_object_set (G_OBJECT (wbcg->actions),
		"sensitive", enable_actions,
		NULL);
	g_object_set (G_OBJECT (wbcg->font_actions),
		"sensitive", enable_actions || enable_edit_ok_cancel,
		NULL);
401 402

	if (scg && scg_sheet (scg)->sheet_type == GNM_SHEET_OBJECT) {
403 404 405 406 407 408
		g_object_set (G_OBJECT (wbcg->data_only_actions),
			"sensitive", FALSE,
			NULL);
		g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
			"sensitive",TRUE,
			NULL);
409 410 411
		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), FALSE);
		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), FALSE);
	} else {
412 413 414 415 416 417
		g_object_set (G_OBJECT (wbcg->data_only_actions),
			"sensitive", TRUE,
			NULL);
		g_object_set (G_OBJECT (wbcg->semi_permanent_actions),
			"sensitive", enable_actions,
			NULL);
418
		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->edit_line.entry), TRUE);
419 420
		gtk_widget_set_sensitive (GTK_WIDGET (wbcg->selection_descriptor), TRUE);
	}
421 422 423 424 425 426 427 428 429
}

void
wbcg_insert_sheet (GtkWidget *unused, WBCGtk *wbcg)
{
	WorkbookControl *wbc = WORKBOOK_CONTROL (wbcg);
	Sheet *sheet = wb_control_cur_sheet (wbc);
	Workbook *wb = sheet->workbook;
	WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
430
	/* Use same size as current sheet.  */
431 432 433
	workbook_sheet_add (wb, sheet->index_in_wb,
			    gnm_sheet_get_max_cols (sheet),
			    gnm_sheet_get_max_rows (sheet));
434 435 436 437 438 439 440 441 442 443
	cmd_reorganize_sheets (wbc, old_state, sheet);
}

void
wbcg_append_sheet (GtkWidget *unused, WBCGtk *wbcg)
{
	WorkbookControl *wbc = WORKBOOK_CONTROL (wbcg);
	Sheet *sheet = wb_control_cur_sheet (wbc);
	Workbook *wb = sheet->workbook;
	WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
444
	/* Use same size as current sheet.  */
445 446 447
	workbook_sheet_add (wb, -1,
			    gnm_sheet_get_max_cols (sheet),
			    gnm_sheet_get_max_rows (sheet));
448 449 450 451 452 453 454 455 456 457 458 459
	cmd_reorganize_sheets (wbc, old_state, sheet);
}

void
wbcg_clone_sheet (GtkWidget *unused, WBCGtk *wbcg)
{
	WorkbookControl *wbc = WORKBOOK_CONTROL (wbcg);
	Sheet *sheet = wb_control_cur_sheet (wbc);
	Workbook *wb = sheet->workbook;
	WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
	Sheet *new_sheet = sheet_dup (sheet);
	workbook_sheet_attach_at_pos (wb, new_sheet, sheet->index_in_wb + 1);
460
	/* See workbook_sheet_add:  */
461 462 463 464 465
	g_signal_emit_by_name (G_OBJECT (wb), "sheet_added", 0);
	cmd_reorganize_sheets (wbc, old_state, sheet);
	g_object_unref (new_sheet);
}

466 467 468 469 470
static void
cb_show_sheet (SheetControlGUI *scg)
{
	WBCGtk *wbcg = scg->wbcg;
	int page_number = gtk_notebook_page_num (wbcg->snotebook,
471
						 GTK_WIDGET (scg->grid));
472 473 474 475
	gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
}


476

477 478 479 480
static void cb_sheets_manage (SheetControlGUI *scg) { dialog_sheet_order (scg->wbcg); }
static void cb_sheets_insert (SheetControlGUI *scg) { wbcg_insert_sheet (NULL, scg->wbcg); }
static void cb_sheets_add    (SheetControlGUI *scg) { wbcg_append_sheet (NULL, scg->wbcg); }
static void cb_sheets_clone  (SheetControlGUI *scg) { wbcg_clone_sheet  (NULL, scg->wbcg); }
481
static void cb_sheets_rename (SheetControlGUI *scg) { dialog_sheet_rename (scg->wbcg, scg_sheet (scg)); }
482
static void cb_sheets_resize (SheetControlGUI *scg) { dialog_sheet_resize (scg->wbcg); }
Morten Welinder's avatar
Morten Welinder committed
483

484 485 486 487 488 489 490 491 492 493 494 495 496

static gint
cb_by_scg_sheet_name (gconstpointer a_, gconstpointer b_)
{
	const SheetControlGUI *a = a_;
	const SheetControlGUI *b = b_;
	Sheet *sa = scg_sheet (a);
	Sheet *sb = scg_sheet (b);

	return g_utf8_collate (sa->name_unquoted, sb->name_unquoted);
}


497
static void
498
sheet_menu_label_run (SheetControlGUI *scg, GdkEvent *event)
499
{
500
	enum { CM_MULTIPLE = 1, CM_DATA_SHEET = 2 };
501
	struct SheetTabMenu {
502
		char const *text;
503
		void (*function) (SheetControlGUI *scg);
504
		int flags;
505
		int submenu;
506
	} const sheet_label_context_actions [] = {
507
		{ N_("Manage Sheets..."), &cb_sheets_manage,	0, 0},
508 509 510 511 512 513 514 515 516
		{ NULL, NULL, 0, 0 },
		{ N_("Insert"),		  &cb_sheets_insert,	0, 0 },
		{ N_("Append"),		  &cb_sheets_add,	0, 0 },
		{ N_("Duplicate"),	  &cb_sheets_clone,	0, 0 },
		{ N_("Remove"),		  &scg_delete_sheet_if_possible, CM_MULTIPLE, 0 },
		{ N_("Rename"),		  &cb_sheets_rename,	0, 0 },
		{ N_("Resize..."),        &cb_sheets_resize,    CM_DATA_SHEET, 0 },
		{ N_("Select"),           NULL,                 0, 1 },
		{ N_("Select (sorted)"),  NULL,                 0, 2 }
517 518
	};

519
	unsigned int ui;
520
	GtkWidget *item, *menu = gtk_menu_new ();
521 522 523 524
	GtkWidget *guru = wbc_gtk_get_guru (scg_wbcg (scg));
	unsigned int N_visible, pass;
	GtkWidget *submenus[2 + 1];
	GSList *scgs = get_all_scgs (scg->wbcg);
525

526 527
	for (pass = 1; pass <= 2; pass++) {
		GSList *l;
528

529 530
		submenus[pass] = gtk_menu_new ();
		N_visible = 0;
531 532 533 534 535 536
		for (l = scgs; l; l = l->next) {
			SheetControlGUI *scg1 = l->data;
			Sheet *sheet = scg_sheet (scg1);
			if (!sheet_is_visible (sheet))
				continue;

537 538
			N_visible++;

539 540 541
			item = gtk_menu_item_new_with_label (sheet->name_unquoted);
			g_signal_connect_swapped (G_OBJECT (item), "activate",
						  G_CALLBACK (cb_show_sheet), scg1);
542
			gtk_menu_shell_append (GTK_MENU_SHELL (submenus[pass]), item);
543 544 545
			gtk_widget_show (item);
		}

546 547 548 549 550 551 552 553
		scgs = g_slist_sort (scgs, cb_by_scg_sheet_name);
	}
	g_slist_free (scgs);

	for (ui = 0; ui < G_N_ELEMENTS (sheet_label_context_actions); ui++) {
		const struct SheetTabMenu *it =
			sheet_label_context_actions + ui;
		gboolean inactive =
554 555
			((it->flags & CM_MULTIPLE) && N_visible <= 1) ||
			((it->flags & CM_DATA_SHEET) && scg_sheet (scg)->sheet_type != GNM_SHEET_DATA) ||
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
			(!it->submenu && guru != NULL);

		item = it->text
			? gtk_menu_item_new_with_label (_(it->text))
			: gtk_separator_menu_item_new ();
		if (it->function)
			g_signal_connect_swapped (G_OBJECT (item), "activate",
						  G_CALLBACK (it->function), scg);
		if (it->submenu)
			gtk_menu_item_set_submenu (GTK_MENU_ITEM (item),
						   submenus[it->submenu]);

		gtk_widget_set_sensitive (item, !inactive);
		gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
		gtk_widget_show (item);
571 572
	}

573 574 575 576 577 578
	gnumeric_popup_menu (GTK_MENU (menu), event);
}

/**
 * cb_sheet_label_button_press:
 *
579
 * Invoked when the user has clicked on the sheet name widget.
580 581 582
 * This takes care of switching to the notebook that contains the label
 */
static gboolean
583
cb_sheet_label_button_press (GtkWidget *widget, GdkEvent *event,
584 585
			     SheetControlGUI *scg)
{
586
	WBCGtk *wbcg = scg->wbcg;
587
	gint page_number;
588

589 590 591
	if (event->type != GDK_BUTTON_PRESS)
		return FALSE;

592
	page_number = gtk_notebook_page_num (wbcg->snotebook,
593
					     GTK_WIDGET (scg->grid));
594
	gnm_notebook_set_current_page (wbcg->bnotebook, page_number);
595

596
	if (event->button.button == 1 || NULL != wbcg->rangesel)
597
		return FALSE;
598

599
	if (event->button.button == 3) {
600 601
		if ((scg_wbcg (scg))->edit_line.guru == NULL)
			scg_object_unselect (scg, NULL);
602
		if (g_object_get_data (G_OBJECT (widget), "editable")) {
603 604 605 606
			sheet_menu_label_run (scg, event);
			scg_take_focus (scg);
			return TRUE;
		}
607 608 609 610 611 612 613 614
	}

	return FALSE;
}

static void
cb_sheet_label_drag_data_get (GtkWidget *widget, GdkDragContext *context,
			      GtkSelectionData *selection_data,
615
			      guint info, guint time)
616
{
617 618
	SheetControlGUI *scg = get_scg (widget);
	g_return_if_fail (IS_SHEET_CONTROL_GUI (scg));
619

620
	scg_drag_data_get (scg, selection_data);
621 622 623 624 625 626 627 628
}

static void
cb_sheet_label_drag_data_received (GtkWidget *widget, GdkDragContext *context,
				   gint x, gint y, GtkSelectionData *data, guint info, guint time,
				   WBCGtk *wbcg)
{
	GtkWidget *w_source;
629 630
	SheetControlGUI *scg_src, *scg_dst;
	Sheet *s_src, *s_dst;
631 632 633 634 635 636 637 638 639 640

	g_return_if_fail (IS_WBC_GTK (wbcg));
	g_return_if_fail (GTK_IS_WIDGET (widget));

	w_source = gtk_drag_get_source_widget (context);
	if (!w_source) {
		g_warning ("Not yet implemented!"); /* Different process */
		return;
	}

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
	scg_src = get_scg (w_source);
	g_return_if_fail (scg_src != NULL);
	s_src = scg_sheet (scg_src);

	scg_dst = get_scg (widget);
	g_return_if_fail (scg_dst != NULL);
	s_dst = scg_sheet (scg_dst);

	if (s_src == s_dst) {
		/* Nothing */
	} else if (s_src->workbook == s_dst->workbook) {
		/* Move within workbook */
		Workbook *wb = s_src->workbook;
		int p_src = s_src->index_in_wb;
		int p_dst = s_dst->index_in_wb;
		WorkbookSheetState *old_state = workbook_sheet_state_new (wb);
		workbook_sheet_move (s_src, p_dst - p_src);
		cmd_reorganize_sheets (WORKBOOK_CONTROL (wbcg),
				       old_state,
				       s_src);
661
	} else {
662
		g_return_if_fail (IS_SHEET_CONTROL_GUI (gtk_selection_data_get_data (data)));
663 664 665 666 667 668 669 670 671 672 673 674

		/* Different workbook, same process */
		g_warning ("Not yet implemented!");
	}
}

static void
cb_sheet_label_drag_begin (GtkWidget *widget, GdkDragContext *context,
			   WBCGtk *wbcg)
{
	GtkWidget *arrow, *image;
	GdkPixbuf *pixbuf;
675 676
#warning GTK3: how can we mask there?
#if 0
677
	GdkBitmap *bitmap;
678
#endif
679 680 681 682 683

	g_return_if_fail (IS_WBC_GTK (wbcg));

	/* Create the arrow. */
	arrow = gtk_window_new (GTK_WINDOW_POPUP);
684 685
	gtk_window_set_screen (GTK_WINDOW (arrow),
			       gtk_widget_get_screen (widget));
686 687 688 689 690 691 692
	gtk_widget_realize (arrow);
	pixbuf = gtk_icon_theme_load_icon (
		gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)),
		"sheet_move_marker", 13, 0, NULL);
	image = gtk_image_new_from_pixbuf (pixbuf);
	gtk_widget_show (image);
	gtk_container_add (GTK_CONTAINER (arrow), image);
693
#if 0
694 695
	gdk_pixbuf_render_pixmap_and_mask_for_colormap (pixbuf,
		gtk_widget_get_colormap (widget), NULL, &bitmap, 0x7f);
696
#endif
697
	g_object_unref (pixbuf);
698
#if 0
699 700
	gtk_widget_shape_combine_mask (arrow, bitmap, 0, 0);
	g_object_unref (bitmap);
701
#endif
702 703 704 705 706 707 708 709 710 711 712 713 714 715
	g_object_ref_sink (arrow);
	g_object_set_data (G_OBJECT (widget), "arrow", arrow);
}

static void
cb_sheet_label_drag_end (GtkWidget *widget, GdkDragContext *context,
			 WBCGtk *wbcg)
{
	GtkWidget *arrow;

	g_return_if_fail (IS_WORKBOOK_CONTROL (wbcg));

	/* Destroy the arrow. */
	arrow = g_object_get_data (G_OBJECT (widget), "arrow");
716
	gtk_widget_destroy (arrow);
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
	g_object_unref (arrow);
	g_object_set_data (G_OBJECT (widget), "arrow", NULL);
}

static void
cb_sheet_label_drag_leave (GtkWidget *widget, GdkDragContext *context,
			   guint time, WBCGtk *wbcg)
{
	GtkWidget *w_source, *arrow;

	/* Hide the arrow. */
	w_source = gtk_drag_get_source_widget (context);
	if (w_source) {
		arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
		gtk_widget_hide (arrow);
	}
}

static gboolean
cb_sheet_label_drag_motion (GtkWidget *widget, GdkDragContext *context,
737
			    gint x, gint y, guint time, WBCGtk *wbcg)
738
{
739
	SheetControlGUI *scg_src, *scg_dst;
740
	GtkWidget *w_source, *arrow, *window;
741
	gint root_x, root_y, pos_x, pos_y;
742
	GtkAllocation wa, wsa;
743 744 745 746 747 748

	g_return_val_if_fail (IS_WBC_GTK (wbcg), FALSE);
	g_return_val_if_fail (IS_WBC_GTK (wbcg), FALSE);

	/* Make sure we are really hovering over another label. */
	w_source = gtk_drag_get_source_widget (context);
749
	if (!w_source)
750
		return FALSE;
751

752
	arrow = g_object_get_data (G_OBJECT (w_source), "arrow");
753 754 755 756 757

	scg_src = get_scg (w_source);
	scg_dst = get_scg (widget);

	if (scg_src == scg_dst) {
758 759 760 761 762 763 764
		gtk_widget_hide (arrow);
		return (FALSE);
	}

	/* Move the arrow to the correct position and show it. */
	window = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
	gtk_window_get_position (GTK_WINDOW (window), &root_x, &root_y);
765 766 767 768 769 770
	gtk_widget_get_allocation (widget ,&wa);
	pos_x = root_x + wa.x;
	pos_y = root_y + wa.y;
	gtk_widget_get_allocation (w_source ,&wsa);
	if (wsa.x < wa.x)
		pos_x += wa.width;
771 772 773 774 775 776 777
	gtk_window_move (GTK_WINDOW (arrow), pos_x, pos_y);
	gtk_widget_show (arrow);

	return (TRUE);
}

static void
778
set_dir (GtkWidget *w, GtkTextDirection *dir)
779
{
780 781 782 783 784
	gtk_widget_set_direction (w, *dir);
	if (GTK_IS_CONTAINER (w))
		gtk_container_foreach (GTK_CONTAINER (w),
				       (GtkCallback)&set_dir,
				       dir);
785 786
}

787 788 789 790 791 792 793 794 795 796 797
static void
wbcg_set_direction (SheetControlGUI const *scg)
{
	GtkWidget *w = (GtkWidget *)scg->wbcg->snotebook;
	gboolean text_is_rtl = scg_sheet (scg)->text_is_rtl;
	GtkTextDirection dir = text_is_rtl
		? GTK_TEXT_DIR_RTL
		: GTK_TEXT_DIR_LTR;

	if (dir != gtk_widget_get_direction (w))
		set_dir (w, &dir);
798 799
	if (scg->hs)
		g_object_set (scg->hs, "inverted", text_is_rtl, NULL);
800 801
}

802
static void
803 804 805
cb_direction_change (G_GNUC_UNUSED Sheet *null_sheet,
		     G_GNUC_UNUSED GParamSpec *null_pspec,
		     SheetControlGUI const *scg)
806
{
807 808
	if (scg && scg == wbcg_cur_scg (scg->wbcg))
		wbcg_set_direction (scg);
809 810 811 812 813 814 815 816 817 818
}

static void
wbcg_update_menu_feedback (WBCGtk *wbcg, Sheet const *sheet)
{
	g_return_if_fail (IS_SHEET (sheet));

	if (!wbcg_ui_update_begin (wbcg))
		return;

819
	wbc_gtk_set_toggle_action_state (wbcg,
820
		"SheetDisplayFormulas", sheet->display_formulas);
821
	wbc_gtk_set_toggle_action_state (wbcg,
822
		"SheetHideZeros", sheet->hide_zero);
823
	wbc_gtk_set_toggle_action_state (wbcg,
824
		"SheetHideGridlines", sheet->hide_grid);
825
	wbc_gtk_set_toggle_action_state (wbcg,
826
		"SheetHideColHeader", sheet->hide_col_header);
827
	wbc_gtk_set_toggle_action_state (wbcg,
828
		"SheetHideRowHeader", sheet->hide_row_header);
829
	wbc_gtk_set_toggle_action_state (wbcg,
830
		"SheetDisplayOutlines", sheet->display_outlines);
831
	wbc_gtk_set_toggle_action_state (wbcg,
832
		"SheetOutlineBelow", sheet->outline_symbols_below);
833
	wbc_gtk_set_toggle_action_state (wbcg,
834
		"SheetOutlineRight", sheet->outline_symbols_right);
835
	wbc_gtk_set_toggle_action_state (wbcg,
836 837 838 839 840 841 842 843 844 845 846
		"SheetUseR1C1", sheet->convs->r1c1_addresses);
	wbcg_ui_update_end (wbcg);
}

static void
cb_zoom_change (Sheet *sheet,
		G_GNUC_UNUSED GParamSpec *null_pspec,
		WBCGtk *wbcg)
{
	if (wbcg_ui_update_begin (wbcg)) {
		int pct = sheet->last_zoom_factor_used * 100 + .5;
847
		char *label = g_strdup_printf ("%d%%", pct);
848
		go_action_combo_text_set_entry (wbcg->zoom_haction, label,
849 850
			GO_ACTION_COMBO_SEARCH_CURRENT);
		g_free (label);
851 852 853 854 855
		wbcg_ui_update_end (wbcg);
	}
}

static void
856
cb_notebook_switch_page (G_GNUC_UNUSED GtkNotebook *notebook_,
857
			 G_GNUC_UNUSED GtkWidget *page_,
858
			 guint page_num, WBCGtk *wbcg)
859
{
860 861 862 863 864 865
	Sheet *sheet;
	SheetControlGUI *new_scg;

	g_return_if_fail (IS_WBC_GTK (wbcg));

	/* Ignore events during destruction */
866
	if (wbcg->snotebook == NULL)
867 868
		return;

869 870
	if (debug_tab_order)
		g_printerr ("Notebook page switch\n");
871

872 873 874 875 876 877 878 879 880 881
	/* While initializing adding the sheets will trigger page changes, but
	 * we do not actually want to change the focus sheet for the view
	 */
	if (wbcg->updating_ui)
		return;

	/* If we are not at a subexpression boundary then finish editing */
	if (NULL != wbcg->rangesel)
		scg_rangesel_stop (wbcg->rangesel, TRUE);

882 883 884 885 886 887
	/*
	 * Make snotebook follow bnotebook.  This should be the only place
	 * that changes pages for snotebook.
	 */
	gtk_notebook_set_current_page (wbcg->snotebook, page_num);

888
	new_scg = wbcg_get_nth_scg (wbcg, page_num);
889
	wbcg_set_direction (new_scg);
890

891 892 893 894 895 896 897 898
	if (wbcg_is_editing (wbcg) && wbcg_rangesel_possible (wbcg)) {
		/*
		 * When we are editing, sheet changes are not done fully.
		 * We revert to the original sheet later.
		 *
		 * On the other hand, when we are selecting a range for a
		 * dialog, we do change sheet fully.
		 */
899 900 901 902 903 904 905
		scg_take_focus (new_scg);
		return;
	}

	gnm_expr_entry_set_scg (wbcg->edit_line.entry, new_scg);

	/*
906 907 908
	 * Make absolutely sure the expression doesn't get 'lost',
	 * if it's invalid then prompt the user and don't switch
	 * the notebook page.
909 910
	 */
	if (wbcg_is_editing (wbcg)) {
911 912
		guint prev = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (wbcg->snotebook),
								 "previous_page"));
913 914 915 916 917

		if (prev == page_num)
			return;

		if (!wbcg_edit_finish (wbcg, WBC_EDIT_ACCEPT, NULL))
918 919
			gnm_notebook_set_current_page (wbcg->bnotebook,
						       prev);
920 921
		else
			/* Looks silly, but is really neccesarry */
922 923 924
			gnm_notebook_set_current_page (wbcg->bnotebook,
						       page_num);

925 926 927
		return;
	}

928 929
	g_object_set_data (G_OBJECT (wbcg->snotebook), "previous_page",
			   GINT_TO_POINTER (gtk_notebook_get_current_page (wbcg->snotebook)));
930 931 932

	/* if we are not selecting a range for an expression update */
	sheet = wbcg_focus_cur_scg (wbcg);
933
	if (sheet != wbcg_cur_sheet (wbcg)) {
934 935 936 937 938 939 940 941
		wbcg_update_menu_feedback (wbcg, sheet);
		sheet_flag_status_update_range (sheet, NULL);
		sheet_update (sheet);
		wb_view_sheet_focus (wb_control_view (WORKBOOK_CONTROL (wbcg)), sheet);
		cb_zoom_change (sheet, NULL, wbcg);
	}
}

942 943 944 945 946 947 948 949 950 951 952 953 954
static gboolean
cb_bnotebook_button_press (GtkWidget *widget, GdkEventButton *event)
{
	if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
		/*
		 * Eat the click so cb_paned_button_press doesn't see it.
		 * see bug #607794.
		 */
		return TRUE;
	}

	return FALSE;
}
955

956 957 958 959 960 961 962
static void
cb_bnotebook_page_reordered (GtkNotebook *notebook, GtkWidget *child,
			     int page_num, WBCGtk *wbcg)
{
	GtkNotebook *snotebook = GTK_NOTEBOOK (wbcg->snotebook);
	int old = gtk_notebook_get_current_page (snotebook);

963 964 965 966 967
	if (wbcg->updating_ui)
		return;

	if (debug_tab_order)
		g_printerr ("Reordered %d -> %d\n", old, page_num);
968 969

	if (old != page_num) {
970 971
		WorkbookControl * wbc = WORKBOOK_CONTROL (wbcg);
		Workbook *wb = wb_control_get_workbook (wbc);
972
		Sheet *sheet = workbook_sheet_by_index (wb, old);
973
		WorkbookSheetState * old_state = workbook_sheet_state_new(wb);
974
		workbook_sheet_move (sheet, page_num - old);
975
		cmd_reorganize_sheets (wbc, old_state, sheet);
976 977 978 979
	}
}


980 981 982
static void
wbc_gtk_create_notebook_area (WBCGtk *wbcg)
{
983
	GtkWidget *placeholder;
984

985 986 987
	wbcg->bnotebook = g_object_new (GNM_NOTEBOOK_TYPE,
					"can-focus", FALSE,
					NULL);
988 989
	g_object_ref (wbcg->bnotebook);

990
	g_signal_connect_after (G_OBJECT (wbcg->bnotebook),
991 992
		"switch_page",
		G_CALLBACK (cb_notebook_switch_page), wbcg);
993
	g_signal_connect (G_OBJECT (wbcg->bnotebook),
994 995
			  "button-press-event",
			  G_CALLBACK (cb_bnotebook_button_press),
996
			  NULL);
997 998 999 1000
	g_signal_connect (G_OBJECT (wbcg->bnotebook),
			  "page-reordered",
			  G_CALLBACK (cb_bnotebook_page_reordered),
			  wbcg);
1001 1002 1003
	placeholder = gtk_paned_get_child1 (wbcg->tabs_paned);
	if (placeholder)
		gtk_widget_destroy (placeholder);
1004
	gtk_paned_pack1 (wbcg->tabs_paned, GTK_WIDGET (wbcg->bnotebook), FALSE, TRUE);
1005

1006
	gtk_widget_show_all (GTK_WIDGET (wbcg->tabs_paned));
1007 1008 1009 1010 1011 1012
}


static void
wbcg_menu_state_sheet_count (WBCGtk *wbcg)
{
1013
	int const sheet_count = gnm_notebook_get_n_visible (wbcg->bnotebook);
1014 1015 1016 1017 1018 1019
	/* Should we enable commands requiring multiple sheets */
	gboolean const multi_sheet = (sheet_count > 1);

	wbc_gtk_set_action_sensitivity (wbcg, "SheetRemove", multi_sheet);
}

1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
static void
cb_sheet_direction_change (Sheet *sheet,
			   G_GNUC_UNUSED GParamSpec *pspec,
			   GtkAction *a)
{
	g_object_set (a,
		      "icon-name", (sheet->text_is_rtl
				    ? "format-text-direction-rtl"
				    : "format-text-direction-ltr"),
		      NULL);
}

1032 1033 1034
static void
cb_sheet_tab_change (Sheet *sheet,
		     G_GNUC_UNUSED GParamSpec *pspec,
1035
		     GtkWidget *widget)
1036
{
1037
	GdkRGBA cfore, cback;
1038
	SheetControlGUI *scg = get_scg (widget);
1039

1040 1041
	g_return_if_fail (IS_SHEET_CONTROL_GUI (scg));

1042
	/* We're lazy and just set all relevant attributes.  */
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
	g_object_set (widget,
		      "label", sheet->name_unquoted,
		      "background-color",
		      (sheet->tab_color
		       ? go_color_to_gdk_rgba (sheet->tab_color->go_color,
					       &cback)
		       : NULL),
		      "text-color",
		      (sheet->tab_text_color
		       ? go_color_to_gdk_rgba (sheet->tab_text_color->go_color,
					       &cfore)
		       : NULL),
		      NULL);
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
}

static void
cb_toggle_menu_item_changed (Sheet *sheet,
			     G_GNUC_UNUSED GParamSpec *pspec,
			     WBCGtk *wbcg)
{
	/* We're lazy and just update all.  */
	wbcg_update_menu_feedback (wbcg, sheet);
}

static void
cb_sheet_visibility_change (Sheet *sheet,
			    G_GNUC_UNUSED GParamSpec *pspec,
1070
			    SheetControlGUI *scg)
1071
{
1072 1073
	gboolean viz;

1074
	g_return_if_fail (IS_SHEET_CONTROL_GUI (scg));
1075

1076
	viz = sheet_is_visible (sheet);
1077
	gtk_widget_set_visible (GTK_WIDGET (scg->grid), viz);
1078 1079
	gtk_widget_set_visible (GTK_WIDGET (scg->label), viz);

1080
	wbcg_menu_state_sheet_count (scg->wbcg);
1081 1082 1083
}

static void
1084
disconnect_sheet_focus_signals (WBCGtk *wbcg)
1085
{
1086 1087
	SheetControlGUI *scg = wbcg->active_scg;
	Sheet *sheet;
1088

1089 1090
	if (!scg)
		return;
1091

1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
	sheet = scg_sheet (scg);

#if 0
	g_printerr ("Disconnecting focus for %s with scg=%p\n", sheet->name_unquoted, scg);
#endif

	g_signal_handlers_disconnect_by_func (sheet, cb_toggle_menu_item_changed, wbcg);
	g_signal_handlers_disconnect_by_func (sheet, cb_direction_change, scg);
	g_signal_handlers_disconnect_by_func (sheet, cb_zoom_change, wbcg);

	wbcg->active_scg = NULL;
}

static void
disconnect_sheet_signals (SheetControlGUI *scg)
{
	WBCGtk *wbcg = scg->wbcg;
	Sheet *sheet = scg_sheet (scg);

	if (scg == wbcg->active_scg)
		disconnect_sheet_focus_signals (wbcg);

#if 0
	g_printerr ("Disconnecting all for %s with scg=%p\n", sheet->name_unquoted, scg);
#endif

1118
	g_signal_handlers_disconnect_by_func (sheet, cb_sheet_direction_change, gtk_action_group_get_action (wbcg->actions, "SheetDirection"));
1119 1120
	g_signal_handlers_disconnect_by_func (sheet, cb_sheet_tab_change, scg->label);
	g_signal_handlers_disconnect_by_func (sheet, cb_sheet_visibility_change, scg);
1121 1122 1123 1124 1125 1126
}

static void
wbcg_sheet_add (WorkbookControl *wbc, SheetView *sv)
{
	static GtkTargetEntry const drag_types[] = {
1127 1128 1129 1130 1131 1132 1133 1134
		{ (char *)"GNUMERIC_SHEET", GTK_TARGET_SAME_APP, TARGET_SHEET },
		{ (char *)"UTF8_STRING", 0, 0 },
		{ (char *)"image/svg+xml", 0, 0 },
		{ (char *)"image/x-wmf", 0, 0 },
		{ (char *)"image/x-emf", 0, 0 },
		{ (char *)"image/png", 0, 0 },
		{ (char *)"image/jpeg", 0, 0 },
		{ (char *)"image/bmp", 0, 0 }
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
	};

	WBCGtk *wbcg = (WBCGtk *)wbc;
	SheetControlGUI *scg;
	Sheet		*sheet   = sv_sheet (sv);
	gboolean	 visible = sheet_is_visible (sheet);

	g_return_if_fail (wbcg != NULL);

	scg = sheet_control_gui_new (sv, wbcg);
1145

1146
	g_object_set_data (G_OBJECT (scg->grid), SHEET_CONTROL_KEY, scg);
1147

1148
	g_object_set_data (G_OBJECT (scg->label), SHEET_CONTROL_KEY, scg);
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165

	/* do not preempt the editable label handler */
	g_signal_connect_after (G_OBJECT (scg->label),
		"button_press_event",
		G_CALLBACK (cb_sheet_label_button_press), scg);

	/* Drag & Drop */
	gtk_drag_source_set (scg->label, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
			drag_types, G_N_ELEMENTS (drag_types),
			GDK_ACTION_MOVE);
	gtk_drag_dest_set (scg->label, GTK_DEST_DEFAULT_ALL,
			drag_types, G_N_ELEMENTS (drag_types),
			GDK_ACTION_MOVE);
	g_object_connect (G_OBJECT (scg->label),
		"signal::drag_begin", G_CALLBACK (cb_sheet_label_drag_begin), wbcg,
		"signal::drag_end", G_CALLBACK (cb_sheet_label_drag_end), wbcg,
		"signal::drag_leave", G_CALLBACK (cb_sheet_label_drag_leave), wbcg,
1166
		"signal::drag_data_get", G_CALLBACK (cb_sheet_label_drag_data_get), NULL,
1167 1168 1169 1170 1171
		"signal::drag_data_received", G_CALLBACK (cb_sheet_label_drag_data_received), wbcg,
		"signal::drag_motion", G_CALLBACK (cb_sheet_label_drag_motion), wbcg,
		NULL);

	gtk_widget_show (scg->label);
1172
	gtk_widget_show_all (GTK_WIDGET (scg->grid));
1173
	if (!visible) {
1174
		gtk_widget_hide (GTK_WIDGET (scg->grid));
1175 1176
		gtk_widget_hide (GTK_WIDGET (scg->label));
	}
1177
	g_object_connect (G_OBJECT (sheet),
1178
			  "signal::notify::visibility", cb_sheet_visibility_change, scg,
1179 1180 1181
			  "signal::notify::name", cb_sheet_tab_change, scg->label,
			  "signal::notify::tab-foreground", cb_sheet_tab_change, scg->label,
			  "signal::notify::tab-background", cb_sheet_tab_change, scg->label,
1182
			  "signal::notify::text-is-rtl", cb_sheet_direction_change, gtk_action_group_get_action (wbcg->actions, "SheetDirection"),
1183 1184 1185
			  NULL);

	if (wbcg_ui_update_begin (wbcg)) {
1186 1187 1188 1189 1190 1191
		/*
		 * Just let wbcg_sheet_order_changed deal with where to put
		 * it.
		 */
		int pos = -1;
		gtk_notebook_insert_page (wbcg->snotebook,
1192
					  GTK_WIDGET (scg->grid), NULL,
1193 1194 1195 1196
					  pos);
		gnm_notebook_insert_tab (wbcg->bnotebook,
					 GTK_WIDGET (scg->label),
					 pos);
1197 1198 1199 1200 1201 1202 1203
		wbcg_menu_state_sheet_count (wbcg);
		wbcg_ui_update_end (wbcg);
	}

	scg_adjust_preferences (scg);
	if (sheet == wb_control_cur_sheet (wbc)) {
		scg_take_focus (scg);
1204
		wbcg_set_direction (scg);
1205 1206 1207 1208 1209 1210 1211 1212 1213
		cb_zoom_change (sheet, NULL, wbcg);
		cb_toggle_menu_item_changed (sheet, NULL, wbcg);
	}
}

static void
wbcg_sheet_remove (WorkbookControl *wbc, Sheet *sheet)
{
	WBCGtk *wbcg = (WBCGtk *)wbc;
1214
	SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);
1215 1216

	/* During destruction we may have already removed the notebook */
1217
	if (scg == NULL)
1218 1219
		return;

1220 1221 1222
	disconnect_sheet_signals (scg);

	gtk_widget_destroy (GTK_WIDGET (scg->label));
1223
	gtk_widget_destroy (GTK_WIDGET (scg->grid));
1224 1225 1226 1227 1228 1229 1230 1231 1232 1233

	wbcg_menu_state_sheet_count (wbcg);
}

static void
wbcg_sheet_focus (WorkbookControl *wbc, Sheet *sheet)
{
	WBCGtk *wbcg = (WBCGtk *)wbc;
	SheetControlGUI *scg = wbcg_get_scg (wbcg, sheet);

1234 1235
	if (scg) {
		int n = gtk_notebook_page_num (wbcg->snotebook,
1236
					       GTK_WIDGET (scg->grid));
1237 1238
		gnm_notebook_set_current_page (wbcg->bnotebook, n);

1239 1240 1241
		if (wbcg->rangesel == NULL)
			gnm_expr_entry_set_scg (wbcg->edit_line.entry, scg);
	}
1242

1243
	disconnect_sheet_focus_signals (wbcg);
1244 1245 1246

	if (sheet) {
		wbcg_update_menu_feedback (wbcg, sheet);
Morten Welinder's avatar
Morten Welinder committed
1247 1248

		if (scg)
1249
			wbcg_set_direction (scg);
1250 1251 1252 1253

#if 0
		g_printerr ("Connecting for %s with scg=%p\n", sheet->name_unquoted, scg);
#endif
1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267

		g_object_connect
			(G_OBJECT (sheet),
			 "signal::notify::display-formulas", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-zeros", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-grid", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-column-header", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-row-header", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-outlines", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-outlines-below", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::display-outlines-right", cb_toggle_menu_item_changed, wbcg,
			 "signal::notify::text-is-rtl", cb_direction_change, scg,
			 "signal::notify::zoom-factor", cb_zoom_change, wbcg,
			 NULL);
1268 1269

		wbcg->active_scg = scg;
1270 1271 1272
	}
}

1273 1274 1275 1276 1277 1278 1279 1280
static gint
by_sheet_index (gconstpointer a, gconstpointer b)
{
	SheetControlGUI *scga = (SheetControlGUI *)a;
	SheetControlGUI *scgb = (SheetControlGUI *)b;
	return scg_sheet (scga)->index_in_wb - scg_sheet (scgb)->index_in_wb;
}

1281 1282 1283
static void
wbcg_sheet_order_changed (WBCGtk *wbcg)
{
1284 1285 1286
	if (wbcg_ui_update_begin (wbcg)) {
		GSList *l, *scgs;
		int i;
1287

1288 1289
		/* Reorder all tabs so they end up in index_in_wb order. */
		scgs = g_slist_sort (get_all_scgs (wbcg), by_sheet_index);
1290

1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303
		for (i = 0, l = scgs; l; l = l->next, i++) {
			SheetControlGUI *scg = l->data;
			gtk_notebook_reorder_child (wbcg->snotebook,
						    GTK_WIDGET (scg->grid),
						    i);
			gnm_notebook_move_tab (wbcg->bnotebook,
					       GTK_WIDGET (scg->label),
					       i);
		}
		g_slist_free (scgs);

		wbcg_ui_update_end (wbcg);
	}
1304 1305 1306 1307 1308 1309
}

static void
wbcg_update_title (WBCGtk *wbcg)
{
	GODoc *doc = wb_control_get_doc (WORKBOOK_CONTROL (wbcg));
1310
	char *basename = doc->uri ? go_basename_from_uri (doc->uri) : NULL;
1311 1312 1313
	char *title = g_strconcat
		(go_doc_is_dirty (doc) ? "*" : "",
		 basename ? basename : doc->uri,
1314
		 _(" - Gnumeric"),
1315
		 NULL);
1316
	gtk_window_set_title (wbcg_toplevel (wbcg), title);
1317 1318 1319 1320 1321 1322 1323 1324 1325
	g_free (title);
	g_free (basename);
}

static void
wbcg_sheet_remove_all (WorkbookControl *wbc)
{
	WBCGtk *wbcg = (WBCGtk *)wbc;

1326
	if (wbcg->snotebook != NULL) {
1327
		GtkNotebook *tmp = wbcg->snotebook;
1328
		GSList *l, *all = get_all_scgs (wbcg);
1329
		SheetControlGUI *current = wbcg_cur_scg (wbcg);
1330 1331 1332

		/* Clear notebook to disable updates as focus changes for pages
		 * during destruction */
1333
		wbcg->snotebook = NULL;
1334 1335 1336 1337

		/* Be sure we are no longer editing */
		wbcg_edit_finish (wbcg, WBC_EDIT_REJECT, NULL);

1338 1339 1340
		for (l = all; l; l = l->next) {
			SheetControlGUI *scg = l->data;
			disconnect_sheet_signals (scg);
1341 1342
			if (scg != current) {
				gtk_widget_destroy (GTK_WIDGET (scg->label));
1343
				gtk_widget_destroy (GTK_WIDGET (scg->grid));
1344
			}
1345 1346
		}

1347
		g_slist_free (all);
1348

1349 1350 1351
		/* Do current scg last.  */
		if (current) {
			gtk_widget_destroy (GTK_WIDGET (current->label));
1352
			gtk_widget_destroy (GTK_WIDGET (current->grid));
1353 1354
		}

1355
		wbcg->snotebook = tmp;
1356 1357 1358
	}
}

1359 1360 1361 1362 1363 1364 1365 1366 1367 1368
static double
color_diff (const GdkRGBA *a, const GdkRGBA *b)
{
	/* Ignoring alpha.  */
	return ((a->red - b->red) * (a->red - b->red) +
		(a->green - b->green) * (a->green - b->green) +
		(a->blue - b->blue) * (a->blue - b->blue));
}


1369
static gboolean
1370 1371
cb_adjust_foreground_attributes (PangoAttribute *attribute,
				 gpointer data)
1372
{
1373 1374
	const GdkRGBA *back = data;

1375
	if (attribute->klass->type == PANGO_ATTR_FOREGROUND) {
1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
		PangoColor *pfore = &((PangoAttrColor *)attribute)->color;
		GdkRGBA fore;
		const double threshold = 0.01;

		fore.red = pfore->red / 65535.0;
		fore.green = pfore->green / 65535.0;
		fore.blue = pfore->blue / 65535.0;

		if (color_diff (&fore, back) < threshold) {
			static const GdkRGBA black = { 0, 0, 0, 1 };
			static const GdkRGBA white = { 1, 1, 1, 1 };
			double back_norm = color_diff (back, &black);
			double f = 0.2;
			const GdkRGBA *ref =
				back_norm > 0.75 ? &black : &white;

#define DO_CHANNEL(channel)						\
do {									\
	double val = fore.channel * (1 - f) + ref->channel * f;		\
	pfore->channel = CLAMP (val, 0, 1) * 65535;			\
1396
} while (0)
1397 1398 1399 1400
			DO_CHANNEL(red);
			DO_CHANNEL(green);
			DO_CHANNEL(blue);
#undef DO_CHANNEL
1401 1402 1403 1404 1405
		}
	}
	return FALSE;
}

1406
static void
1407
adjust_foreground_attributes (PangoAttrList *attrs, GtkWidget *w)
1408
{
1409 1410 1411 1412 1413 1414 1415 1416 1417
	GdkRGBA c;
	GtkStyleContext *ctxt = gtk_widget_get_style_context (w);

	gtk_style_context_get_background_color (ctxt, GTK_STATE_FLAG_NORMAL,
						&c);

	if (0)
		g_printerr ("back=%s\n", gdk_rgba_to_string (&c));

1418 1419
	pango_attr_list_unref
		(pango_attr_list_filter
1420
		 (attrs,
1421 1422
		  cb_adjust_foreground_attributes,
		  &c));
1423 1424 1425 1426 1427 1428 1429
}


static void
wbcg_auto_expr_value_changed (WorkbookView *wbv,
			      G_GNUC_UNUSED GParamSpec *pspec,
			      WBCGtk *wbcg)
1430
{
1431
	GtkLabel *lbl = GTK_LABEL (wbcg->auto_expr_label);
1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446
	GnmValue const *v = wbv->auto_expr.value;

	if (v) {
		GOFormat const *format = VALUE_FMT (v);
		GString *str = g_string_new (wbv->auto_expr.descr);
		PangoAttrList *attrs = NULL;

		g_string_append (str, " = ");

		if (format) {
			PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (wbcg->toplevel), NULL);
			gsize old_len = str->len;
			GODateConventions const *date_conv = workbook_date_conv (wb_view_get_workbook (wbv));
			GOFormatNumberError err =
				format_value_layout (layout, format, v,
1447
						     strlen (AUTO_EXPR_SAMPLE) - g_utf8_strlen (str->str, -1),
1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463
						     date_conv);
			switch (err) {
			case GO_FORMAT_NUMBER_OK:
			case GO_FORMAT_NUMBER_DATE_ERROR: {
				PangoAttrList *atl;

				go_pango_translate_layout (layout); /* translating custom attributes */
				g_string_append (str, pango_layout_get_text (layout));
				/* We need to shift the attribute list  */
				atl = pango_attr_list_ref (pango_layout_get_attributes (layout));
				if (atl != NULL) {
					attrs = pango_attr_list_new ();
					pango_attr_list_splice
						(attrs, atl, old_len,
						 str->len - old_len);
					pango_attr_list_unref (atl);
1464 1465 1466 1467
					/* Adjust colours to make text visible. */
					adjust_foreground_attributes
						(attrs,
						 gtk_widget_get_parent (GTK_WIDGET (lbl)));
1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481
				}
				break;
			}
			default:
			case GO_FORMAT_NUMBER_INVALID_FORMAT:
				g_string_append (str,  _("Invalid format"));
				break;
			}
			g_object_unref (layout);
		} else
			g_string_append (str, value_peek_string (v));

		gtk_label_set_text (lbl, str->str);
		gtk_label_set_attributes (lbl, attrs);
1482

1483 1484 1485 1486 1487 1488
		pango_attr_list_unref (attrs);
		g_string_free (str, TRUE);
	} else {
		gtk_label_set_text (lbl, "");
		gtk_label_set_attributes (lbl, NULL);
	}
1489 1490
}

1491 1492 1493 1494 1495 1496 1497 1498 1499
static void
wbcg_scrollbar_visibility (WorkbookView *wbv,
			   G_GNUC_UNUSED GParamSpec *pspec,
			   WBCGtk *wbcg)
{
	SheetControlGUI *scg = wbcg_cur_scg (wbcg);
	scg_adjust_preferences (scg);
}

1500 1501 1502 1503 1504
static void
wbcg_notebook_tabs_visibility (WorkbookView *wbv,
			       G_GNUC_UNUSED GParamSpec *pspec,
			       WBCGtk *wbcg)
{
1505 1506
	gtk_widget_set_visible (