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>
23 #include "sheet-diff.h"
29 #include <workbook-view.h>
34 #include <sheet-style.h>
35 #include <application.h>
36 #include <widgets/gnm-sheet-sel.h>
37 #include <widgets/gnm-workbook-sel.h>
39 #define SHEET_COMPARE_KEY "sheet-compare-dialog"
60 DIR_QUIET
// Like CHANGED, but for always-changed context
71 GtkWidget
*cancel_btn
;
72 GtkWidget
*compare_btn
;
74 GtkWidget
*sheet_sel_A
;
75 GtkWidget
*sheet_sel_B
;
78 GtkWidget
*results_window
;
80 GtkTreeView
*results_view
;
81 GtkTreeStore
*results
;
83 gboolean has_cell_section
;
84 GtkTreeIter cell_section_iter
;
86 gboolean has_style_section
;
87 GtkTreeIter style_section_iter
;
95 cb_sheet_compare_destroy (SheetCompare
*state
)
97 Workbook
*wb
= wb_control_get_workbook (GNM_WBC (state
->wbcg
));
99 g_object_unref (state
->gui
);
100 g_object_set_data (G_OBJECT (wb
), SHEET_COMPARE_KEY
, NULL
);
107 cb_cancel_clicked (G_GNUC_UNUSED GtkWidget
*ignore
,
110 gtk_widget_destroy (GTK_WIDGET (state
->dialog
));
114 create_wb_selector (SheetCompare
*state
, GtkWidget
*sheet_sel
)
116 GtkWidget
*res
= gnm_workbook_sel_new ();
117 gnm_sheet_sel_link (GNM_SHEET_SEL (sheet_sel
),
118 GNM_WORKBOOK_SEL (res
));
123 select_default_sheets (SheetCompare
*state
)
125 Workbook
*wb
= wb_control_get_workbook (GNM_WBC (state
->wbcg
));
126 GList
*wb_list
= gnm_app_workbook_list ();
128 if (g_list_length (wb_list
) > 1) {
129 // Multiple workbooks
131 gnm_workbook_sel_set_workbook
132 (GNM_WORKBOOK_SEL (state
->wb_sel_A
), wb
);
133 gnm_workbook_sel_set_workbook
134 (GNM_WORKBOOK_SEL (state
->wb_sel_B
),
135 wb
== wb_list
->data
? wb_list
->next
->data
: wb_list
->data
);
136 } else if (workbook_sheet_count (wb
) > 1) {
137 // One workbook, multiple sheets
138 gnm_sheet_sel_set_sheet (GNM_SHEET_SEL (state
->sheet_sel_B
),
139 workbook_sheet_by_index (wb
, 1));
141 // One workbook, one sheet
145 /* ------------------------------------------------------------------------- */
148 setup_section (SheetCompare
*state
, gboolean
*phas
, GtkTreeIter
*iter
,
152 gtk_tree_store_insert (state
->results
, iter
, NULL
, -1);
153 gtk_tree_store_set (state
->results
, iter
,
154 ITEM_SECTION
, section
,
155 ITEM_DIRECTION
, DIR_NA
,
162 extract_range (GnmRangeRef
const *rr
, GnmRange
*r
, Sheet
**psheet
)
164 *psheet
= rr
->a
.sheet
;
165 r
->start
.col
= rr
->a
.col
;
166 r
->start
.row
= rr
->a
.row
;
167 r
->end
.col
= rr
->b
.col
;
168 r
->end
.row
= rr
->b
.row
;
172 get_mstyle_name (int e
)
175 case MSTYLE_COLOR_BACK
: return _("Background color");
176 case MSTYLE_COLOR_PATTERN
: return _("Pattern color");
178 case MSTYLE_BORDER_TOP
: return _("Top border");
179 case MSTYLE_BORDER_BOTTOM
: return _("Bottom border");
180 case MSTYLE_BORDER_LEFT
: return _("Left border");
181 case MSTYLE_BORDER_RIGHT
: return _("Right border");
182 case MSTYLE_BORDER_REV_DIAGONAL
: return _("Reverse diagonal border");
183 case MSTYLE_BORDER_DIAGONAL
: return _("Diagonal border");
184 case MSTYLE_PATTERN
: return _("Pattern");
186 case MSTYLE_FONT_COLOR
: return _("Color");
187 case MSTYLE_FONT_NAME
: return _("Font");
188 case MSTYLE_FONT_BOLD
: return _("Bold");
189 case MSTYLE_FONT_ITALIC
: return _("Italic");
190 case MSTYLE_FONT_UNDERLINE
: return _("Underline");
191 case MSTYLE_FONT_STRIKETHROUGH
: return _("Strikethrough");
192 case MSTYLE_FONT_SCRIPT
: return _("Script");
193 case MSTYLE_FONT_SIZE
: return _("Size");
195 case MSTYLE_FORMAT
: return _("Format");
197 case MSTYLE_ALIGN_V
: return _("Vertical alignment");
198 case MSTYLE_ALIGN_H
: return _("Horizontal alignment");
199 case MSTYLE_INDENT
: return _("Indentation");
200 case MSTYLE_ROTATION
: return _("Rotation");
201 case MSTYLE_TEXT_DIR
: return _("Direction");
202 case MSTYLE_WRAP_TEXT
: return _("Wrap");
203 case MSTYLE_SHRINK_TO_FIT
: return _("Shrink-to-fit");
205 case MSTYLE_CONTENTS_LOCKED
: return _("Locked");
206 case MSTYLE_CONTENTS_HIDDEN
: return _("Hidden");
208 case MSTYLE_VALIDATION
: return _("Validation");
209 case MSTYLE_HLINK
: return _("Hyperlink");
210 case MSTYLE_INPUT_MSG
: return _("Input message");
211 case MSTYLE_CONDITIONS
: return _("Conditional format");
218 section_renderer_func (GtkTreeViewColumn
*tree_column
,
219 GtkCellRenderer
*cell
,
225 const char *text
= "?";
228 gtk_tree_model_get (model
, iter
,
229 ITEM_SECTION
, §ion
,
230 ITEM_DIRECTION
, &dir
,
231 ITEM_MSTYLE_ELEM
, &e
,
236 case SEC_CELLS
: text
= _("Cells"); break;
237 case SEC_STYLE
: text
= _("Formatting"); break;
241 text
= (e
== -1) ? _("Various") : get_mstyle_name (e
);
243 case DIR_ADDED
: text
= _("Added"); break;
244 case DIR_REMOVED
: text
= _("Removed"); break;
245 case DIR_CHANGED
: text
= _("Changed"); break;
248 g_object_set (cell
, "text", text
, NULL
);
252 location_renderer_func (GtkTreeViewColumn
*tree_column
,
253 GtkCellRenderer
*cell
,
258 GnmRangeRef
*loc_old
= NULL
;
259 GnmRangeRef
*loc_new
= NULL
;
262 gtk_tree_model_get (model
, iter
,
263 ITEM_OLD_LOC
, &loc_new
,
264 ITEM_NEW_LOC
, &loc_old
,
267 loc
= loc_old
? loc_old
: loc_new
;
271 extract_range (loc
, &r
, &sheet
);
272 g_object_set (cell
, "text", range_as_string (&r
), NULL
);
274 g_object_set (cell
, "text", "", NULL
);
281 do_color (GnmColor
const *gcolor
)
283 GOColor color
= gcolor
->go_color
;
286 const char *coltxt
= NULL
;
290 GO_COLOR_TO_RGBA (color
, &r
, &g
, &b
, &a
);
292 snprintf (buf
, sizeof (coltxt
), "#%02X%02X%02X", r
, g
, b
);
294 snprintf (buf
, sizeof (coltxt
), "#%02X%02X%02X%02X", r
, g
, b
, a
);
296 for (n
= 0; go_color_palette_query (n
, &nc
); n
++) {
297 if (nc
.color
== color
) {
303 return g_strdup_printf
304 ("%s%s (<span bgcolor=\"%s\"> </span>)",
305 gcolor
->is_auto
? "Auto " : "",
306 coltxt
? coltxt
: buf
,
313 return g_strdup (b
? _("Yes") : _("No"));
319 return g_strdup_printf ("%d", i
);
325 return g_strdup_printf ("%g", d
);
329 do_halign (GnmHAlign h
)
332 case GNM_HALIGN_GENERAL
: return g_strdup (_("General"));
333 case GNM_HALIGN_LEFT
: return g_strdup (_("Left"));
334 case GNM_HALIGN_RIGHT
: return g_strdup (_("Right"));
335 case GNM_HALIGN_CENTER
: return g_strdup (_("Center"));
336 case GNM_HALIGN_FILL
: return g_strdup (_("Fill"));
337 case GNM_HALIGN_JUSTIFY
: return g_strdup (_("Justify"));
338 case GNM_HALIGN_CENTER_ACROSS_SELECTION
: return g_strdup (_("Center across selection"));
339 case GNM_HALIGN_DISTRIBUTED
: return g_strdup (_("Distributed"));
340 default: return g_strdup ("?");
345 do_valign (GnmVAlign v
)
348 case GNM_VALIGN_TOP
: return g_strdup (_("Top"));
349 case GNM_VALIGN_BOTTOM
: return g_strdup (_("Bottom"));
350 case GNM_VALIGN_CENTER
: return g_strdup (_("Center"));
351 case GNM_VALIGN_JUSTIFY
: return g_strdup (_("Justify"));
352 case GNM_VALIGN_DISTRIBUTED
: return g_strdup (_("Distributed"));
353 default: return g_strdup ("?");
357 static const char *underlines
[] = {
358 N_("None"), N_("Single"), N_("Double"),
359 N_("Single low"), N_("Double low"), NULL
362 static const char *textdirs
[] = {
363 N_("Right-to-left"), N_("Auto"), N_("Left-to-rightDouble"), NULL
367 do_enum (int i
, const char *const choices
[])
369 if (i
< 0 || i
>= (int)g_strv_length ((gchar
**)choices
))
370 return g_strdup ("?");
371 return g_strdup (_(choices
[i
]));
376 oldnew_renderer_func (GtkTreeViewColumn
*tree_column
,
377 GtkCellRenderer
*cell
,
382 gboolean qnew
= GPOINTER_TO_UINT (user_data
);
383 GnmRangeRef
*loc
= NULL
;
386 gboolean qmarkup
= FALSE
;
388 gtk_tree_model_get (model
, iter
,
389 ITEM_SECTION
, §ion
,
390 ITEM_DIRECTION
, &dir
,
391 (qnew
? ITEM_NEW_LOC
: ITEM_OLD_LOC
), &loc
,
392 ITEM_MSTYLE_ELEM
, &e
,
394 if (dir
== DIR_NA
|| !loc
|| !loc
->a
.sheet
)
397 if (section
== SEC_CELLS
) {
398 GnmCell
const *cell
=
399 sheet_cell_get (loc
->a
.sheet
, loc
->a
.col
, loc
->a
.row
);
402 text
= gnm_cell_get_entered_text (cell
);
403 } else if (section
== SEC_STYLE
) {
404 GnmStyle
const *style
;
408 style
= sheet_style_get (loc
->a
.sheet
, loc
->a
.col
, loc
->a
.row
);
411 case MSTYLE_COLOR_BACK
:
412 text
= do_color (gnm_style_get_back_color (style
));
415 case MSTYLE_COLOR_PATTERN
:
416 text
= do_color (gnm_style_get_pattern_color (style
));
419 case MSTYLE_FONT_COLOR
:
420 text
= do_color (gnm_style_get_font_color (style
));
424 // TODO: Add api to get pattern name from goffice
425 text
= do_int (gnm_style_get_pattern (style
));
428 case MSTYLE_FONT_BOLD
:
429 text
= do_bool (gnm_style_get_font_bold (style
));
431 case MSTYLE_FONT_ITALIC
:
432 text
= do_bool (gnm_style_get_font_italic (style
));
434 case MSTYLE_FONT_UNDERLINE
:
435 text
= do_enum (gnm_style_get_font_uline (style
),
438 case MSTYLE_FONT_STRIKETHROUGH
:
439 text
= do_bool (gnm_style_get_font_strike (style
));
441 case MSTYLE_FONT_SCRIPT
:
442 text
= do_int (gnm_style_get_font_script (style
));
444 case MSTYLE_FONT_SIZE
:
445 text
= do_double (gnm_style_get_font_size (style
));
447 case MSTYLE_ROTATION
:
448 text
= do_int (gnm_style_get_rotation (style
));
451 text
= do_int (gnm_style_get_indent (style
));
455 text
= g_strdup (go_format_as_XL (gnm_style_get_format (style
)));
458 case MSTYLE_TEXT_DIR
:
459 text
= do_enum (1 + gnm_style_get_text_dir (style
),
463 text
= do_halign (gnm_style_get_align_h (style
));
466 text
= do_valign (gnm_style_get_align_v (style
));
468 case MSTYLE_CONTENTS_LOCKED
:
469 text
= do_bool (gnm_style_get_contents_locked (style
));
471 case MSTYLE_CONTENTS_HIDDEN
:
472 text
= do_bool (gnm_style_get_contents_hidden (style
));
476 text
= g_strdup (_("Unavailable"));
483 (qmarkup
? "markup" : "text"),
484 (text
? text
: ""), NULL
);
491 text
= g_strdup ("?");
496 dsc_sheet_start (gpointer user
, Sheet
const *os
, Sheet
const *ns
)
498 SheetCompare
*state
= user
;
499 state
->old_sheet
= (Sheet
*)os
;
500 state
->new_sheet
= (Sheet
*)ns
;
504 dsc_sheet_end (gpointer user
)
506 SheetCompare
*state
= user
;
507 state
->old_sheet
= NULL
;
508 state
->new_sheet
= NULL
;
512 dsc_cell_changed (gpointer user
, GnmCell
const *oc
, GnmCell
const *nc
)
514 SheetCompare
*state
= user
;
518 setup_section (state
,
519 &state
->has_cell_section
,
520 &state
->cell_section_iter
,
523 dir
= (oc
? (nc
? DIR_CHANGED
: DIR_REMOVED
) : DIR_ADDED
);
525 gtk_tree_store_insert (state
->results
, &iter
,
526 &state
->cell_section_iter
,
528 gtk_tree_store_set (state
->results
, &iter
,
529 ITEM_SECTION
, SEC_CELLS
,
535 gnm_cellref_init (&loc
.a
, oc
->base
.sheet
,
536 oc
->pos
.col
, oc
->pos
.row
,
539 gtk_tree_store_set (state
->results
, &iter
,
546 gnm_cellref_init (&loc
.a
, nc
->base
.sheet
,
547 nc
->pos
.col
, nc
->pos
.row
,
550 gtk_tree_store_set (state
->results
, &iter
,
557 dsc_style_changed (gpointer user
, GnmRange
const *r
,
558 GnmStyle
const *os
, GnmStyle
const *ns
)
560 SheetCompare
*state
= user
;
561 GtkTreeIter iter
, piter
;
562 GnmRangeRef loc_old
, loc_new
;
563 unsigned int conflicts
;
566 conflicts
= gnm_style_find_differences (os
, ns
, TRUE
);
568 setup_section (state
,
569 &state
->has_style_section
,
570 &state
->style_section_iter
,
573 gnm_cellref_init (&loc_old
.a
, state
->old_sheet
,
574 r
->start
.col
, r
->start
.row
,
576 gnm_cellref_init (&loc_old
.b
, state
->old_sheet
,
577 r
->end
.col
, r
->end
.row
,
580 loc_new
.a
.sheet
= loc_new
.b
.sheet
= state
->new_sheet
;
582 piter
= state
->style_section_iter
;
583 estart
= ((conflicts
& (conflicts
- 1)) == 0 ? 0 : -1);
584 for (e
= estart
; e
< MSTYLE_ELEMENT_MAX
; e
++) {
585 gboolean qhead
= (e
== -1);
586 if (!qhead
&& (conflicts
& (1u << e
)) == 0)
589 gtk_tree_store_insert (state
->results
, &iter
, &piter
, -1);
590 if (qhead
) piter
= iter
;
592 gtk_tree_store_set (state
->results
, &iter
,
593 ITEM_SECTION
, SEC_STYLE
,
594 ITEM_DIRECTION
, DIR_QUIET
,
595 ITEM_OLD_LOC
, &loc_old
,
596 ITEM_NEW_LOC
, &loc_new
,
602 static const GnmDiffActions dsc_actions
= {
603 .sheet_start
= dsc_sheet_start
,
604 .sheet_end
= dsc_sheet_end
,
605 .cell_changed
= dsc_cell_changed
,
606 .style_changed
= dsc_style_changed
,
610 cb_compare_clicked (G_GNUC_UNUSED GtkWidget
*ignore
,
613 GtkTreeView
*tv
= state
->results_view
;
614 GtkTreeStore
*ts
= gtk_tree_store_new
616 G_TYPE_INT
, // Enum, really
617 G_TYPE_INT
, // Enum, really
618 gnm_rangeref_get_type (),
619 gnm_rangeref_get_type (),
620 G_TYPE_INT
); // GnmStyleElement
621 Sheet
*sheet_A
, *sheet_B
;
623 if (gtk_tree_view_get_n_columns (tv
) == 0) {
624 GtkTreeViewColumn
*tvc
;
627 tvc
= gtk_tree_view_column_new ();
628 cr
= gtk_cell_renderer_text_new ();
629 gtk_tree_view_column_set_title (tvc
, _("Description"));
630 gtk_tree_view_column_set_cell_data_func
631 (tvc
, cr
, section_renderer_func
, NULL
, NULL
);
632 gtk_tree_view_column_pack_start (tvc
, cr
, TRUE
);
633 gtk_tree_view_append_column (tv
, tvc
);
635 tvc
= gtk_tree_view_column_new ();
636 cr
= gtk_cell_renderer_text_new ();
637 gtk_tree_view_column_set_title (tvc
, _("Location"));
638 gtk_tree_view_column_set_cell_data_func
639 (tvc
, cr
, location_renderer_func
, NULL
, NULL
);
640 gtk_tree_view_column_pack_start (tvc
, cr
, TRUE
);
641 gtk_tree_view_append_column (tv
, tvc
);
643 tvc
= gtk_tree_view_column_new ();
644 cr
= gtk_cell_renderer_text_new ();
645 gtk_tree_view_column_set_title (tvc
, _("Old"));
646 gtk_tree_view_column_set_cell_data_func
647 (tvc
, cr
, oldnew_renderer_func
,
648 GUINT_TO_POINTER (FALSE
), NULL
);
649 gtk_tree_view_column_pack_start (tvc
, cr
, TRUE
);
650 gtk_tree_view_append_column (tv
, tvc
);
652 tvc
= gtk_tree_view_column_new ();
653 cr
= gtk_cell_renderer_text_new ();
654 gtk_tree_view_column_set_title (tvc
, _("New"));
655 gtk_tree_view_column_set_cell_data_func
656 (tvc
, cr
, oldnew_renderer_func
,
657 GUINT_TO_POINTER (TRUE
), NULL
);
658 gtk_tree_view_column_pack_start (tvc
, cr
, TRUE
);
659 gtk_tree_view_append_column (tv
, tvc
);
662 state
->has_cell_section
= FALSE
;
663 state
->has_style_section
= FALSE
;
665 sheet_A
= gnm_sheet_sel_get_sheet (GNM_SHEET_SEL (state
->sheet_sel_A
));
666 sheet_B
= gnm_sheet_sel_get_sheet (GNM_SHEET_SEL (state
->sheet_sel_B
));
668 if (sheet_A
&& sheet_B
) {
670 gnm_diff_sheets (&dsc_actions
, state
, sheet_A
, sheet_B
);
671 state
->results
= NULL
;
674 gtk_tree_view_set_model (tv
, GTK_TREE_MODEL (ts
));
677 gtk_notebook_set_current_page (GTK_NOTEBOOK (state
->notebook
), 1);
680 /* ------------------------------------------------------------------------- */
683 dialog_sheet_compare (WBCGtk
*wbcg
)
691 g_return_if_fail (wbcg
!= NULL
);
693 wb
= wb_control_get_workbook (GNM_WBC (wbcg
));
695 gui
= gnm_gtk_builder_load ("sheet-compare.ui", NULL
, GO_CMD_CONTEXT (wbcg
));
699 /* Only pop up one copy per workbook */
700 if (gnm_dialog_raise_if_exists (wbcg
, SHEET_COMPARE_KEY
))
703 layout
= gtk_widget_create_pango_layout (GTK_WIDGET (wbcg_toplevel (wbcg
)), "Mg19");
704 pango_layout_get_pixel_size (layout
, &width
, &height
);
705 g_object_unref (layout
);
707 g_object_set_data (G_OBJECT (wb
), SHEET_COMPARE_KEY
, (gpointer
) gui
);
708 state
= g_new0 (SheetCompare
, 1);
711 state
->dialog
= go_gtk_builder_get_widget (gui
, "sheet-compare-dialog");
712 state
->notebook
= go_gtk_builder_get_widget (gui
, "notebook");
713 state
->cancel_btn
= go_gtk_builder_get_widget (gui
, "cancel_button");
714 state
->compare_btn
= go_gtk_builder_get_widget (gui
, "compare_button");
715 state
->results_window
= go_gtk_builder_get_widget (gui
, "results_window");
716 state
->results_view
= GTK_TREE_VIEW (go_gtk_builder_get_widget (gui
, "results_treeview"));
718 gtk_widget_set_size_request (state
->results_window
,
722 state
->sheet_sel_A
= gnm_sheet_sel_new ();
723 state
->wb_sel_A
= create_wb_selector (state
, state
->sheet_sel_A
);
724 go_gtk_widget_replace (go_gtk_builder_get_widget (gui
, "sheet_selector_A"),
726 go_gtk_widget_replace (go_gtk_builder_get_widget (gui
, "wb_selector_A"),
729 state
->sheet_sel_B
= gnm_sheet_sel_new ();
730 state
->wb_sel_B
= create_wb_selector (state
, state
->sheet_sel_B
);
731 go_gtk_widget_replace (go_gtk_builder_get_widget (gui
, "sheet_selector_B"),
733 go_gtk_widget_replace (go_gtk_builder_get_widget (gui
, "wb_selector_B"),
736 select_default_sheets (state
);
738 #define CONNECT(o,s,c) g_signal_connect(G_OBJECT(o),s,G_CALLBACK(c),state)
739 CONNECT (state
->cancel_btn
, "clicked", cb_cancel_clicked
);
740 CONNECT (state
->compare_btn
, "clicked", cb_compare_clicked
);
743 /* a candidate for merging into attach guru */
744 wbc_gtk_attach_guru (state
->wbcg
, GTK_WIDGET (state
->dialog
));
745 g_object_set_data_full (G_OBJECT (state
->dialog
),
747 (GDestroyNotify
) cb_sheet_compare_destroy
);
749 gnm_restore_window_geometry (GTK_WINDOW (state
->dialog
),
752 go_gtk_nonmodal_dialog (wbcg_toplevel (state
->wbcg
),
753 GTK_WINDOW (state
->dialog
));
754 gtk_widget_show_all (GTK_WIDGET (state
->dialog
));