1.12.42
[gnumeric.git] / src / dialogs / dialog-sheet-compare.c
blobbafbb2ba6ba8146b752b6b6d225ea39bacebe600
1 /*
2 * dialog-comparet-order.c: Dialog to compare two sheets.
4 * (C) Copyright 2018 Morten Welinder (terra@gnome.org)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <https://www.gnu.org/licenses/>.
20 #include <gnumeric-config.h>
21 #include <glib/gi18n-lib.h>
22 #include <gnumeric.h>
23 #include <sheet-diff.h>
24 #include <dialogs/dialogs.h>
25 #include <dialogs/help.h>
27 #include <gui-util.h>
28 #include <wbc-gtk.h>
29 #include <workbook-view.h>
30 #include <workbook.h>
31 #include <workbook-priv.h>
32 #include <sheet.h>
33 #include <ranges.h>
34 #include <cell.h>
35 #include <sheet-style.h>
36 #include <application.h>
37 #include <selection.h>
38 #include <sheet-view.h>
39 #include <widgets/gnm-sheet-sel.h>
40 #include <widgets/gnm-workbook-sel.h>
42 #define SHEET_COMPARE_KEY "sheet-compare-dialog"
44 enum {
45 ITEM_SECTION,
46 ITEM_DIRECTION,
47 ITEM_OLD_LOC,
48 ITEM_NEW_LOC,
49 ITEM_NO,
50 ITEM_QCOLS,
51 NUM_COLUMNS
54 enum {
55 SEC_CELLS,
56 SEC_STYLE,
57 SEC_COLROW
60 enum {
61 DIR_NA,
62 DIR_ADDED,
63 DIR_REMOVED,
64 DIR_CHANGED,
65 DIR_QUIET // Like CHANGED, but for always-changed context
69 typedef struct {
70 WBCGtk *wbcg;
72 GtkBuilder *gui;
73 GtkWidget *dialog;
74 GtkWidget *notebook;
76 GtkWidget *cancel_btn;
77 GtkWidget *compare_btn;
79 GtkWidget *sheet_sel_A;
80 GtkWidget *sheet_sel_B;
81 GtkWidget *wb_sel_A;
82 GtkWidget *wb_sel_B;
83 GtkWidget *results_window;
85 GtkTreeView *results_view;
86 GtkTreeStore *results;
88 gboolean has_cell_section;
89 GtkTreeIter cell_section_iter;
91 gboolean has_style_section;
92 GtkTreeIter style_section_iter;
94 gboolean has_colrow_section;
95 GtkTreeIter colrow_section_iter;
97 Sheet *old_sheet;
98 Sheet *new_sheet;
99 } SheetCompare;
102 static void
103 cb_sheet_compare_destroy (SheetCompare *state)
105 Workbook *wb = wb_control_get_workbook (GNM_WBC (state->wbcg));
107 g_object_unref (state->gui);
108 g_object_set_data (G_OBJECT (wb), SHEET_COMPARE_KEY, NULL);
109 state->gui = NULL;
111 g_free (state);
114 static void
115 cb_cancel_clicked (G_GNUC_UNUSED GtkWidget *ignore,
116 SheetCompare *state)
118 gtk_widget_destroy (GTK_WIDGET (state->dialog));
121 static GtkWidget *
122 create_wb_selector (SheetCompare *state, GtkWidget *sheet_sel)
124 GtkWidget *res = gnm_workbook_sel_new ();
125 gnm_sheet_sel_link (GNM_SHEET_SEL (sheet_sel),
126 GNM_WORKBOOK_SEL (res));
127 return res;
130 static void
131 select_default_sheets (SheetCompare *state)
133 Workbook *wb = wb_control_get_workbook (GNM_WBC (state->wbcg));
134 GList *wb_list = gnm_app_workbook_list ();
136 if (g_list_length (wb_list) > 1) {
137 // Multiple workbooks
139 gnm_workbook_sel_set_workbook
140 (GNM_WORKBOOK_SEL (state->wb_sel_A), wb);
141 gnm_workbook_sel_set_workbook
142 (GNM_WORKBOOK_SEL (state->wb_sel_B),
143 wb == wb_list->data ? wb_list->next->data : wb_list->data);
144 } else if (workbook_sheet_count (wb) > 1) {
145 // One workbook, multiple sheets
146 gnm_sheet_sel_set_sheet (GNM_SHEET_SEL (state->sheet_sel_B),
147 workbook_sheet_by_index (wb, 1));
148 } else {
149 // One workbook, one sheet
153 /* ------------------------------------------------------------------------- */
155 static void
156 setup_section (SheetCompare *state, gboolean *phas, GtkTreeIter *iter,
157 int section)
159 if (!*phas) {
160 gtk_tree_store_insert (state->results, iter, NULL, -1);
161 gtk_tree_store_set (state->results, iter,
162 ITEM_SECTION, section,
163 ITEM_DIRECTION, DIR_NA,
164 -1);
165 *phas = TRUE;
169 static void
170 extract_range (GnmRangeRef const *rr, GnmRange *r, Sheet **psheet)
172 *psheet = rr->a.sheet;
173 r->start.col = rr->a.col;
174 r->start.row = rr->a.row;
175 r->end.col = rr->b.col;
176 r->end.row = rr->b.row;
179 static void
180 loc_from_range (GnmRangeRef *loc, Sheet *sheet, GnmRange const *r)
182 gnm_cellref_init (&loc->a, sheet,
183 r->start.col, r->start.row,
184 FALSE);
185 gnm_cellref_init (&loc->b, sheet,
186 r->end.col, r->end.row,
187 FALSE);
190 static const char *
191 get_mstyle_name (int e)
193 switch (e) {
194 case MSTYLE_COLOR_BACK: return _("Background color");
195 case MSTYLE_COLOR_PATTERN: return _("Pattern color");
197 case MSTYLE_BORDER_TOP: return _("Top border");
198 case MSTYLE_BORDER_BOTTOM: return _("Bottom border");
199 case MSTYLE_BORDER_LEFT: return _("Left border");
200 case MSTYLE_BORDER_RIGHT: return _("Right border");
201 case MSTYLE_BORDER_REV_DIAGONAL: return _("Reverse diagonal border");
202 case MSTYLE_BORDER_DIAGONAL: return _("Diagonal border");
203 case MSTYLE_PATTERN: return _("Pattern");
205 case MSTYLE_FONT_COLOR: return _("Font color");
206 case MSTYLE_FONT_NAME: return _("Font");
207 case MSTYLE_FONT_BOLD: return _("Bold");
208 case MSTYLE_FONT_ITALIC: return _("Italic");
209 case MSTYLE_FONT_UNDERLINE: return _("Underline");
210 case MSTYLE_FONT_STRIKETHROUGH: return _("Strikethrough");
211 case MSTYLE_FONT_SCRIPT: return _("Script");
212 case MSTYLE_FONT_SIZE: return _("Size");
214 case MSTYLE_FORMAT: return _("Format");
216 case MSTYLE_ALIGN_V: return _("Vertical alignment");
217 case MSTYLE_ALIGN_H: return _("Horizontal alignment");
218 case MSTYLE_INDENT: return _("Indentation");
219 case MSTYLE_ROTATION: return _("Rotation");
220 case MSTYLE_TEXT_DIR: return _("Direction");
221 case MSTYLE_WRAP_TEXT: return _("Wrap");
222 case MSTYLE_SHRINK_TO_FIT: return _("Shrink-to-fit");
224 case MSTYLE_CONTENTS_LOCKED: return _("Locked");
225 case MSTYLE_CONTENTS_HIDDEN: return _("Hidden");
227 case MSTYLE_VALIDATION: return _("Validation");
228 case MSTYLE_HLINK: return _("Hyperlink");
229 case MSTYLE_INPUT_MSG: return _("Input message");
230 case MSTYLE_CONDITIONS: return _("Conditional format");
231 default: return "?";
236 static void
237 section_renderer_func (GtkTreeViewColumn *tree_column,
238 GtkCellRenderer *cell,
239 GtkTreeModel *model,
240 GtkTreeIter *iter,
241 gpointer user_data)
243 int section, dir;
244 const char *text = "?";
245 int e;
247 gtk_tree_model_get (model, iter,
248 ITEM_SECTION, &section,
249 ITEM_DIRECTION, &dir,
250 ITEM_NO, &e,
251 -1);
252 switch (dir) {
253 case DIR_NA:
254 switch (section) {
255 case SEC_CELLS: text = _("Cells"); break;
256 case SEC_STYLE: text = _("Formatting"); break;
257 case SEC_COLROW: text = _("Columns/Rows"); break;
259 break;
260 case DIR_QUIET:
261 switch (section) {
262 case SEC_STYLE:
263 text = (e == -1) ? _("Various") : get_mstyle_name (e);
264 break;
265 case SEC_COLROW:
266 text = _("Size");
267 break;
268 default:
269 text = "";
271 break;
272 case DIR_ADDED: text = _("Added"); break;
273 case DIR_REMOVED: text = _("Removed"); break;
274 case DIR_CHANGED: text = _("Changed"); break;
277 g_object_set (cell, "text", text, NULL);
280 static void
281 location_renderer_func (GtkTreeViewColumn *tree_column,
282 GtkCellRenderer *cell,
283 GtkTreeModel *model,
284 GtkTreeIter *iter,
285 gpointer user_data)
287 GnmRangeRef *loc_old = NULL;
288 GnmRangeRef *loc_new = NULL;
289 GnmRangeRef *loc;
291 gtk_tree_model_get (model, iter,
292 ITEM_OLD_LOC, &loc_new,
293 ITEM_NEW_LOC, &loc_old,
294 -1);
296 loc = loc_old ? loc_old : loc_new;
297 if (loc) {
298 GnmRange r;
299 Sheet *sheet;
300 char *str = NULL;
301 const char *text;
303 extract_range (loc, &r, &sheet);
305 if (range_is_full (&r, sheet, TRUE) &&
306 r.start.row == r.end.row)
307 text = str = g_strdup_printf
308 (_("Row %s"), row_name (r.start.row));
309 else if (range_is_full (&r, sheet, FALSE) &&
310 r.start.col == r.end.col)
311 text = str = g_strdup_printf
312 (_("Column %s"), col_name (r.start.col));
313 else
314 text = range_as_string (&r);
316 g_object_set (cell, "text", text, NULL);
317 g_free (str);
318 } else
319 g_object_set (cell, "text", "", NULL);
321 g_free (loc_old);
322 g_free (loc_new);
325 static char *
326 do_color (GnmColor const *gcolor)
328 GOColor color = gcolor->go_color;
329 unsigned r, g, b, a;
330 char buf[16];
331 const char *coltxt = NULL;
332 int n;
333 GONamedColor nc;
335 GO_COLOR_TO_RGBA (color, &r, &g, &b, &a);
336 if (a == 0xff)
337 snprintf (buf, sizeof (buf), "#%02X%02X%02X", r, g, b);
338 else
339 snprintf (buf, sizeof (buf), "#%02X%02X%02X%02X", r, g, b, a);
341 for (n = 0; go_color_palette_query (n, &nc); n++) {
342 if (nc.color == color) {
343 coltxt = nc.name;
344 break;
348 return g_strdup_printf
349 ("%s%s (<span bgcolor=\"%s\"> </span>)",
350 gcolor->is_auto ? "Auto " : "",
351 coltxt ? coltxt : buf,
352 buf);
355 static char *
356 do_bool (gboolean b)
358 return g_strdup (b ? _("Yes") : _("No"));
361 static char *
362 do_int (int i)
364 return g_strdup_printf ("%d", i);
367 static char *
368 do_double (double d)
370 return g_strdup_printf ("%g", d);
373 static char *
374 do_halign (GnmHAlign h)
376 switch (h) {
377 case GNM_HALIGN_GENERAL: return g_strdup (_("General"));
378 case GNM_HALIGN_LEFT: return g_strdup (_("Left"));
379 case GNM_HALIGN_RIGHT: return g_strdup (_("Right"));
380 case GNM_HALIGN_CENTER: return g_strdup (_("Center"));
381 case GNM_HALIGN_FILL: return g_strdup (_("Fill"));
382 case GNM_HALIGN_JUSTIFY: return g_strdup (_("Justify"));
383 case GNM_HALIGN_CENTER_ACROSS_SELECTION: return g_strdup (_("Center across selection"));
384 case GNM_HALIGN_DISTRIBUTED: return g_strdup (_("Distributed"));
385 default: return g_strdup ("?");
389 static char *
390 do_valign (GnmVAlign v)
392 switch (v) {
393 case GNM_VALIGN_TOP: return g_strdup (_("Top"));
394 case GNM_VALIGN_BOTTOM: return g_strdup (_("Bottom"));
395 case GNM_VALIGN_CENTER: return g_strdup (_("Center"));
396 case GNM_VALIGN_JUSTIFY: return g_strdup (_("Justify"));
397 case GNM_VALIGN_DISTRIBUTED: return g_strdup (_("Distributed"));
398 default: return g_strdup ("?");
402 static const char *underlines[] = {
403 N_("None"), N_("Single"), N_("Double"),
404 N_("Single low"), N_("Double low"), NULL
407 static const char *textdirs[] = {
408 N_("Right-to-left"), N_("Auto"), N_("Left-to-right"), NULL
411 static char *
412 do_enum (int i, const char *const choices[])
414 if (i < 0 || i >= (int)g_strv_length ((gchar **)choices))
415 return g_strdup ("?");
416 return g_strdup (_(choices[i]));
420 static void
421 oldnew_renderer_func (GtkTreeViewColumn *tree_column,
422 GtkCellRenderer *cell,
423 GtkTreeModel *model,
424 GtkTreeIter *iter,
425 gpointer user_data)
427 gboolean qnew = GPOINTER_TO_UINT (user_data);
428 GnmRangeRef *loc = NULL;
429 int section, dir, e;
430 gboolean is_cols;
431 char *text = NULL;
432 gboolean qmarkup = FALSE;
434 gtk_tree_model_get (model, iter,
435 ITEM_SECTION, &section,
436 ITEM_DIRECTION, &dir,
437 (qnew ? ITEM_NEW_LOC : ITEM_OLD_LOC), &loc,
438 ITEM_NO, &e,
439 ITEM_QCOLS, &is_cols,
440 -1);
441 if (dir == DIR_NA || !loc || !loc->a.sheet)
442 goto done;
444 if (section == SEC_CELLS) {
445 GnmCell const *cell =
446 sheet_cell_get (loc->a.sheet, loc->a.col, loc->a.row);
447 if (!cell)
448 goto error;
449 text = gnm_cell_get_entered_text (cell);
450 } else if (section == SEC_STYLE) {
451 GnmStyle const *style;
452 if (e == -1)
453 goto done;
455 style = sheet_style_get (loc->a.sheet, loc->a.col, loc->a.row);
457 switch (e) {
458 case MSTYLE_COLOR_BACK:
459 text = do_color (gnm_style_get_back_color (style));
460 qmarkup = TRUE;
461 break;
462 case MSTYLE_COLOR_PATTERN:
463 text = do_color (gnm_style_get_pattern_color (style));
464 qmarkup = TRUE;
465 break;
466 case MSTYLE_FONT_COLOR:
467 text = do_color (gnm_style_get_font_color (style));
468 qmarkup = TRUE;
469 break;
470 case MSTYLE_PATTERN:
471 // TODO: Add api to get pattern name from goffice
472 text = do_int (gnm_style_get_pattern (style));
473 break;
475 case MSTYLE_FONT_NAME:
476 text = g_strdup (gnm_style_get_font_name (style));
477 break;
478 case MSTYLE_FONT_BOLD:
479 text = do_bool (gnm_style_get_font_bold (style));
480 break;
481 case MSTYLE_FONT_ITALIC:
482 text = do_bool (gnm_style_get_font_italic (style));
483 break;
484 case MSTYLE_FONT_UNDERLINE:
485 text = do_enum (gnm_style_get_font_uline (style),
486 underlines);
487 break;
488 case MSTYLE_FONT_STRIKETHROUGH:
489 text = do_bool (gnm_style_get_font_strike (style));
490 break;
491 case MSTYLE_FONT_SCRIPT:
492 text = do_int (gnm_style_get_font_script (style));
493 break;
494 case MSTYLE_FONT_SIZE:
495 text = do_double (gnm_style_get_font_size (style));
496 break;
497 case MSTYLE_ROTATION:
498 text = do_int (gnm_style_get_rotation (style));
499 break;
500 case MSTYLE_INDENT:
501 text = do_int (gnm_style_get_indent (style));
502 break;
504 case MSTYLE_FORMAT:
505 text = g_strdup (go_format_as_XL (gnm_style_get_format (style)));
506 break;
508 case MSTYLE_TEXT_DIR:
509 text = do_enum (1 + gnm_style_get_text_dir (style),
510 textdirs);
511 break;
512 case MSTYLE_ALIGN_H:
513 text = do_halign (gnm_style_get_align_h (style));
514 break;
515 case MSTYLE_ALIGN_V:
516 text = do_valign (gnm_style_get_align_v (style));
517 break;
518 case MSTYLE_CONTENTS_LOCKED:
519 text = do_bool (gnm_style_get_contents_locked (style));
520 break;
521 case MSTYLE_CONTENTS_HIDDEN:
522 text = do_bool (gnm_style_get_contents_hidden (style));
523 break;
525 default:
526 text = g_strdup (_("Unavailable"));
528 } else if (section == SEC_COLROW) {
529 ColRowInfo const *cr =
530 sheet_colrow_get_info (loc->a.sheet, e, is_cols);
531 text = g_strdup_printf (ngettext ("%d pixel", "%d pixels", cr->size_pixels), cr->size_pixels);
534 done:
535 g_object_set (cell,
536 (qmarkup ? "markup" : "text"),
537 (text ? text : ""), NULL);
538 g_free (text);
540 g_free (loc);
541 return;
543 error:
544 text = g_strdup ("?");
545 goto done;
548 static void
549 dsc_sheet_start (gpointer user, Sheet const *os, Sheet const *ns)
551 SheetCompare *state = user;
552 state->old_sheet = (Sheet *)os;
553 state->new_sheet = (Sheet *)ns;
556 static void
557 dsc_sheet_end (gpointer user)
559 SheetCompare *state = user;
560 state->old_sheet = NULL;
561 state->new_sheet = NULL;
564 static void
565 dsc_cell_changed (gpointer user, GnmCell const *oc, GnmCell const *nc)
567 SheetCompare *state = user;
568 GtkTreeIter iter;
569 int dir;
571 setup_section (state,
572 &state->has_cell_section,
573 &state->cell_section_iter,
574 SEC_CELLS);
576 dir = (oc ? (nc ? DIR_CHANGED : DIR_REMOVED) : DIR_ADDED);
578 gtk_tree_store_insert (state->results, &iter,
579 &state->cell_section_iter,
580 -1);
581 gtk_tree_store_set (state->results, &iter,
582 ITEM_SECTION, SEC_CELLS,
583 ITEM_DIRECTION, dir,
584 -1);
586 if (oc) {
587 GnmRangeRef loc;
588 gnm_cellref_init (&loc.a, oc->base.sheet,
589 oc->pos.col, oc->pos.row,
590 FALSE);
591 loc.b = loc.a;
592 gtk_tree_store_set (state->results, &iter,
593 ITEM_OLD_LOC, &loc,
594 -1);
597 if (nc) {
598 GnmRangeRef loc;
599 gnm_cellref_init (&loc.a, nc->base.sheet,
600 nc->pos.col, nc->pos.row,
601 FALSE);
602 loc.b = loc.a;
603 gtk_tree_store_set (state->results, &iter,
604 ITEM_NEW_LOC, &loc,
605 -1);
609 static void
610 dsc_style_changed (gpointer user, GnmRange const *r,
611 GnmStyle const *os, GnmStyle const *ns)
613 SheetCompare *state = user;
614 GtkTreeIter iter, piter;
615 GnmRangeRef loc_old, loc_new;
616 unsigned int conflicts;
617 int e, estart;
619 conflicts = gnm_style_find_differences (os, ns, TRUE);
621 setup_section (state,
622 &state->has_style_section,
623 &state->style_section_iter,
624 SEC_STYLE);
626 loc_from_range (&loc_old, state->old_sheet, r);
627 loc_from_range (&loc_new, state->new_sheet, r);
629 piter = state->style_section_iter;
630 estart = ((conflicts & (conflicts - 1)) == 0 ? 0 : -1);
631 for (e = estart; e < MSTYLE_ELEMENT_MAX; e++) {
632 gboolean qhead = (e == -1);
633 if (!qhead && (conflicts & (1u << e)) == 0)
634 continue;
636 gtk_tree_store_insert (state->results, &iter, &piter, -1);
637 if (qhead) piter = iter;
639 gtk_tree_store_set (state->results, &iter,
640 ITEM_SECTION, SEC_STYLE,
641 ITEM_DIRECTION, DIR_QUIET,
642 ITEM_OLD_LOC, &loc_old,
643 ITEM_NEW_LOC, &loc_new,
644 ITEM_NO, e,
645 -1);
649 static void
650 dsc_colrow_changed (gpointer user, ColRowInfo const *oc, ColRowInfo const *nc,
651 gboolean is_cols, int i)
653 SheetCompare *state = user;
654 GtkTreeIter iter;
655 GnmRangeRef loc_old, loc_new;
656 GnmRange rold, rnew;
658 (is_cols ? range_init_cols : range_init_rows)
659 (&rold, state->old_sheet, i, i);
660 loc_from_range (&loc_old, state->old_sheet, &rold);
662 (is_cols ? range_init_cols : range_init_rows)
663 (&rnew, state->new_sheet, i, i);
664 loc_from_range (&loc_new, state->new_sheet, &rnew);
666 setup_section (state,
667 &state->has_colrow_section,
668 &state->colrow_section_iter,
669 SEC_COLROW);
671 gtk_tree_store_insert (state->results,
672 &iter, &state->colrow_section_iter, -1);
674 gtk_tree_store_set (state->results, &iter,
675 ITEM_SECTION, SEC_COLROW,
676 ITEM_DIRECTION, DIR_QUIET,
677 ITEM_OLD_LOC, &loc_old,
678 ITEM_NEW_LOC, &loc_new,
679 ITEM_NO, i,
680 ITEM_QCOLS, is_cols,
681 -1);
684 static const GnmDiffActions dsc_actions = {
685 .sheet_start = dsc_sheet_start,
686 .sheet_end = dsc_sheet_end,
687 .cell_changed = dsc_cell_changed,
688 .style_changed = dsc_style_changed,
689 .colrow_changed = dsc_colrow_changed,
692 static void
693 cb_compare_clicked (G_GNUC_UNUSED GtkWidget *ignore,
694 SheetCompare *state)
696 GtkTreeView *tv = state->results_view;
697 GtkTreeStore *ts = gtk_tree_store_new
698 (NUM_COLUMNS,
699 G_TYPE_INT, // Enum, really
700 G_TYPE_INT, // Enum, really
701 gnm_rangeref_get_type (),
702 gnm_rangeref_get_type (),
703 G_TYPE_INT,
704 G_TYPE_BOOLEAN);
705 Sheet *sheet_A, *sheet_B;
707 if (gtk_tree_view_get_n_columns (tv) == 0) {
708 GtkTreeViewColumn *tvc;
709 GtkCellRenderer *cr;
711 tvc = gtk_tree_view_column_new ();
712 cr = gtk_cell_renderer_text_new ();
713 gtk_tree_view_column_set_title (tvc, _("Description"));
714 gtk_tree_view_column_set_cell_data_func
715 (tvc, cr, section_renderer_func, NULL, NULL);
716 gtk_tree_view_column_pack_start (tvc, cr, TRUE);
717 gtk_tree_view_append_column (tv, tvc);
719 tvc = gtk_tree_view_column_new ();
720 cr = gtk_cell_renderer_text_new ();
721 gtk_tree_view_column_set_title (tvc, _("Location"));
722 gtk_tree_view_column_set_cell_data_func
723 (tvc, cr, location_renderer_func, NULL, NULL);
724 gtk_tree_view_column_pack_start (tvc, cr, TRUE);
725 gtk_tree_view_append_column (tv, tvc);
727 tvc = gtk_tree_view_column_new ();
728 cr = gtk_cell_renderer_text_new ();
729 g_object_set (G_OBJECT (cr), "max-width-chars", 30, NULL);
730 gtk_tree_view_column_set_title (tvc, _("Old"));
731 gtk_tree_view_column_set_cell_data_func
732 (tvc, cr, oldnew_renderer_func,
733 GUINT_TO_POINTER (FALSE), NULL);
734 gtk_tree_view_column_pack_start (tvc, cr, TRUE);
735 gtk_tree_view_append_column (tv, tvc);
737 tvc = gtk_tree_view_column_new ();
738 cr = gtk_cell_renderer_text_new ();
739 g_object_set (G_OBJECT (cr), "max-width-chars", 30, NULL);
740 gtk_tree_view_column_set_title (tvc, _("New"));
741 gtk_tree_view_column_set_cell_data_func
742 (tvc, cr, oldnew_renderer_func,
743 GUINT_TO_POINTER (TRUE), NULL);
744 gtk_tree_view_column_pack_start (tvc, cr, TRUE);
745 gtk_tree_view_append_column (tv, tvc);
748 state->has_cell_section = FALSE;
749 state->has_style_section = FALSE;
750 state->has_colrow_section = FALSE;
752 sheet_A = gnm_sheet_sel_get_sheet (GNM_SHEET_SEL (state->sheet_sel_A));
753 sheet_B = gnm_sheet_sel_get_sheet (GNM_SHEET_SEL (state->sheet_sel_B));
755 if (sheet_A && sheet_B) {
756 state->results = ts;
757 gnm_diff_sheets (&dsc_actions, state, sheet_A, sheet_B);
758 state->results = NULL;
761 gtk_tree_view_set_model (tv, GTK_TREE_MODEL (ts));
762 g_object_unref (ts);
764 gtk_notebook_set_current_page (GTK_NOTEBOOK (state->notebook), 1);
767 static SheetView *
768 find_and_focus (GnmRangeRef const *loc, SheetView *avoid)
770 Workbook *wb;
771 GnmRange r;
772 Sheet *loc_sheet;
774 if (!loc)
775 return NULL;
776 extract_range (loc, &r, &loc_sheet);
777 wb = loc_sheet->workbook;
779 WORKBOOK_FOREACH_VIEW(wb, view, {
780 SheetView *sv;
781 int col = r.start.col;
782 int row = r.start.row;
784 sv = wb_view_cur_sheet_view (view);
786 if (sv == avoid)
787 continue;
788 if (wb_view_cur_sheet (view) != loc_sheet)
789 continue;
791 gnm_sheet_view_set_edit_pos (sv, &r.start);
792 sv_selection_set (sv, &r.start, col, row, col, row);
793 gnm_sheet_view_make_cell_visible (sv, col, row, FALSE);
794 gnm_sheet_view_update (sv);
795 return sv;
797 return NULL;
800 static void
801 cb_cursor_changed (GtkTreeView *tree_view, SheetCompare *state)
803 GtkTreePath *path;
804 gboolean ok;
805 GnmRangeRef *loc_old = NULL;
806 GnmRangeRef *loc_new = NULL;
807 GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
808 SheetView *sv;
809 GtkTreeIter iter;
811 gtk_tree_view_get_cursor (tree_view, &path, NULL);
812 if (!path)
813 return;
815 ok = gtk_tree_model_get_iter (model, &iter, path);
816 gtk_tree_path_free (path);
817 if (!ok)
818 return;
820 gtk_tree_model_get (model, &iter,
821 ITEM_OLD_LOC, &loc_new,
822 ITEM_NEW_LOC, &loc_old,
823 -1);
825 sv = find_and_focus (loc_old, NULL);
826 (void)find_and_focus (loc_new, sv);
828 g_free (loc_old);
829 g_free (loc_new);
832 /* ------------------------------------------------------------------------- */
834 void
835 dialog_sheet_compare (WBCGtk *wbcg)
837 SheetCompare *state;
838 GtkBuilder *gui;
839 Workbook *wb;
840 PangoLayout *layout;
841 int height, width;
843 g_return_if_fail (wbcg != NULL);
845 wb = wb_control_get_workbook (GNM_WBC (wbcg));
847 gui = gnm_gtk_builder_load ("res:ui/sheet-compare.ui", NULL, GO_CMD_CONTEXT (wbcg));
848 if (gui == NULL)
849 return;
851 /* Only pop up one copy per workbook */
852 if (gnm_dialog_raise_if_exists (wbcg, SHEET_COMPARE_KEY))
853 return;
855 layout = gtk_widget_create_pango_layout (GTK_WIDGET (wbcg_toplevel (wbcg)), "Mg19");
856 pango_layout_get_pixel_size (layout, &width, &height);
857 g_object_unref (layout);
859 g_object_set_data (G_OBJECT (wb), SHEET_COMPARE_KEY, (gpointer) gui);
860 state = g_new0 (SheetCompare, 1);
861 state->gui = gui;
862 state->wbcg = wbcg;
863 state->dialog = go_gtk_builder_get_widget (gui, "sheet-compare-dialog");
864 state->notebook = go_gtk_builder_get_widget (gui, "notebook");
865 state->cancel_btn = go_gtk_builder_get_widget (gui, "cancel_button");
866 state->compare_btn = go_gtk_builder_get_widget (gui, "compare_button");
867 state->results_window = go_gtk_builder_get_widget (gui, "results_window");
868 state->results_view = GTK_TREE_VIEW (go_gtk_builder_get_widget (gui, "results_treeview"));
870 gtk_widget_set_size_request (state->results_window,
871 width / 4 * 40,
872 height * 10);
874 state->sheet_sel_A = gnm_sheet_sel_new ();
875 state->wb_sel_A = create_wb_selector (state, state->sheet_sel_A);
876 go_gtk_widget_replace (go_gtk_builder_get_widget (gui, "sheet_selector_A"),
877 state->sheet_sel_A);
878 go_gtk_widget_replace (go_gtk_builder_get_widget (gui, "wb_selector_A"),
879 state->wb_sel_A);
881 state->sheet_sel_B = gnm_sheet_sel_new ();
882 state->wb_sel_B = create_wb_selector (state, state->sheet_sel_B);
883 go_gtk_widget_replace (go_gtk_builder_get_widget (gui, "sheet_selector_B"),
884 state->sheet_sel_B);
885 go_gtk_widget_replace (go_gtk_builder_get_widget (gui, "wb_selector_B"),
886 state->wb_sel_B);
888 select_default_sheets (state);
890 #define CONNECT(o,s,c) g_signal_connect(G_OBJECT(o),s,G_CALLBACK(c),state)
891 CONNECT (state->cancel_btn, "clicked", cb_cancel_clicked);
892 CONNECT (state->compare_btn, "clicked", cb_compare_clicked);
893 CONNECT (state->results_view, "cursor-changed", cb_cursor_changed);
894 #undef CONNECT
896 /* a candidate for merging into attach guru */
897 wbc_gtk_attach_guru (state->wbcg, GTK_WIDGET (state->dialog));
898 g_object_set_data_full (G_OBJECT (state->dialog),
899 "state", state,
900 (GDestroyNotify) cb_sheet_compare_destroy);
902 gnm_restore_window_geometry (GTK_WINDOW (state->dialog),
903 SHEET_COMPARE_KEY);
905 go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
906 GTK_WINDOW (state->dialog));
907 gtk_widget_show_all (GTK_WIDGET (state->dialog));