1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
4  * gimpmemsizeentry.c
5  * Copyright (C) 2000-2003  Sven Neumann <sven@gimp.org>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This library 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 GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <gegl.h>
25 #include <gtk/gtk.h>
26 
27 #include "gimpwidgetstypes.h"
28 
29 #include "gimpmemsizeentry.h"
30 #include "gimpspinbutton.h"
31 #include "gimpwidgets.h"
32 
33 #include "libgimp/libgimp-intl.h"
34 
35 
36 /**
37  * SECTION: gimpmemsizeentry
38  * @title: GimpMemSizeEntry
39  * @short_description: A composite widget to enter a memory size.
40  *
41  * Similar to a #GimpSizeEntry but instead of lengths, this widget is
42  * used to let the user enter memory sizes. A combo box allows one to
43  * switch between Kilobytes, Megabytes and Gigabytes. Used in the GIMP
44  * preferences dialog.
45  **/
46 
47 
48 enum
49 {
50   VALUE_CHANGED,
51   LAST_SIGNAL
52 };
53 
54 
55 static void  gimp_memsize_entry_finalize      (GObject          *object);
56 
57 static void  gimp_memsize_entry_adj_callback  (GtkAdjustment    *adj,
58                                                GimpMemsizeEntry *entry);
59 static void  gimp_memsize_entry_unit_callback (GtkWidget        *widget,
60                                                GimpMemsizeEntry *entry);
61 
62 static guint64 gimp_memsize_entry_get_rounded_value (GimpMemsizeEntry *entry,
63                                                      guint64           value);
64 
65 G_DEFINE_TYPE (GimpMemsizeEntry, gimp_memsize_entry, GTK_TYPE_BOX)
66 
67 #define parent_class gimp_memsize_entry_parent_class
68 
69 static guint gimp_memsize_entry_signals[LAST_SIGNAL] = { 0 };
70 
71 
72 static void
gimp_memsize_entry_class_init(GimpMemsizeEntryClass * klass)73 gimp_memsize_entry_class_init (GimpMemsizeEntryClass *klass)
74 {
75   GObjectClass *object_class = G_OBJECT_CLASS (klass);
76 
77   object_class->finalize = gimp_memsize_entry_finalize;
78 
79   klass->value_changed   = NULL;
80 
81   gimp_memsize_entry_signals[VALUE_CHANGED] =
82     g_signal_new ("value-changed",
83                   G_TYPE_FROM_CLASS (klass),
84                   G_SIGNAL_RUN_FIRST,
85                   G_STRUCT_OFFSET (GimpMemsizeEntryClass, value_changed),
86                   NULL, NULL,
87                   g_cclosure_marshal_VOID__VOID,
88                   G_TYPE_NONE, 0);
89 }
90 
91 static void
gimp_memsize_entry_init(GimpMemsizeEntry * entry)92 gimp_memsize_entry_init (GimpMemsizeEntry *entry)
93 {
94   gtk_orientable_set_orientation (GTK_ORIENTABLE (entry),
95                                   GTK_ORIENTATION_HORIZONTAL);
96 
97   gtk_box_set_spacing (GTK_BOX (entry), 4);
98 
99   entry->value      = 0;
100   entry->lower      = 0;
101   entry->upper      = 0;
102   entry->shift      = 0;
103   entry->adjustment = NULL;
104   entry->menu       = NULL;
105 }
106 
107 static void
gimp_memsize_entry_finalize(GObject * object)108 gimp_memsize_entry_finalize (GObject *object)
109 {
110   GimpMemsizeEntry *entry = (GimpMemsizeEntry *) object;
111 
112   g_clear_object (&entry->adjustment);
113 
114   G_OBJECT_CLASS (parent_class)->finalize (object);
115 }
116 
117 static void
gimp_memsize_entry_adj_callback(GtkAdjustment * adj,GimpMemsizeEntry * entry)118 gimp_memsize_entry_adj_callback (GtkAdjustment    *adj,
119                                  GimpMemsizeEntry *entry)
120 {
121   guint64 size = gtk_adjustment_get_value (adj);
122 
123   if (gimp_memsize_entry_get_rounded_value (entry, entry->value) != size)
124     /* Do not allow losing accuracy if the converted/displayed value
125      * stays the same.
126      */
127     entry->value = size << entry->shift;
128 
129   g_signal_emit (entry, gimp_memsize_entry_signals[VALUE_CHANGED], 0);
130 }
131 
132 static void
gimp_memsize_entry_unit_callback(GtkWidget * widget,GimpMemsizeEntry * entry)133 gimp_memsize_entry_unit_callback (GtkWidget        *widget,
134                                   GimpMemsizeEntry *entry)
135 {
136   guint  shift;
137 
138   gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), (gint *) &shift);
139 
140 #if _MSC_VER < 1300
141 #  define CAST (gint64)
142 #else
143 #  define CAST
144 #endif
145 
146   if (shift != entry->shift)
147     {
148       entry->shift = shift;
149 
150       gtk_adjustment_configure (entry->adjustment,
151                                 gimp_memsize_entry_get_rounded_value (entry, entry->value),
152                                 CAST entry->lower >> shift,
153                                 CAST entry->upper >> shift,
154                                 gtk_adjustment_get_step_increment (entry->adjustment),
155                                 gtk_adjustment_get_page_increment (entry->adjustment),
156                                 gtk_adjustment_get_page_size (entry->adjustment));
157     }
158 
159 #undef CAST
160 }
161 
162 /**
163  * gimp_memsize_entry_get_rounded_value:
164  * @entry: #GimpMemsizeEntry whose set unit is used.
165  * @value: value to convert to @entry unit, and rounded.
166  *
167  * Returns: the proper integer value to be displayed for current unit.
168  *          This value has been appropriately rounded to the nearest
169  *          integer, away from zero.
170  */
171 static guint64
gimp_memsize_entry_get_rounded_value(GimpMemsizeEntry * entry,guint64 value)172 gimp_memsize_entry_get_rounded_value (GimpMemsizeEntry *entry,
173                                       guint64           value)
174 {
175   guint64 converted;
176 
177 #if _MSC_VER < 1300
178 #  define CAST (gint64)
179 #else
180 #  define CAST
181 #endif
182 
183   converted = (CAST value >> entry->shift) +
184               ((CAST entry->value >> (entry->shift - 1)) & 1);
185 
186 #undef CAST
187 
188   return converted;
189 }
190 
191 
192 /**
193  * gimp_memsize_entry_new:
194  * @value: the initial value (in Bytes)
195  * @lower: the lower limit for the value (in Bytes)
196  * @upper: the upper limit for the value (in Bytes)
197  *
198  * Creates a new #GimpMemsizeEntry which is a #GtkHBox with a #GtkSpinButton
199  * and a #GtkOptionMenu all setup to allow the user to enter memory sizes.
200  *
201  * Returns: Pointer to the new #GimpMemsizeEntry.
202  **/
203 GtkWidget *
gimp_memsize_entry_new(guint64 value,guint64 lower,guint64 upper)204 gimp_memsize_entry_new (guint64  value,
205                         guint64  lower,
206                         guint64  upper)
207 {
208   GimpMemsizeEntry *entry;
209   GtkAdjustment    *adj;
210   guint             shift;
211 
212 #if _MSC_VER < 1300
213 #  define CAST (gint64)
214 #else
215 #  define CAST
216 #endif
217 
218   g_return_val_if_fail (value >= lower && value <= upper, NULL);
219 
220   entry = g_object_new (GIMP_TYPE_MEMSIZE_ENTRY, NULL);
221 
222   for (shift = 30; shift > 10; shift -= 10)
223     {
224       if (value > (G_GUINT64_CONSTANT (1) << shift) &&
225           value % (G_GUINT64_CONSTANT (1) << shift) == 0)
226         break;
227     }
228 
229   entry->value = value;
230   entry->lower = lower;
231   entry->upper = upper;
232   entry->shift = shift;
233 
234   adj = (GtkAdjustment *) gtk_adjustment_new (gimp_memsize_entry_get_rounded_value (entry,
235                                                                                     entry->value),
236                                               CAST (lower >> shift),
237                                               CAST (upper >> shift),
238                                               1, 8, 0);
239 
240   entry->spinbutton = gimp_spin_button_new (adj, 1.0, 0);
241   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (entry->spinbutton), TRUE);
242 
243 #undef CAST
244 
245   entry->adjustment = GTK_ADJUSTMENT (adj);
246   g_object_ref_sink (entry->adjustment);
247 
248   gtk_entry_set_width_chars (GTK_ENTRY (entry->spinbutton), 7);
249   gtk_box_pack_start (GTK_BOX (entry), entry->spinbutton, FALSE, FALSE, 0);
250   gtk_widget_show (entry->spinbutton);
251 
252   g_signal_connect (entry->adjustment, "value-changed",
253                     G_CALLBACK (gimp_memsize_entry_adj_callback),
254                     entry);
255 
256   entry->menu = gimp_int_combo_box_new (_("Kibibyte"), 10,
257                                         _("Mebibyte"), 20,
258                                         _("Gibibyte"), 30,
259                                         NULL);
260 
261   gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (entry->menu), shift);
262 
263   g_signal_connect (entry->menu, "changed",
264                     G_CALLBACK (gimp_memsize_entry_unit_callback),
265                     entry);
266 
267   gtk_box_pack_start (GTK_BOX (entry), entry->menu, FALSE, FALSE, 0);
268   gtk_widget_show (entry->menu);
269 
270   return GTK_WIDGET (entry);
271 }
272 
273 /**
274  * gimp_memsize_entry_set_value:
275  * @entry: a #GimpMemsizeEntry
276  * @value: the new value (in Bytes)
277  *
278  * Sets the @entry's value. Please note that the #GimpMemsizeEntry rounds
279  * the value to full Kilobytes.
280  **/
281 void
gimp_memsize_entry_set_value(GimpMemsizeEntry * entry,guint64 value)282 gimp_memsize_entry_set_value (GimpMemsizeEntry *entry,
283                               guint64           value)
284 {
285   guint shift;
286 
287   g_return_if_fail (GIMP_IS_MEMSIZE_ENTRY (entry));
288   g_return_if_fail (value >= entry->lower && value <= entry->upper);
289 
290   for (shift = 30; shift > 10; shift -= 10)
291     {
292       if (value > (G_GUINT64_CONSTANT (1) << shift) &&
293           value % (G_GUINT64_CONSTANT (1) << shift) == 0)
294         break;
295     }
296 
297   if (shift != entry->shift)
298     gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (entry->menu), shift);
299 
300   gtk_adjustment_set_value (entry->adjustment,
301                             (gdouble) gimp_memsize_entry_get_rounded_value (entry, value));
302 
303 #undef CASE
304 }
305 
306 /**
307  * gimp_memsize_entry_get_value:
308  * @entry: a #GimpMemsizeEntry
309  *
310  * Retrieves the current value from a #GimpMemsizeEntry.
311  *
312  * Returns: the current value of @entry (in Bytes).
313  **/
314 guint64
gimp_memsize_entry_get_value(GimpMemsizeEntry * entry)315 gimp_memsize_entry_get_value (GimpMemsizeEntry *entry)
316 {
317   g_return_val_if_fail (GIMP_IS_MEMSIZE_ENTRY (entry), 0);
318 
319   return entry->value;
320 }
321