1.12.42
[gnumeric.git] / src / dialogs / dialog-autoformat.c
blob08b5230fc6def6593787a4626732f01e691c23c4
1 /*
2 * dialog-autoformat.c : implementation of the autoformat dialog
4 * Author : Almer S. Tigelaar <almer@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/>.
21 * WORKING NOTE : Once the edit dialog is ready, search for FIXME and
22 * remove the disabling of new/edit/remove buttons!
25 #include <gnumeric-config.h>
26 #include <glib/gi18n-lib.h>
27 #include <gnumeric.h>
28 #include <dialogs/dialogs.h>
29 #include <dialogs/help.h>
31 #include <gui-util.h>
32 #include <mstyle.h>
33 #include <style-border.h>
34 #include <value.h>
35 #include <preview-grid-impl.h>
36 #include <format-template.h>
37 #include <file-autoft.h>
38 #include <command-context.h>
39 #include <workbook-control.h>
40 #include <workbook.h>
41 #include <wbc-gtk.h>
42 #include <commands.h>
43 #include <selection.h>
44 #include <ranges.h>
46 #include <goffice/goffice.h>
47 #include <gsf/gsf-impl-utils.h>
48 #include <string.h>
50 /* Table to show for
51 * previews, please don't make this larger than 5x5
53 #define PREVIEW_COLS 5
54 #define PREVIEW_ROWS 5
55 #define NUM_PREVIEWS 6
56 #define DEFAULT_COL_WIDTH 52
57 #define DEFAULT_ROW_HEIGHT 17
58 #define BORDER 7
59 #define INNER_BORDER 5
60 #define TOTAL_WIDTH (DEFAULT_COL_WIDTH * PREVIEW_COLS)
61 #define TOTAL_HEIGHT (DEFAULT_ROW_HEIGHT * PREVIEW_ROWS)
63 /* Keep these strings very short.
64 They are used as a sample data for a sheet, so you can put anything here
65 ("One", "Two", "Three" for example) */
66 static char const *const
67 demotable[PREVIEW_ROWS][PREVIEW_COLS] = {
68 { N_(" ") , N_("Jan"), N_("Feb"), N_("Mar"), N_("Total") },
69 { N_("North"), N_("6"), N_("13"), N_("20"), N_("39") },
70 { N_("South"), N_("12"), N_("4"), N_("17"), N_("33") },
71 { N_("West") , N_("8"), N_("2"), N_("0"), N_("10") },
72 { N_("Total"), N_("26"), N_("19"), N_("37"), N_("81") }
75 typedef struct {
76 Workbook *wb; /* Workbook we are working on */
77 WBCGtk *wbcg;
78 GocItem *grid[NUM_PREVIEWS]; /* Previewgrid's */
79 GocItem *selrect; /* Selection rectangle */
80 GSList *templates; /* List of GnmFT's */
81 GnmFT *selected_template; /* The currently selected template */
82 GList *category_groups; /* List of groups of categories */
84 GnmFTCategoryGroup *current_category_group; /* Currently selected category group */
86 int preview_top; /* Top index of the previewlist */
87 int preview_index; /* Selected canvas in previewlist */
88 gboolean previews_locked; /* If true, the preview_free and preview_load will not function */
89 gboolean more_down; /* If true, more was clicked and the button caption is now 'Less' */
92 * Gui elements
94 GtkDialog *dialog;
96 GtkComboBox *category;
98 GocCanvas *canvas[NUM_PREVIEWS];
99 GtkFrame *frame[NUM_PREVIEWS];
100 GtkScrollbar *scroll;
101 GtkCheckMenuItem *gridlines;
103 GtkEntry *info_name, *info_author, *info_cat;
104 GtkTextView *info_descr;
106 GtkCheckMenuItem *number, *border, *font, *patterns, *alignment;
108 struct {
109 GtkCheckMenuItem *left;
110 GtkCheckMenuItem *right;
111 GtkCheckMenuItem *top;
112 GtkCheckMenuItem *bottom;
113 } edges;
115 GtkButton *ok, *cancel;
116 } AutoFormatState;
118 /********************************************************************************/
120 typedef struct {
121 GnmPreviewGrid base;
122 GnmFT *ft;
123 } AutoFormatGrid;
124 typedef GnmPreviewGridClass AutoFormatGridClass;
126 static GnmStyle *
127 afg_get_cell_style (GnmPreviewGrid *pg, int col, int row)
129 /* If this happens to be NULL the default style
130 * will automatically be used. */
131 AutoFormatGrid *ag = (AutoFormatGrid *) pg;
132 return gnm_ft_get_style (ag->ft, row, col);
135 static GnmValue *
136 afg_get_cell_value (G_GNUC_UNUSED GnmPreviewGrid *pg, int col, int row)
138 char const *text;
139 char *endptr = NULL;
140 double tmp;
142 if (row >= PREVIEW_ROWS || col >= PREVIEW_COLS)
143 return NULL;
145 text = _(demotable[row][col]);
146 tmp = go_strtod (text, &endptr);
148 if (*endptr == '\0')
149 return value_new_float (tmp);
150 return value_new_string (text);
153 static void
154 auto_format_grid_class_init (GnmPreviewGridClass *klass)
156 klass->get_cell_style = afg_get_cell_style;
157 klass->get_cell_value = afg_get_cell_value;
160 static GSF_CLASS (AutoFormatGrid, auto_format_grid,
161 auto_format_grid_class_init, NULL,
162 gnm_preview_grid_get_type())
164 static GocItem *
165 auto_format_grid_new (AutoFormatState *state, int i, GnmFT *ft)
167 GocItem *item = goc_item_new (
168 goc_canvas_get_root (state->canvas[i]),
169 auto_format_grid_get_type (),
170 "render-gridlines", gtk_check_menu_item_get_active (state->gridlines),
171 "default-col-width", DEFAULT_COL_WIDTH,
172 "default-row-height", DEFAULT_ROW_HEIGHT,
173 "x", 0.,
174 "y", 0.,
175 NULL);
176 ((AutoFormatGrid *) item)->ft = ft;
177 return item;
179 /********************************************************************************
180 * UTILITY FUNCTIONS
181 ********************************************************************************/
183 static void
184 templates_free (AutoFormatState *state)
186 GSList *ptr;
188 g_return_if_fail (state != NULL);
190 for (ptr = state->templates; ptr != NULL ; ptr = ptr->next)
191 gnm_ft_free (ptr->data);
192 g_slist_free (state->templates);
193 state->templates = NULL;
197 * templates_load:
198 * @state: AutoFormatState
200 * This function will load the templates in the currently selected
201 * category group (it looks at state->category_groups to determine the selection)
203 * Returns: %TRUE if all went well, %FALSE otherwise.
205 static gboolean
206 templates_load (AutoFormatState *state)
208 GSList *l;
209 gint n_templates;
211 g_return_val_if_fail (state != NULL, FALSE);
213 if (state->category_groups == NULL)
214 return FALSE;
216 state->templates = gnm_ft_category_group_get_templates_list (
217 state->current_category_group, GO_CMD_CONTEXT (state->wbcg));
218 for (l = state->templates; l != NULL; l = l->next) {
219 GnmFT *ft = l->data;
220 range_init (&ft->dimension,
221 0, 0, PREVIEW_COLS - 1, PREVIEW_ROWS - 1);
222 ft->invalidate_hash = TRUE;
224 n_templates = g_slist_length (state->templates);
227 * We need to temporary lock the preview loading/freeing or
228 * else our scrollbar will trigger an event (value_changed) and create the
229 * previews. (which we don't want to happen at this moment)
231 state->previews_locked = TRUE;
233 GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (state->scroll));
234 gtk_adjustment_configure (adjustment,
235 0, 0, n_templates / 2,
236 1, 3, 3);
238 state->previews_locked = FALSE;
241 * Hide the scrollbar when it's not needed
243 gtk_widget_set_visible (GTK_WIDGET (state->scroll),
244 n_templates > NUM_PREVIEWS);
246 return TRUE;
250 * previews_free:
251 * @state: AutoFormatState
253 * This function will free all previews.
255 static void
256 previews_free (AutoFormatState *state)
258 int i;
260 if (state->previews_locked)
261 return;
263 if (state->selrect) {
264 goc_item_destroy (state->selrect);
265 state->selrect = NULL;
268 for (i = 0; i < NUM_PREVIEWS; i++) {
269 GocItem *item = state->grid[i];
270 if (item) {
271 goc_item_destroy (state->grid[i]);
272 state->grid[i] = NULL;
278 * previews_load:
279 * @state: AutoFormatState
280 * @topindex: The index of the template to be displayed in the upper left corner
282 * This function will create grids and rects for each canvas and associate
283 * them with the right format templates.
284 * NOTE : if state->preview_locked is %TRUE this function will do nothing,
285 * this is handy in situation where signals can cause previews_load to be
286 * called before previews_free.
288 static void
289 previews_load (AutoFormatState *state, int topindex)
291 GSList *iterator, *start;
292 int i, count = topindex;
294 g_return_if_fail (state != NULL);
296 if (state->previews_locked)
297 return;
299 iterator = state->templates;
300 start = iterator;
301 while (iterator && count > 0) {
302 iterator = g_slist_next (iterator);
303 start = iterator;
304 count--;
307 for (i = 0; i < NUM_PREVIEWS; i++) {
308 if (start == NULL) {
309 gtk_widget_hide (GTK_WIDGET (state->canvas[i]));
310 gtk_frame_set_shadow_type (state->frame[i], GTK_SHADOW_NONE);
311 } else {
312 GnmFT *ft = start->data;
314 state->grid[i] = auto_format_grid_new (state, i, ft);
316 /* Are we selected? Then draw a selection rectangle */
317 if (topindex + i == state->preview_index) {
318 GOStyle *style;
319 g_return_if_fail (state->selrect == NULL);
321 state->selrect = goc_item_new (goc_canvas_get_root (state->canvas[i]),
322 GOC_TYPE_RECTANGLE,
323 "x", (double)(-INNER_BORDER),
324 "y", (double)(-INNER_BORDER),
325 "width", (double)(TOTAL_WIDTH + 2 * INNER_BORDER),
326 "height", (double)(TOTAL_HEIGHT + 2 * INNER_BORDER),
327 NULL);
328 style = go_styled_object_get_style (GO_STYLED_OBJECT (state->selrect));
329 style->line.width = 3.;
330 style->line.color = GO_COLOR_RED;
331 style->fill.pattern.back = 0;
333 gtk_frame_set_shadow_type (state->frame[i], GTK_SHADOW_IN);
334 } else
335 gtk_frame_set_shadow_type (state->frame[i], GTK_SHADOW_ETCHED_IN);
337 goc_canvas_scroll_to (state->canvas[i],
338 -BORDER, -BORDER);
340 gtk_widget_set_tooltip_text
341 (GTK_WIDGET (state->canvas[i]),
342 _(ft->name));
344 gtk_widget_show (GTK_WIDGET (state->canvas[i]));
345 start = g_slist_next (start);
349 state->preview_top = topindex;
352 /********************************************************************************
353 * SIGNAL HANDLERS
354 ********************************************************************************/
356 static void
357 cb_ok_clicked (G_GNUC_UNUSED GtkButton *button,
358 AutoFormatState *state)
360 if (state->selected_template)
361 cmd_selection_autoformat (GNM_WBC (state->wbcg),
362 gnm_ft_clone (state->selected_template));
364 gtk_widget_destroy (GTK_WIDGET (state->dialog));
367 static void
368 cb_autoformat_destroy (AutoFormatState *state)
370 templates_free (state);
371 gnm_ft_category_group_list_free (state->category_groups);
372 g_free (state);
375 static void
376 cb_scroll_value_changed (GtkAdjustment *adjustment, AutoFormatState *state)
378 previews_free (state);
379 previews_load (state, rint (gtk_adjustment_get_value (adjustment)) * 2);
382 static gboolean
383 cb_canvas_button_press (GocCanvas *canvas,
384 G_GNUC_UNUSED GdkEventButton *event,
385 AutoFormatState *state)
387 GnmFT *ft;
388 GSList *ptr;
389 int index = 0;
391 while (index < NUM_PREVIEWS && canvas != state->canvas[index])
392 index++;
394 g_return_val_if_fail (index < NUM_PREVIEWS, FALSE);
396 state->preview_index = state->preview_top + index;
398 previews_free (state);
399 previews_load (state, state->preview_top);
401 for (ptr = state->templates, index = 0; ptr != NULL ; ptr = ptr->next, index++)
402 if (index == state->preview_index)
403 break;
405 g_return_val_if_fail (ptr != NULL && ptr->data != NULL, FALSE);
407 ft = ptr->data;
408 state->selected_template = ft;
409 gtk_entry_set_text (state->info_name, _(ft->name));
410 gtk_entry_set_text (state->info_author, ft->author);
411 gnm_textview_set_text (GTK_TEXT_VIEW (state->info_descr),
412 _(ft->description));
414 gtk_entry_set_text (state->info_cat, _(ft->category->name));
416 return TRUE;
419 static void
420 cb_check_item_toggled (G_GNUC_UNUSED GtkCheckMenuItem *item,
421 AutoFormatState *state)
423 GSList *ptr;
424 int i;
426 for (ptr = state->templates; ptr != NULL ; ptr = ptr->next) {
427 GnmFT *ft = ptr->data;
429 ft->number = gtk_check_menu_item_get_active (state->number);
430 ft->border = gtk_check_menu_item_get_active (state->border);
431 ft->font = gtk_check_menu_item_get_active (state->font);
432 ft->patterns = gtk_check_menu_item_get_active (state->patterns);
433 ft->alignment = gtk_check_menu_item_get_active (state->alignment);
435 ft->edges.left = gtk_check_menu_item_get_active (state->edges.left);
436 ft->edges.right = gtk_check_menu_item_get_active (state->edges.right);
437 ft->edges.top = gtk_check_menu_item_get_active (state->edges.top);
438 ft->edges.bottom = gtk_check_menu_item_get_active (state->edges.bottom);
440 ft->invalidate_hash = TRUE;
443 for (i = 0; i < NUM_PREVIEWS; i++)
444 goc_canvas_invalidate (state->canvas [i],
445 -2, -2, INT_MAX/2, INT_MAX/2);
448 static void
449 cb_category_changed (AutoFormatState *state)
451 GList *selection = g_list_nth (state->category_groups,
452 gtk_combo_box_get_active (state->category));
453 char const *tip = NULL;
455 state->current_category_group = (selection != NULL) ? selection->data : NULL;
456 previews_free (state);
457 templates_free (state);
458 if (templates_load (state) == FALSE)
459 g_warning ("Error while loading templates!");
461 if (NULL != state->current_category_group) {
462 tip = state->current_category_group->description;
463 if (NULL == tip)
464 tip = state->current_category_group->name;
466 gtk_widget_set_tooltip_text (GTK_WIDGET (state->category),
467 (NULL != tip) ? _(tip) : "");
468 previews_load (state, 0);
469 cb_check_item_toggled (NULL, state);
470 cb_canvas_button_press (state->canvas[0], NULL, state);
473 static void
474 cb_gridlines_item_toggled (G_GNUC_UNUSED GtkCheckMenuItem *item,
475 AutoFormatState *state)
477 previews_free (state);
478 previews_load (state, state->preview_top);
481 /********************************************************************************
482 * MAIN
483 ********************************************************************************/
485 static gboolean
486 cb_canvas_focus (GtkWidget *canvas,
487 G_GNUC_UNUSED GtkDirectionType direction,
488 AutoFormatState *state)
490 if (!gtk_widget_has_focus (canvas)) {
491 gtk_widget_grab_focus (canvas);
492 cb_canvas_button_press (GOC_CANVAS (canvas), NULL, state);
493 return TRUE;
495 return FALSE;
499 * dialog_autoformat:
500 * @wbcg: the control that invoked this dialog
502 * This function will show the AutoFormatTemplate dialog and apply
503 * the template the user chooses to the current selection in the active
504 * sheet of the workbook if the user desires.
506 void
507 dialog_autoformat (WBCGtk *wbcg)
509 GtkBuilder *gui;
510 AutoFormatState *state;
511 int i;
513 gui = gnm_gtk_builder_load ("res:ui/autoformat.ui", NULL, GO_CMD_CONTEXT (wbcg));
514 if (gui == NULL)
515 return;
517 state = g_new0 (AutoFormatState, 1);
518 state->wb = wb_control_get_workbook (GNM_WBC (wbcg));
519 state->wbcg = wbcg;
520 state->templates = NULL;
521 state->category_groups = NULL;
522 state->selrect = NULL;
523 for (i = 0; i < NUM_PREVIEWS; i++)
524 state->grid[i] = NULL;
526 state->current_category_group = NULL;
527 state->preview_top = 0;
528 state->preview_index = -1;
529 state->previews_locked = FALSE;
530 state->more_down = FALSE;
531 state->selected_template = NULL;
533 state->dialog = GTK_DIALOG (go_gtk_builder_get_widget (gui, "dialog"));
534 state->category = GTK_COMBO_BOX (go_gtk_builder_get_widget (gui, "format_category"));
535 state->scroll = GTK_SCROLLBAR (go_gtk_builder_get_widget (gui, "format_scroll"));
536 state->gridlines = GTK_CHECK_MENU_ITEM (go_gtk_builder_get_widget (gui, "format_gridlines"));
538 state->info_name = GTK_ENTRY (go_gtk_builder_get_widget (gui, "format_info_name"));
539 state->info_author = GTK_ENTRY (go_gtk_builder_get_widget (gui, "format_info_author"));
540 state->info_cat = GTK_ENTRY (go_gtk_builder_get_widget (gui, "format_info_cat"));
541 state->info_descr = GTK_TEXT_VIEW (go_gtk_builder_get_widget (gui, "format_info_descr"));
543 state->ok = GTK_BUTTON (go_gtk_builder_get_widget (gui, "format_ok"));
544 state->cancel = GTK_BUTTON (go_gtk_builder_get_widget (gui, "format_cancel"));
546 #define CHECK_ITEM(v_, w_,h_) do { \
547 GtkWidget *w = go_gtk_builder_get_widget (gui, (w_)); \
548 state->v_ = GTK_CHECK_MENU_ITEM (w); \
549 g_signal_connect (w, "activate", G_CALLBACK (h_), state); \
550 } while (0)
552 CHECK_ITEM (number, "number_menuitem", cb_check_item_toggled);
553 CHECK_ITEM (border, "border_menuitem", cb_check_item_toggled);
554 CHECK_ITEM (font, "font_menuitem", cb_check_item_toggled);
555 CHECK_ITEM (patterns, "pattern_menuitem", cb_check_item_toggled);
556 CHECK_ITEM (alignment, "alignment_menuitem", cb_check_item_toggled);
557 CHECK_ITEM (edges.left, "left_menuitem", cb_check_item_toggled);
558 CHECK_ITEM (edges.right, "right_menuitem", cb_check_item_toggled);
559 CHECK_ITEM (edges.top, "top_menuitem", cb_check_item_toggled);
560 CHECK_ITEM (edges.bottom, "bottom_menuitem", cb_check_item_toggled);
561 CHECK_ITEM (gridlines, "gridlines_menuitem", cb_gridlines_item_toggled);
563 #undef CHECK_ITEM
565 for (i = 0; i < NUM_PREVIEWS; i++) {
566 char *name;
568 name = g_strdup_printf ("format_frame%d", i+1);
569 state->frame[i] = GTK_FRAME (go_gtk_builder_get_widget (gui, name));
570 g_free (name);
572 state->canvas[i] = GOC_CANVAS (g_object_new (GOC_TYPE_CANVAS, NULL));
573 gtk_widget_set_size_request (GTK_WIDGET (state->canvas[i]),
574 TOTAL_WIDTH + (2 * BORDER),
575 TOTAL_HEIGHT + (2 * BORDER));
576 gtk_container_add (GTK_CONTAINER (state->frame[i]),
577 GTK_WIDGET (state->canvas[i]));
579 g_signal_connect (G_OBJECT (state->canvas[i]),
580 "button-press-event",
581 G_CALLBACK (cb_canvas_button_press), state);
582 g_signal_connect (G_OBJECT (state->canvas[i]),
583 "focus",
584 G_CALLBACK (cb_canvas_focus), state);
587 g_signal_connect (G_OBJECT (gtk_range_get_adjustment (GTK_RANGE (state->scroll))),
588 "value_changed",
589 G_CALLBACK (cb_scroll_value_changed), state);
590 g_signal_connect (G_OBJECT (state->gridlines),
591 "toggled",
592 G_CALLBACK (cb_gridlines_item_toggled), state);
593 g_signal_connect (G_OBJECT (state->ok),
594 "clicked",
595 G_CALLBACK (cb_ok_clicked), state);
596 g_signal_connect_swapped (G_OBJECT (state->cancel), "clicked",
597 G_CALLBACK (gtk_widget_destroy), state->dialog);
599 /* Fill category list */
600 state->category_groups =
601 g_list_sort (gnm_ft_category_group_list_get (), gnm_ft_category_group_cmp);
603 if (state->category_groups == NULL) {
604 GtkWidget *dialog;
606 dialog = gtk_message_dialog_new (GTK_WINDOW (state->dialog),
607 GTK_DIALOG_DESTROY_WITH_PARENT,
608 GTK_MESSAGE_WARNING,
609 GTK_BUTTONS_CLOSE,
610 _("An error occurred while reading the category list"));
611 gtk_dialog_run (GTK_DIALOG (dialog));
612 } else {
613 unsigned i, select = 0;
614 GList *ptr = state->category_groups;
615 GtkListStore* store = gtk_list_store_new (1, G_TYPE_STRING);
616 GtkTreeIter iter;
617 GtkCellRenderer *renderer = (GtkCellRenderer*) gtk_cell_renderer_text_new();
618 gtk_combo_box_set_model (state->category, GTK_TREE_MODEL (store));
619 g_object_unref (store);
620 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (state->category), renderer, TRUE);
621 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (state->category), renderer,
622 "text", 0,
623 NULL);
625 for (i = 0 ; ptr != NULL ; ptr = ptr->next, i++) {
626 GnmFTCategoryGroup *group = ptr->data;
627 if (!strcmp (group->name, "General" ))
628 select = i;
629 gtk_list_store_append (store, &iter);
630 gtk_list_store_set (store, &iter,
631 0, _(group->name),
632 -1);
635 g_signal_connect_swapped (G_OBJECT (state->category),
636 "changed",
637 G_CALLBACK (cb_category_changed), state);
638 gtk_combo_box_set_active (GTK_COMBO_BOX (state->category), select);
639 gtk_widget_show_all (GTK_WIDGET (state->category));
642 gnm_init_help_button (
643 go_gtk_builder_get_widget (gui, "help_button"),
644 GNUMERIC_HELP_LINK_AUTOFORMAT);
646 gtk_dialog_set_default_response (state->dialog, GTK_RESPONSE_OK);
648 /* a candidate for merging into attach guru */
649 go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
650 GTK_WINDOW (state->dialog));
651 wbc_gtk_attach_guru (state->wbcg, GTK_WIDGET (state->dialog));
652 g_object_set_data_full (G_OBJECT (state->dialog),
653 "state", state, (GDestroyNotify)cb_autoformat_destroy);
655 /* not show all or the scrollbars will appear */
656 gtk_widget_show (GTK_WIDGET (state->dialog));
657 g_object_unref (gui);