1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpscalecombobox.c
5 * Copyright (C) 2004, 2008 Sven Neumann <sven@gimp.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include "stdlib.h"
24
25 #include <gegl.h>
26 #include <gtk/gtk.h>
27 #include "gdk/gdkkeysyms.h"
28
29 #include "libgimpbase/gimpbase.h"
30 #include "libgimpmath/gimpmath.h"
31
32 #include "display-types.h"
33
34 #include "core/gimpmarshal.h"
35
36 #include "gimpscalecombobox.h"
37
38
39 #define MAX_ITEMS 10
40
41 enum
42 {
43 COLUMN_SCALE,
44 COLUMN_LABEL,
45 COLUMN_PERSISTENT,
46 N_COLUMNS
47 };
48
49 enum
50 {
51 ENTRY_ACTIVATED,
52 LAST_SIGNAL
53 };
54
55
56 static void gimp_scale_combo_box_constructed (GObject *object);
57 static void gimp_scale_combo_box_finalize (GObject *object);
58
59 static void gimp_scale_combo_box_style_set (GtkWidget *widget,
60 GtkStyle *prev_style);
61
62 static void gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box);
63 static void gimp_scale_combo_box_entry_activate (GtkWidget *entry,
64 GimpScaleComboBox *combo_box);
65 static gboolean gimp_scale_combo_box_entry_key_press (GtkWidget *entry,
66 GdkEventKey *event,
67 GimpScaleComboBox *combo_box);
68
69 static void gimp_scale_combo_box_scale_iter_set (GtkListStore *store,
70 GtkTreeIter *iter,
71 gdouble scale,
72 gboolean persistent);
73
74
75 G_DEFINE_TYPE (GimpScaleComboBox, gimp_scale_combo_box,
76 GTK_TYPE_COMBO_BOX)
77
78 #define parent_class gimp_scale_combo_box_parent_class
79
80 static guint scale_combo_box_signals[LAST_SIGNAL] = { 0 };
81
82
83 static void
gimp_scale_combo_box_class_init(GimpScaleComboBoxClass * klass)84 gimp_scale_combo_box_class_init (GimpScaleComboBoxClass *klass)
85 {
86 GObjectClass *object_class = G_OBJECT_CLASS (klass);
87 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
88
89 scale_combo_box_signals[ENTRY_ACTIVATED] =
90 g_signal_new ("entry-activated",
91 G_TYPE_FROM_CLASS (klass),
92 G_SIGNAL_RUN_FIRST,
93 G_STRUCT_OFFSET (GimpScaleComboBoxClass, entry_activated),
94 NULL, NULL,
95 gimp_marshal_VOID__VOID,
96 G_TYPE_NONE, 0);
97
98 object_class->constructed = gimp_scale_combo_box_constructed;
99 object_class->finalize = gimp_scale_combo_box_finalize;
100
101 widget_class->style_set = gimp_scale_combo_box_style_set;
102
103 klass->entry_activated = NULL;
104
105 gtk_widget_class_install_style_property (widget_class,
106 g_param_spec_double ("label-scale",
107 NULL, NULL,
108 0.0,
109 G_MAXDOUBLE,
110 1.0,
111 GIMP_PARAM_READABLE));
112 }
113
114 static void
gimp_scale_combo_box_init(GimpScaleComboBox * combo_box)115 gimp_scale_combo_box_init (GimpScaleComboBox *combo_box)
116 {
117 combo_box->scale = 1.0;
118 combo_box->last_path = NULL;
119 }
120
121 static void
gimp_scale_combo_box_constructed(GObject * object)122 gimp_scale_combo_box_constructed (GObject *object)
123 {
124 GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object);
125 GtkWidget *entry;
126 GtkListStore *store;
127 GtkCellLayout *layout;
128 GtkCellRenderer *cell;
129 GtkTreeIter iter;
130 GtkBorder border = { 0, 0, 0, 0 };
131 gint i;
132
133 G_OBJECT_CLASS (parent_class)->constructed (object);
134
135 store = gtk_list_store_new (N_COLUMNS,
136 G_TYPE_DOUBLE, /* SCALE */
137 G_TYPE_STRING, /* LABEL */
138 G_TYPE_BOOLEAN); /* PERSISTENT */
139
140 gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
141 g_object_unref (store);
142
143 gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo_box),
144 COLUMN_LABEL);
145
146 entry = gtk_bin_get_child (GTK_BIN (combo_box));
147
148 g_object_set (entry,
149 "xalign", 1.0,
150 "width-chars", 5,
151 "truncate-multiline", TRUE,
152 "inner-border", &border,
153 NULL);
154
155 layout = GTK_CELL_LAYOUT (combo_box);
156
157 cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
158 "xalign", 1.0,
159 NULL);
160
161 gtk_cell_layout_clear (layout);
162 gtk_cell_layout_pack_start (layout, cell, TRUE);
163 gtk_cell_layout_set_attributes (layout, cell,
164 "text", COLUMN_LABEL,
165 NULL);
166
167 for (i = 8; i > 0; i /= 2)
168 {
169 gtk_list_store_append (store, &iter);
170 gimp_scale_combo_box_scale_iter_set (store, &iter, i, TRUE);
171 }
172
173 for (i = 2; i <= 8; i *= 2)
174 {
175 gtk_list_store_append (store, &iter);
176 gimp_scale_combo_box_scale_iter_set (store, &iter, 1.0 / i, TRUE);
177 }
178
179 g_signal_connect (combo_box, "changed",
180 G_CALLBACK (gimp_scale_combo_box_changed),
181 NULL);
182
183 g_signal_connect (entry, "activate",
184 G_CALLBACK (gimp_scale_combo_box_entry_activate),
185 combo_box);
186 g_signal_connect (entry, "key-press-event",
187 G_CALLBACK (gimp_scale_combo_box_entry_key_press),
188 combo_box);
189 }
190
191 static void
gimp_scale_combo_box_finalize(GObject * object)192 gimp_scale_combo_box_finalize (GObject *object)
193 {
194 GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object);
195
196 if (combo_box->last_path)
197 {
198 gtk_tree_path_free (combo_box->last_path);
199 combo_box->last_path = NULL;
200 }
201
202 if (combo_box->mru)
203 {
204 g_list_free_full (combo_box->mru,
205 (GDestroyNotify) gtk_tree_row_reference_free);
206 combo_box->mru = NULL;
207 }
208
209 G_OBJECT_CLASS (parent_class)->finalize (object);
210 }
211
212 static void
gimp_scale_combo_box_style_set(GtkWidget * widget,GtkStyle * prev_style)213 gimp_scale_combo_box_style_set (GtkWidget *widget,
214 GtkStyle *prev_style)
215 {
216 GtkWidget *entry;
217 GtkRcStyle *rc_style;
218 PangoContext *context;
219 PangoFontDescription *font_desc;
220 gint font_size;
221 gdouble label_scale;
222
223 GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style);
224
225 gtk_widget_style_get (widget, "label-scale", &label_scale, NULL);
226
227 entry = gtk_bin_get_child (GTK_BIN (widget));
228
229 rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (entry));
230
231 if (rc_style->font_desc)
232 pango_font_description_free (rc_style->font_desc);
233
234 context = gtk_widget_get_pango_context (widget);
235 font_desc = pango_context_get_font_description (context);
236 rc_style->font_desc = pango_font_description_copy (font_desc);
237
238 font_size = pango_font_description_get_size (rc_style->font_desc);
239 pango_font_description_set_size (rc_style->font_desc, label_scale * font_size);
240
241 gtk_widget_modify_style (GTK_WIDGET (entry), rc_style);
242 }
243
244 static void
gimp_scale_combo_box_changed(GimpScaleComboBox * combo_box)245 gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box)
246 {
247 GtkTreeIter iter;
248
249 if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
250 {
251 GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
252 gdouble scale;
253
254 gtk_tree_model_get (model, &iter,
255 COLUMN_SCALE, &scale,
256 -1);
257 if (scale > 0.0)
258 {
259 combo_box->scale = scale;
260
261 if (combo_box->last_path)
262 gtk_tree_path_free (combo_box->last_path);
263
264 combo_box->last_path = gtk_tree_model_get_path (model, &iter);
265 }
266 }
267 }
268
269 static gboolean
gimp_scale_combo_box_parse_text(const gchar * text,gdouble * scale)270 gimp_scale_combo_box_parse_text (const gchar *text,
271 gdouble *scale)
272 {
273 gchar *end;
274 gdouble left_number;
275 gdouble right_number;
276
277 /* try to parse a number */
278 left_number = strtod (text, &end);
279
280 if (end == text)
281 return FALSE;
282 else
283 text = end;
284
285 /* skip over whitespace */
286 while (g_unichar_isspace (g_utf8_get_char (text)))
287 text = g_utf8_next_char (text);
288
289 if (*text == '\0' || *text == '%')
290 {
291 *scale = left_number / 100.0;
292 return TRUE;
293 }
294
295 /* check for a valid separator */
296 if (*text != '/' && *text != ':')
297 {
298 *scale = left_number;
299 return TRUE;
300 }
301
302 text = g_utf8_next_char (text);
303
304 /* skip over whitespace */
305 while (g_unichar_isspace (g_utf8_get_char (text)))
306 text = g_utf8_next_char (text);
307
308 /* try to parse another number */
309 right_number = strtod (text, &end);
310
311 if (end == text)
312 return FALSE;
313
314 if (right_number == 0.0)
315 return FALSE;
316
317 *scale = left_number / right_number;
318 return TRUE;
319 }
320
321 static void
gimp_scale_combo_box_entry_activate(GtkWidget * entry,GimpScaleComboBox * combo_box)322 gimp_scale_combo_box_entry_activate (GtkWidget *entry,
323 GimpScaleComboBox *combo_box)
324 {
325 const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry));
326 gdouble scale;
327
328 if (gimp_scale_combo_box_parse_text (text, &scale) &&
329 scale >= 1.0 / 256.0 &&
330 scale <= 256.0)
331 {
332 gimp_scale_combo_box_set_scale (combo_box, scale);
333 }
334 else
335 {
336 gtk_widget_error_bell (entry);
337
338 gimp_scale_combo_box_set_scale (combo_box, combo_box->scale);
339 }
340
341 g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0);
342 }
343
344 static gboolean
gimp_scale_combo_box_entry_key_press(GtkWidget * entry,GdkEventKey * event,GimpScaleComboBox * combo_box)345 gimp_scale_combo_box_entry_key_press (GtkWidget *entry,
346 GdkEventKey *event,
347 GimpScaleComboBox *combo_box)
348 {
349 if (event->keyval == GDK_KEY_Escape)
350 {
351 gimp_scale_combo_box_set_scale (combo_box, combo_box->scale);
352
353 g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0);
354
355 return TRUE;
356 }
357
358 if (event->keyval == GDK_KEY_Tab ||
359 event->keyval == GDK_KEY_KP_Tab ||
360 event->keyval == GDK_KEY_ISO_Left_Tab)
361 {
362 gimp_scale_combo_box_entry_activate (entry, combo_box);
363
364 return TRUE;
365 }
366
367 return FALSE;
368 }
369
370 static void
gimp_scale_combo_box_scale_iter_set(GtkListStore * store,GtkTreeIter * iter,gdouble scale,gboolean persistent)371 gimp_scale_combo_box_scale_iter_set (GtkListStore *store,
372 GtkTreeIter *iter,
373 gdouble scale,
374 gboolean persistent)
375 {
376 gchar label[32];
377
378 #ifdef G_OS_WIN32
379
380 /* use a normal space until pango's windows backend uses harfbuzz,
381 * see bug #735505
382 */
383 #define PERCENT_SPACE " "
384
385 #else
386
387 /* use U+2009 THIN SPACE to separate the percent sign from the number */
388 #define PERCENT_SPACE "\342\200\211"
389
390 #endif
391
392 if (scale > 1.0)
393 g_snprintf (label, sizeof (label),
394 "%d" PERCENT_SPACE "%%", (gint) ROUND (100.0 * scale));
395 else
396 g_snprintf (label, sizeof (label),
397 "%.3g" PERCENT_SPACE "%%", 100.0 * scale);
398
399 gtk_list_store_set (store, iter,
400 COLUMN_SCALE, scale,
401 COLUMN_LABEL, label,
402 COLUMN_PERSISTENT, persistent,
403 -1);
404 }
405
406 static void
gimp_scale_combo_box_mru_add(GimpScaleComboBox * combo_box,GtkTreeIter * iter)407 gimp_scale_combo_box_mru_add (GimpScaleComboBox *combo_box,
408 GtkTreeIter *iter)
409 {
410 GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
411 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
412 GList *list;
413 gboolean found;
414
415 for (list = combo_box->mru, found = FALSE; list && !found; list = list->next)
416 {
417 GtkTreePath *this = gtk_tree_row_reference_get_path (list->data);
418
419 if (gtk_tree_path_compare (this, path) == 0)
420 {
421 if (list->prev)
422 {
423 combo_box->mru = g_list_remove_link (combo_box->mru, list);
424 combo_box->mru = g_list_concat (list, combo_box->mru);
425 }
426
427 found = TRUE;
428 }
429
430 gtk_tree_path_free (this);
431 }
432
433 if (! found)
434 combo_box->mru = g_list_prepend (combo_box->mru,
435 gtk_tree_row_reference_new (model, path));
436
437 gtk_tree_path_free (path);
438 }
439
440 static void
gimp_scale_combo_box_mru_remove_last(GimpScaleComboBox * combo_box)441 gimp_scale_combo_box_mru_remove_last (GimpScaleComboBox *combo_box)
442 {
443 GtkTreeModel *model;
444 GtkTreePath *path;
445 GList *last;
446 GtkTreeIter iter;
447
448 if (! combo_box->mru)
449 return;
450
451 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
452
453 last = g_list_last (combo_box->mru);
454 path = gtk_tree_row_reference_get_path (last->data);
455
456 if (gtk_tree_model_get_iter (model, &iter, path))
457 {
458 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
459 gtk_tree_row_reference_free (last->data);
460 combo_box->mru = g_list_delete_link (combo_box->mru, last);
461 }
462
463 gtk_tree_path_free (path);
464 }
465
466
467 /**
468 * gimp_scale_combo_box_new:
469 *
470 * Return value: a new #GimpScaleComboBox.
471 **/
472 GtkWidget *
gimp_scale_combo_box_new(void)473 gimp_scale_combo_box_new (void)
474 {
475 return g_object_new (GIMP_TYPE_SCALE_COMBO_BOX,
476 "has-entry", TRUE,
477 NULL);
478 }
479
480 void
gimp_scale_combo_box_set_scale(GimpScaleComboBox * combo_box,gdouble scale)481 gimp_scale_combo_box_set_scale (GimpScaleComboBox *combo_box,
482 gdouble scale)
483 {
484 GtkTreeModel *model;
485 GtkListStore *store;
486 GtkWidget *entry;
487 GtkTreeIter iter;
488 gboolean iter_valid;
489 gboolean persistent;
490 gint n_digits;
491
492 g_return_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box));
493 g_return_if_fail (scale > 0.0);
494
495 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
496 store = GTK_LIST_STORE (model);
497
498 for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
499 iter_valid;
500 iter_valid = gtk_tree_model_iter_next (model, &iter))
501 {
502 gdouble this;
503
504 gtk_tree_model_get (model, &iter,
505 COLUMN_SCALE, &this,
506 -1);
507
508 if (fabs (this - scale) < 0.0001)
509 break;
510 }
511
512 if (! iter_valid)
513 {
514 GtkTreeIter sibling;
515
516 for (iter_valid = gtk_tree_model_get_iter_first (model, &sibling);
517 iter_valid;
518 iter_valid = gtk_tree_model_iter_next (model, &sibling))
519 {
520 gdouble this;
521
522 gtk_tree_model_get (model, &sibling,
523 COLUMN_SCALE, &this,
524 -1);
525
526 if (this < scale)
527 break;
528 }
529
530 gtk_list_store_insert_before (store, &iter, iter_valid ? &sibling : NULL);
531 gimp_scale_combo_box_scale_iter_set (store, &iter, scale, FALSE);
532 }
533
534 gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);
535
536 gtk_tree_model_get (model, &iter,
537 COLUMN_PERSISTENT, &persistent,
538 -1);
539 if (! persistent)
540 {
541 gimp_scale_combo_box_mru_add (combo_box, &iter);
542
543 if (gtk_tree_model_iter_n_children (model, NULL) > MAX_ITEMS)
544 gimp_scale_combo_box_mru_remove_last (combo_box);
545 }
546
547 /* Update entry size appropriately. */
548 entry = gtk_bin_get_child (GTK_BIN (combo_box));
549 n_digits = (gint) floor (log10 (scale) + 1);
550
551 g_object_set (entry,
552 "width-chars", MAX (5, n_digits + 4),
553 NULL);
554 }
555
556 gdouble
gimp_scale_combo_box_get_scale(GimpScaleComboBox * combo_box)557 gimp_scale_combo_box_get_scale (GimpScaleComboBox *combo_box)
558 {
559 g_return_val_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box), 1.0);
560
561 return combo_box->scale;
562 }
563