gimpscalecombobox.c 13.8 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3 4
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 11 12 13 14 15 16 17
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 20 21 22
 */

#include "config.h"

23 24
#include "stdlib.h"

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 33
#include "display-types.h"

34 35
#include "core/gimpmarshal.h"

36 37 38
#include "gimpscalecombobox.h"


39
#define MAX_ITEMS  10
40 41 42

enum
{
43 44 45 46
  COLUMN_SCALE,
  COLUMN_LABEL,
  COLUMN_PERSISTENT,
  N_COLUMNS
47 48
};

49 50 51 52 53 54
enum
{
  ENTRY_ACTIVATED,
  LAST_SIGNAL
};

55

56
static void      gimp_scale_combo_box_constructed     (GObject           *object);
57
static void      gimp_scale_combo_box_finalize        (GObject           *object);
58

59 60 61 62 63 64
static void      gimp_scale_combo_box_changed         (GimpScaleComboBox *combo_box);
static void      gimp_scale_combo_box_entry_activate  (GtkWidget         *entry,
                                                       GimpScaleComboBox *combo_box);
static gboolean  gimp_scale_combo_box_entry_key_press (GtkWidget         *entry,
                                                       GdkEventKey       *event,
                                                       GimpScaleComboBox *combo_box);
65

66 67 68 69
static void      gimp_scale_combo_box_scale_iter_set  (GtkListStore      *store,
                                                       GtkTreeIter       *iter,
                                                       gdouble            scale,
                                                       gboolean           persistent);
70 71


72
G_DEFINE_TYPE (GimpScaleComboBox, gimp_scale_combo_box,
73
               GTK_TYPE_COMBO_BOX)
74

75
#define parent_class gimp_scale_combo_box_parent_class
76

77 78
static guint scale_combo_box_signals[LAST_SIGNAL] = { 0 };

79 80 81 82

static void
gimp_scale_combo_box_class_init (GimpScaleComboBoxClass *klass)
{
83
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
84

85 86 87 88 89 90 91 92 93
  scale_combo_box_signals[ENTRY_ACTIVATED] =
    g_signal_new ("entry-activated",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpScaleComboBoxClass, entry_activated),
                  NULL, NULL,
                  gimp_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

94 95
  object_class->constructed = gimp_scale_combo_box_constructed;
  object_class->finalize    = gimp_scale_combo_box_finalize;
96 97 98 99 100
}

static void
gimp_scale_combo_box_init (GimpScaleComboBox *combo_box)
{
101
  combo_box->scale     = 1.0;
102
  combo_box->last_path = NULL;
103 104 105 106 107 108 109 110 111 112 113 114 115 116
}

static void
gimp_scale_combo_box_constructed (GObject *object)
{
  GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object);
  GtkWidget         *entry;
  GtkListStore      *store;
  GtkCellLayout     *layout;
  GtkCellRenderer   *cell;
  GtkTreeIter        iter;
  GtkBorder          border = { 0, 0, 0, 0 };
  gint               i;

117
  G_OBJECT_CLASS (parent_class)->constructed (object);
118

119
  store = gtk_list_store_new (N_COLUMNS,
120 121
                              G_TYPE_DOUBLE,    /* SCALE       */
                              G_TYPE_STRING,    /* LABEL       */
122
                              G_TYPE_BOOLEAN);  /* PERSISTENT  */
123 124 125 126

  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
  g_object_unref (store);

127
  gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo_box),
128
                                       COLUMN_LABEL);
129 130 131

  entry = gtk_bin_get_child (GTK_BIN (combo_box));

132 133
  g_object_set (entry,
                "xalign",             1.0,
134
                "width-chars",        5,
135
                "truncate-multiline", TRUE,
Sven Neumann's avatar
Sven Neumann committed
136
                "inner-border",       &border,
137
                NULL);
138

139 140 141 142 143
  layout = GTK_CELL_LAYOUT (combo_box);

  cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
                       "xalign", 1.0,
                       NULL);
144

145
  gtk_cell_layout_clear (layout);
146 147
  gtk_cell_layout_pack_start (layout, cell, TRUE);
  gtk_cell_layout_set_attributes (layout, cell,
148
                                  "text", COLUMN_LABEL,
149 150 151 152 153
                                  NULL);

  for (i = 8; i > 0; i /= 2)
    {
      gtk_list_store_append (store, &iter);
154
      gimp_scale_combo_box_scale_iter_set (store, &iter, i, TRUE);
155 156 157 158 159
    }

  for (i = 2; i <= 8; i *= 2)
    {
      gtk_list_store_append (store, &iter);
160
      gimp_scale_combo_box_scale_iter_set (store, &iter, 1.0 / i, TRUE);
161
    }
162 163 164 165

  g_signal_connect (combo_box, "changed",
                    G_CALLBACK (gimp_scale_combo_box_changed),
                    NULL);
166 167 168 169

  g_signal_connect (entry, "activate",
                    G_CALLBACK (gimp_scale_combo_box_entry_activate),
                    combo_box);
170 171 172
  g_signal_connect (entry, "key-press-event",
                    G_CALLBACK (gimp_scale_combo_box_entry_key_press),
                    combo_box);
173 174 175 176 177 178 179
}

static void
gimp_scale_combo_box_finalize (GObject *object)
{
  GimpScaleComboBox *combo_box = GIMP_SCALE_COMBO_BOX (object);

180 181 182 183 184 185
  if (combo_box->last_path)
    {
      gtk_tree_path_free (combo_box->last_path);
      combo_box->last_path = NULL;
    }

186 187
  if (combo_box->mru)
    {
188 189
      g_list_free_full (combo_box->mru,
                        (GDestroyNotify) gtk_tree_row_reference_free);
190 191 192 193 194 195
      combo_box->mru = NULL;
    }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

196 197 198
static void
gimp_scale_combo_box_changed (GimpScaleComboBox *combo_box)
{
199
  GtkTreeIter iter;
200 201 202 203 204 205 206

  if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter))
    {
      GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
      gdouble       scale;

      gtk_tree_model_get (model, &iter,
207
                          COLUMN_SCALE, &scale,
208
                          -1);
209
      if (scale > 0.0)
210
        {
211 212
          combo_box->scale = scale;

213 214 215 216 217 218 219 220
          if (combo_box->last_path)
            gtk_tree_path_free (combo_box->last_path);

          combo_box->last_path = gtk_tree_model_get_path (model, &iter);
        }
    }
}

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
static gboolean
gimp_scale_combo_box_parse_text (const gchar *text,
                                 gdouble     *scale)
{
  gchar   *end;
  gdouble  left_number;
  gdouble  right_number;

  /* try to parse a number */
  left_number = strtod (text, &end);

  if (end == text)
    return FALSE;
  else
    text = end;

  /* skip over whitespace */
  while (g_unichar_isspace (g_utf8_get_char (text)))
    text = g_utf8_next_char (text);

241
  if (*text == '\0' || *text == '%')
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
    {
      *scale = left_number / 100.0;
      return TRUE;
    }

  /* check for a valid separator */
  if (*text != '/' && *text != ':')
    {
      *scale = left_number;
      return TRUE;
    }

  text = g_utf8_next_char (text);

  /* skip over whitespace */
  while (g_unichar_isspace (g_utf8_get_char (text)))
    text = g_utf8_next_char (text);

  /* try to parse another number */
  right_number = strtod (text, &end);

  if (end == text)
    return FALSE;

  if (right_number == 0.0)
    return FALSE;

  *scale = left_number / right_number;
  return TRUE;
}

static void
274
gimp_scale_combo_box_entry_activate (GtkWidget         *entry,
275 276
                                     GimpScaleComboBox *combo_box)
{
277 278
  const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry));
  gdouble      scale;
279

280 281
  if (gimp_scale_combo_box_parse_text (text, &scale) &&
      scale >= 1.0 / 256.0                           &&
282 283 284 285
      scale <= 256.0)
    {
      gimp_scale_combo_box_set_scale (combo_box, scale);
    }
286 287
  else
    {
288
      gtk_widget_error_bell (entry);
289 290 291

      gimp_scale_combo_box_set_scale (combo_box, combo_box->scale);
    }
292 293

  g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0);
294 295
}

296 297 298 299 300
static gboolean
gimp_scale_combo_box_entry_key_press (GtkWidget         *entry,
                                      GdkEventKey       *event,
                                      GimpScaleComboBox *combo_box)
{
301
  if (event->keyval == GDK_KEY_Escape)
302 303 304 305 306 307 308 309
    {
      gimp_scale_combo_box_set_scale (combo_box, combo_box->scale);

      g_signal_emit (combo_box, scale_combo_box_signals[ENTRY_ACTIVATED], 0);

      return TRUE;
    }

310 311 312
  if (event->keyval == GDK_KEY_Tab    ||
      event->keyval == GDK_KEY_KP_Tab ||
      event->keyval == GDK_KEY_ISO_Left_Tab)
313 314 315 316 317 318
    {
      gimp_scale_combo_box_entry_activate (entry, combo_box);

      return TRUE;
    }

319 320 321
  return FALSE;
}

322 323 324
static void
gimp_scale_combo_box_scale_iter_set (GtkListStore *store,
                                     GtkTreeIter  *iter,
325 326
                                     gdouble       scale,
                                     gboolean      persistent)
327
{
328
  gchar label[32];
329

330 331 332 333 334 335 336 337 338
#ifdef G_OS_WIN32

  /*  use a normal space until pango's windows backend uses harfbuzz,
   *  see bug #735505
   */
#define PERCENT_SPACE " "

#else

339
  /*  use U+2009 THIN SPACE to separate the percent sign from the number */
340 341 342
#define PERCENT_SPACE "\342\200\211"

#endif
343 344 345

  if (scale > 1.0)
    g_snprintf (label, sizeof (label),
346
                "%d" PERCENT_SPACE "%%", (gint) ROUND (100.0 * scale));
347 348
  else
    g_snprintf (label, sizeof (label),
349
                "%.3g" PERCENT_SPACE "%%", 100.0 * scale);
350 351

  gtk_list_store_set (store, iter,
352 353 354
                      COLUMN_SCALE,      scale,
                      COLUMN_LABEL,      label,
                      COLUMN_PERSISTENT, persistent,
355 356 357
                      -1);
}

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
static void
gimp_scale_combo_box_mru_add (GimpScaleComboBox *combo_box,
                              GtkTreeIter       *iter)
{
  GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
  GtkTreePath  *path  = gtk_tree_model_get_path (model, iter);
  GList        *list;
  gboolean      found;

  for (list = combo_box->mru, found = FALSE; list && !found; list = list->next)
    {
      GtkTreePath *this = gtk_tree_row_reference_get_path (list->data);

      if (gtk_tree_path_compare (this, path) == 0)
        {
          if (list->prev)
            {
              combo_box->mru = g_list_remove_link (combo_box->mru, list);
              combo_box->mru = g_list_concat (list, combo_box->mru);
            }

          found = TRUE;
        }

      gtk_tree_path_free (this);
    }

  if (! found)
    combo_box->mru = g_list_prepend (combo_box->mru,
                                     gtk_tree_row_reference_new (model, path));

  gtk_tree_path_free (path);
}

static void
gimp_scale_combo_box_mru_remove_last (GimpScaleComboBox *combo_box)
{
  GtkTreeModel *model;
  GtkTreePath  *path;
  GList        *last;
  GtkTreeIter   iter;

  if (! combo_box->mru)
    return;

  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));

  last = g_list_last (combo_box->mru);
  path = gtk_tree_row_reference_get_path (last->data);

  if (gtk_tree_model_get_iter (model, &iter, path))
    {
      gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
      gtk_tree_row_reference_free (last->data);
      combo_box->mru = g_list_delete_link (combo_box->mru, last);
    }

  gtk_tree_path_free (path);
}


/**
 * gimp_scale_combo_box_new:
 *
 * Return value: a new #GimpScaleComboBox.
 **/
GtkWidget *
gimp_scale_combo_box_new (void)
{
427 428 429
  return g_object_new (GIMP_TYPE_SCALE_COMBO_BOX,
                       "has-entry", TRUE,
                       NULL);
430 431 432 433 434 435 436 437
}

void
gimp_scale_combo_box_set_scale (GimpScaleComboBox *combo_box,
                                gdouble            scale)
{
  GtkTreeModel *model;
  GtkListStore *store;
438
  GtkWidget    *entry;
439 440 441
  GtkTreeIter   iter;
  gboolean      iter_valid;
  gboolean      persistent;
442
  gint          n_digits;
443 444

  g_return_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box));
445
  g_return_if_fail (scale > 0.0);
446 447 448 449 450 451 452 453 454 455 456

  model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box));
  store = GTK_LIST_STORE (model);

  for (iter_valid = gtk_tree_model_get_iter_first (model, &iter);
       iter_valid;
       iter_valid = gtk_tree_model_iter_next (model, &iter))
    {
      gdouble  this;

      gtk_tree_model_get (model, &iter,
457
                          COLUMN_SCALE, &this,
458 459
                          -1);

460
      if (fabs (this - scale) < 0.0001)
461 462 463 464 465 466 467 468 469 470 471
        break;
    }

  if (! iter_valid)
    {
      GtkTreeIter  sibling;

      for (iter_valid = gtk_tree_model_get_iter_first (model, &sibling);
           iter_valid;
           iter_valid = gtk_tree_model_iter_next (model, &sibling))
        {
472
          gdouble this;
473 474

          gtk_tree_model_get (model, &sibling,
475
                              COLUMN_SCALE, &this,
476 477 478 479 480 481
                              -1);

          if (this < scale)
            break;
        }

482
      gtk_list_store_insert_before (store, &iter, iter_valid ? &sibling : NULL);
483
      gimp_scale_combo_box_scale_iter_set (store, &iter, scale, FALSE);
484 485 486 487 488
    }

  gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo_box), &iter);

  gtk_tree_model_get (model, &iter,
489
                      COLUMN_PERSISTENT, &persistent,
490 491 492 493 494 495 496 497
                      -1);
  if (! persistent)
    {
      gimp_scale_combo_box_mru_add (combo_box, &iter);

      if (gtk_tree_model_iter_n_children (model, NULL) > MAX_ITEMS)
        gimp_scale_combo_box_mru_remove_last (combo_box);
    }
498 499 500 501 502 503 504 505

  /* Update entry size appropriately. */
  entry = gtk_bin_get_child (GTK_BIN (combo_box));
  n_digits = (gint) floor (log10 (scale) + 1);

  g_object_set (entry,
                "width-chars", MAX (5, n_digits + 4),
                NULL);
506 507 508 509 510
}

gdouble
gimp_scale_combo_box_get_scale (GimpScaleComboBox *combo_box)
{
511
  g_return_val_if_fail (GIMP_IS_SCALE_COMBO_BOX (combo_box), 1.0);
512

513
  return combo_box->scale;
514
}