Code

Correct calligraphic toggles getting out of sync with behavior.
[inkscape.git] / src / dialogs / input.cpp
1 /** @file
2  * @brief Extended input devices dialog
3  */
4 /* Authors:
5  *   Nicklas Lindgren <nili@lysator.liu.se>
6  *   Johan Engelen <goejendaagh@zonnet.nl>
7  *
8  * Copyright (C) 2005-2006 Authors
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #ifdef HAVE_CONFIG_H
14 #   include <config.h>
15 #endif
17 #include <gtk/gtksignal.h>
18 #include <gtk/gtkinputdialog.h>
19 #include <glibmm/ustring.h>
20 #include <list>
21 #include <set>
23 #include "macros.h"
24 #include "verbs.h"
25 #include "inkscape.h"
26 #include "interface.h"
27 #include "xml/repr.h"
29 #include "dialogs/dialog-events.h"
30 #include "preferences.h"
32 #define MIN_ONSCREEN_DISTANCE 50
34 static GtkWidget *dlg = NULL;
35 static win_data wd;
37 // impossible original values to make sure they are read from prefs
38 static gint x = -1000, y = -1000, w = 0, h = 0;
39 static Glib::ustring const prefs_path = "/dialogs/input/";
41 #define noTEST_WITH_GOOD_TABLET 1
42 #define noTEST_WITH_BAD_TABLET 1
44 #if defined(TEST_WITH_GOOD_TABLET) || defined(TEST_WITH_BAD_TABLET)
45 static int testDeviceCount = 0;
46 static GdkDevice* testDevices = 0;
48 // Defined at the end of the file to keep debugging out of the way.
49 static void initTestDevices();
50 #endif
52 static std::list<GdkDevice *> getInputDevices()
53 {
54     std::list<GdkDevice*> devices;
56 #if defined(TEST_WITH_GOOD_TABLET) || defined(TEST_WITH_BAD_TABLET)
57     initTestDevices();
58     for (int i = 0; i < testDeviceCount; i++) {
59         devices.push_back(&testDevices[i]);
60     }
61 #else
62     for (GList *ptr = gdk_devices_list(); ptr; ptr = ptr->next) {
63         GdkDevice *device = static_cast<GdkDevice *>(ptr->data);
64         devices.push_back(device);
65     }
66 #endif
68     return devices;
69 }
71 // wrap these GDK calls to be able to intercept for testing.
73 static bool setDeviceMode( GdkDevice *device, GdkInputMode mode )
74 {
75 #if defined(TEST_WITH_GOOD_TABLET) || defined(TEST_WITH_BAD_TABLET)
76     (void)device;
77     (void)mode;
78     bool retVal = true; // Can't let the Gdk call be called with bad data
79 #else
80     bool retVal = gdk_device_set_mode(device, mode);
81 #endif
82     return retVal;
83 }
85 static void setDeviceAxisUse( GdkDevice *device, guint index, GdkAxisUse use )
86 {
87 #if defined(TEST_WITH_GOOD_TABLET) && !defined(TEST_WITH_BAD_TABLET)
88     (void)device;
89     (void)index;
90     (void)use;
91 #else
92     gdk_device_set_axis_use(device, index, use);
93 #endif
94 }
96 static void setDeviceKey( GdkDevice* device, guint index, guint keyval, GdkModifierType modifiers )
97 {
98 #if defined(TEST_WITH_GOOD_TABLET) && !defined(TEST_WITH_BAD_TABLET)
99     (void)device;
100     (void)index;
101     (void)keyval;
102     (void)modifiers;
103 #else
104     gdk_device_set_key(device, index, keyval, modifiers);
105 #endif
109 static void
110 sp_input_dialog_destroy (GtkObject */*object*/, gpointer /*data*/)
112     sp_signal_disconnect_by_data (INKSCAPE, dlg);
113     wd.win = dlg = NULL;
114     wd.stop = 0;
117 static gboolean
118 sp_input_dialog_delete (GtkObject */*object*/, GdkEvent */*event*/, gpointer /*data*/)
120     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
121     gtk_window_get_position ((GtkWindow *) dlg, &x, &y);
122     gtk_window_get_size ((GtkWindow *) dlg, &w, &h);
124     if (x<0) x=0;
125     if (y<0) y=0;
127     prefs->setInt(prefs_path + "x", x);
128     prefs->setInt(prefs_path + "y", y);
129     prefs->setInt(prefs_path + "w", w);
130     prefs->setInt(prefs_path + "h", h);
132     return FALSE; // which means, go ahead and destroy it
136 static gchar const *axis_use_strings[GDK_AXIS_LAST] = {
137     "ignore", "x", "y", "pressure", "xtilt", "ytilt", "wheel"
138 };
140 static const int RUNAWAY_MAX = 1000;
142 static Glib::ustring getBaseDeviceName(GdkInputSource source)
144     Glib::ustring name;
145     switch (source) {
146         case GDK_SOURCE_MOUSE:
147             name ="pointer";
148             break;
149         case GDK_SOURCE_PEN:
150             name ="pen";
151             break;
152         case GDK_SOURCE_ERASER:
153             name ="eraser";
154             break;
155         case GDK_SOURCE_CURSOR:
156             name ="cursor";
157             break;
158         default:
159             name = "tablet";
160     }
161     return name;
164 static Glib::ustring createSanitizedPath(GdkDevice* device, std::set<Glib::ustring> &seenPaths)
166     // LP #334800: tablet device names on Windows sometimes contain funny junk like
167     // \x03, \xf2, etc. Moreover this junk changes between runs.
168     // If the tablet name contains unprintable or non-ASCII characters,
169     // we use some default name.
170     // This might break if someone has two tablets with broken names, but it's
171     // not possible to do anything 100% correct then.
172     bool broken = false;
174     if (!device->name || (*(device->name) == 0)) {
175         broken = true;
176     } else {
177         for (gchar const *s = device->name; *s; ++s) {
178             if ((*s < 0x20) || (*s >= 0x7f)) {
179                 broken = true;
180                 break;
181             }
182         }
183     }
185     Glib::ustring device_path;
186     if (broken) {
187         Glib::ustring base = Glib::ustring("/devices/") + getBaseDeviceName(device->source);
188         int num = 1;
189         device_path = base;
190         while ((seenPaths.find(device_path) != seenPaths.end()) && (num < RUNAWAY_MAX)) {
191             device_path = Glib::ustring::compose("%1%2", base, ++num);
192         }
193     } else {
194         device_path += Glib::ustring("/devices/") + device->name;
195     }
197     seenPaths.insert(device_path);
199     return device_path;
202 void sp_input_load_from_preferences(void)
204     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
206     std::list<GdkDevice *> devices = getInputDevices();
207     std::set<Glib::ustring> seenPaths;
208     for (std::list<GdkDevice *>::iterator it = devices.begin(); it != devices.end(); ++it) {
209         GdkDevice *device = *it;
211 //         g_message("    s:%d m:%d hc:%d a:%d k:%d [%s]", device->source, device->mode, device->has_cursor, device->num_axes, device->num_keys, device->name);
212 //         for (int i = 0; i < device->num_axes; i++) {
213 //             GdkDeviceAxis &axis = device->axes[i];
214 //             g_message("        axis[%d] u:%d  min:%f max:%f", i, axis.use, axis.min, axis.max);
215 //         }
217         Glib::ustring device_path = createSanitizedPath(device, seenPaths);
218 //         if (device_path != (Glib::ustring("/devices/") + device->name)) {
219 //             g_message("        re-name [%s]", device_path.c_str());
220 //         }
222         Glib::ustring device_mode = prefs->getString(device_path + "/mode");
224         GdkInputMode mode = GDK_MODE_DISABLED;
225         if (device_mode == "screen") {
226             mode = GDK_MODE_SCREEN;
227         } else if (device_mode == "window") {
228             mode = GDK_MODE_WINDOW;
229         }
231         if (device->mode != mode) {
232             setDeviceMode(device, mode);
233         }
235         Glib::ustring::size_type pos0, pos1;
236         GdkAxisUse axis_use;
238         //temp_ptr = repr->attribute("axes");
239         Glib::ustring const axes_str = prefs->getString(device_path + "/axes");
240         pos0 = pos1 = 0;
241         for (gint i=0; i < device->num_axes; i++) {
242             pos1 = axes_str.find(';', pos0);
243             if (pos1 == Glib::ustring::npos) {
244                 break;  // Too few axis specifications
245             }
247             axis_use = GDK_AXIS_IGNORE;
248             for (gint j=0; j < GDK_AXIS_LAST; j++) {
249                 if (!strcmp(axes_str.substr(pos0, pos1-pos0).c_str(), axis_use_strings[j])) {
250                     axis_use = static_cast<GdkAxisUse>(j);
251                     break;
252                 }
253             }
254             setDeviceAxisUse(device, i, axis_use);
255             pos0 = pos1 + 1;
256         }
258         guint keyval;
259         GdkModifierType modifier;
261         Glib::ustring const keys_str = prefs->getString(device_path + "/keys");
262         pos0 = pos1 = 0;
263         for (gint i=0; i < device->num_keys; i++) {
264             pos1 = keys_str.find(';', pos0);
265             if (pos1 == Glib::ustring::npos) {
266                 break;  // Too few key specifications
267             }
269             gtk_accelerator_parse(keys_str.substr(pos0, pos1-pos0).c_str(), &keyval, &modifier);
270             setDeviceKey(device, i, keyval, modifier);
271             pos0 = pos1 + 1;
272         }
273     }
276 void sp_input_save_to_preferences(void)
278     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
280     std::list<GdkDevice *> devices = getInputDevices();
281     std::set<Glib::ustring> seenPaths;
282     for (std::list<GdkDevice *>::iterator it = devices.begin(); it != devices.end(); ++it) {
283         GdkDevice *device = *it;
285         Glib::ustring device_path = createSanitizedPath(device, seenPaths);
287         switch (device->mode) {
288             default:
289             case GDK_MODE_DISABLED: {
290                 prefs->setString(device_path + "/mode", "disabled");
291                 break;
292             }
293             case GDK_MODE_SCREEN: {
294                 prefs->setString(device_path + "/mode", "screen");
295                 break;
296             }
297             case GDK_MODE_WINDOW: {
298                 prefs->setString(device_path + "/mode", "window");
299                 break;
300             }
301         }
303         Glib::ustring temp_attribute = "";
304         for (gint i=0; i < device->num_axes; i++) {
305             temp_attribute += axis_use_strings[device->axes[i].use];
306             temp_attribute += ";";
307         }
308         prefs->setString(device_path + "/axes", temp_attribute);
310         temp_attribute = "";
311         for (gint i=0; i < device->num_keys; i++) {
312             temp_attribute += gtk_accelerator_name(device->keys[i].keyval, device->keys[i].modifiers);
313             temp_attribute += ";";
314         }
315         prefs->setString(device_path + "/keys", temp_attribute);
316     }
319 static void
320 sp_input_save_button (GtkObject */*object*/, gpointer /*data*/)
322     sp_input_save_to_preferences();
325 void
326 sp_input_dialog (void)
328     if (dlg == NULL) {
329         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
331         gchar title[500];
332         sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_INPUT), title);
334         dlg = gtk_input_dialog_new();
336         if (x == -1000 || y == -1000) {
337             x = prefs->getInt(prefs_path + "x", -1000);
338             y = prefs->getInt(prefs_path + "y", -1000);
339         }
341         if (w ==0 || h == 0) {
342             w = prefs->getInt(prefs_path + "w", 0);
343             h = prefs->getInt(prefs_path + "h", 0);
344         }
346 //        if (x<0) x=0;
347 //        if (y<0) y=0;
349         if (w && h) {
350             gtk_window_resize ((GtkWindow *) dlg, w, h);
351         }
352         if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
353             gtk_window_move ((GtkWindow *) dlg, x, y);
354         } else {
355             gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
356         }
359         sp_transientize (dlg);
360         wd.win = dlg;
361         wd.stop = 0;
363         g_signal_connect   ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd);
364         gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg);
365         gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_input_dialog_destroy), dlg);
366         gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_input_dialog_delete), dlg);
367         g_signal_connect   ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_input_dialog_delete), dlg);
368         g_signal_connect   ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg);
369         g_signal_connect   ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg);
371         // Dialog-specific stuff
372         gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(dlg)->close_button),
373                                    "clicked",
374                                    (GtkSignalFunc)gtk_widget_destroy,
375                                    GTK_OBJECT(dlg));
376         gtk_signal_connect (GTK_OBJECT(GTK_INPUT_DIALOG(dlg)->save_button),
377                             "clicked",
378                             (GtkSignalFunc)sp_input_save_button, NULL);
379     }
381     gtk_window_present ((GtkWindow *) dlg);
384 // /////////////////////////////////
385 // For debugging:
386 // /////////////////////////////////
389 #if defined(TEST_WITH_GOOD_TABLET)
390 static void initTestDevices()
392     static bool init = false;
393     if (!init) {
394         static GdkDevice devs[5] = {};
395         int i = 0; // use variable instead of constant to allow devices to be moved around, commented out, etc.
397         {
398             // Laptop trackpad
399             devs[i].name = g_strdup("pointer");
400             devs[i].source = GDK_SOURCE_MOUSE;
401             devs[i].mode = GDK_MODE_DISABLED;
402             devs[i].has_cursor = 0;
403             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
404                                           {GDK_AXIS_Y, 0, 0}};
405             devs[i].num_axes = G_N_ELEMENTS(tmp);
406             devs[i].axes = tmp;
407             devs[i].num_keys = 0;
408             devs[i].keys = 0;
409             i++;
410         }
412         {
413             // Tablet stylus
414             devs[i].name = g_strdup("pen");
415             devs[i].source = GDK_SOURCE_PEN;
416             devs[i].mode = GDK_MODE_DISABLED;
417             devs[i].has_cursor = 0;
418             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
419                                           {GDK_AXIS_Y, 0, 0},
420                                           {GDK_AXIS_PRESSURE, 0, 1},
421                                           {GDK_AXIS_XTILT, -1, 1},
422                                           {GDK_AXIS_YTILT, -1, 1}};
423             devs[i].num_axes = G_N_ELEMENTS(tmp);
424             devs[i].axes = tmp;
425             devs[i].num_keys = 0;
426             devs[i].keys = 0;
427             i++;
428         }
430         {
431             // Puck
432             devs[i].name = g_strdup("cursor");
433             devs[i].source = GDK_SOURCE_CURSOR;
434             devs[i].mode = GDK_MODE_DISABLED;
435             devs[i].has_cursor = 0;
436             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
437                                           {GDK_AXIS_Y, 0, 0},
438                                           {GDK_AXIS_PRESSURE, 0, 1},
439                                           {GDK_AXIS_XTILT, -1, 1},
440                                           {GDK_AXIS_YTILT, -1, 1}};
441             devs[i].num_axes = G_N_ELEMENTS(tmp);
442             devs[i].axes = tmp;
443             devs[i].num_keys = 0;
444             devs[i].keys = 0;
445             i++;
446         }
448         {
449             // Back of tablet stylus
450             devs[i].name = g_strdup("eraser");
451             devs[i].source = GDK_SOURCE_ERASER;
452             devs[i].mode = GDK_MODE_DISABLED;
453             devs[i].has_cursor = 0;
454             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
455                                           {GDK_AXIS_Y, 0, 0},
456                                           {GDK_AXIS_PRESSURE, 0, 1},
457                                           {GDK_AXIS_XTILT, -1, 1},
458                                           {GDK_AXIS_YTILT, -1, 1}};
459             devs[i].num_axes = G_N_ELEMENTS(tmp);
460             devs[i].axes = tmp;
461             devs[i].num_keys = 0;
462             devs[i].keys = 0;
463             i++;
464         }
466         {
467             // Main (composit) mouse device
468             devs[i].name = g_strdup("Core Pointer");
469             devs[i].source = GDK_SOURCE_MOUSE;
470             devs[i].mode = GDK_MODE_SCREEN;
471             devs[i].has_cursor = 1;
472             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
473                                           {GDK_AXIS_Y, 0, 0}};
474             devs[i].num_axes = G_N_ELEMENTS(tmp);
475             devs[i].axes = tmp;
476             devs[i].num_keys = 0;
477             devs[i].keys = 0;
478             i++;
479         }
481         testDeviceCount = i;
482         testDevices = devs;
483         init = true;
484     }
486 #elif defined(TEST_WITH_BAD_TABLET)
488 /**
489  * Uses the current time in seconds to change a name to be unique from one
490  * run of the program to the next.
491  */
492 void perturbName(gchar *str)
494     if (str) {
495         GTimeVal when = {0,0};
496         g_get_current_time(&when);
497         gchar *tmp = g_strdup_printf("%ld", when.tv_sec);
499         size_t partLen = strlen(tmp);
500         size_t len = strlen(str);
501         if (len > (partLen + 4)) {
502             size_t pos = (len - partLen) / 2;
503             for (size_t i = 0; i < partLen; i++) {
504                 str[pos + i] = tmp[i];
505             }
506         }
507         g_free(tmp);
508     }
511 static void initTestDevices()
513     static bool init = false;
514     if (!init) {
515         static GdkDevice devs[5] = {};
516         int i = 0; // use variable instead of constant to allow devices to be moved around, commented out, etc.
518         {
519             // Main (composit) mouse device
520             devs[i].name = g_strdup("Core Pointer");
521             devs[i].source = GDK_SOURCE_MOUSE;
522             devs[i].mode = GDK_MODE_SCREEN;
523             devs[i].has_cursor = 1;
524             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
525                                           {GDK_AXIS_Y, 0, 0}};
526             devs[i].num_axes = G_N_ELEMENTS(tmp);
527             devs[i].axes = tmp;
528             devs[i].num_keys = 0;
529             devs[i].keys = 0;
530             i++;
531         }
533         {
534             // Back of tablet stylus
535             devs[i].name = g_strdup("\346\205\227\347\221\254\347\201\257\345\220\240\346\211\241\346\225\254t\303\265\006 \347\211\220\347\215\245\347\225\263\346\225\262\345\214\240\347\245\264\347\225\254s\357\227\230#\354\234\274C\356\232\210\307\255\350\271\214\310\201\350\222\200\310\201\356\202\250\310\200\350\223\260\310\201\356\202\250\310\200");
536             perturbName(devs[i].name);
537             devs[i].source = GDK_SOURCE_ERASER;
538             devs[i].mode = GDK_MODE_DISABLED;
539             devs[i].has_cursor = 0;
540             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
541                                           {GDK_AXIS_Y, 0, 0},
542                                           {GDK_AXIS_PRESSURE, 0, 1},
543                                           {GDK_AXIS_XTILT, -1, 1},
544                                           {GDK_AXIS_YTILT, -1, 1}};
545             devs[i].num_axes = G_N_ELEMENTS(tmp);
546             devs[i].axes = tmp;
547             devs[i].num_keys = 0;
548             devs[i].keys = 0;
549             i++;
550         }
552         {
553             // Tablet stylus
554             devs[i].name = g_strdup("\346\205\227\347\221\254\347\201\257\345\220\240\346\211\241\346\225\254t\303\265\006 \347\211\220\347\215\245\347\225\263\346\225\262\345\214\240\347\245\264\347\225\254s\357\227\230#\354\234\274C\341\221\230\307\255\343\277\214\310\202\343\230\200\310\202\331\270\310\202\343\231\260\310\202\331\270\310\202");
555             perturbName(devs[i].name);
556             devs[i].source = GDK_SOURCE_PEN;
557             devs[i].mode = GDK_MODE_DISABLED;
558             devs[i].has_cursor = 0;
559             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
560                                           {GDK_AXIS_Y, 0, 0},
561                                           {GDK_AXIS_PRESSURE, 0, 1},
562                                           {GDK_AXIS_XTILT, -1, 1},
563                                           {GDK_AXIS_YTILT, -1, 1}};
564             devs[i].num_axes = G_N_ELEMENTS(tmp);
565             devs[i].axes = tmp;
566             devs[i].num_keys = 0;
567             devs[i].keys = 0;
568             i++;
569         }
571         {
572             // Tablet stylus
573             devs[i].name = g_strdup("\346\205\227\347\221\254\347\201\257\345\220\240\346\211\241\346\225\254t\303\265\006 \347\211\220\347\215\245\347\225\263\346\225\262\345\214\240\347\245\264\347\225\254s\357\227\230#\354\234\274C\341\221\230\307\255\343\277\214\310\202\343\230\200\310\202\331\270\310\202\343\231\260\310\202\331\270\310\202");
574             perturbName(devs[i].name);
575             devs[i].source = GDK_SOURCE_PEN;
576             devs[i].mode = GDK_MODE_DISABLED;
577             devs[i].has_cursor = 0;
578             static GdkDeviceAxis tmp[] = {{GDK_AXIS_X, 0, 0},
579                                           {GDK_AXIS_Y, 0, 0},
580                                           {GDK_AXIS_PRESSURE, 0, 1},
581                                           {GDK_AXIS_XTILT, -1, 1},
582                                           {GDK_AXIS_YTILT, -1, 1}};
583             devs[i].num_axes = G_N_ELEMENTS(tmp);
584             devs[i].axes = tmp;
585             devs[i].num_keys = 0;
586             devs[i].keys = 0;
587             i++;
588         }
590         testDeviceCount = i;
591         testDevices = devs;
592         init = true;
593     }
595 #endif
598 /*
599   Local Variables:
600   mode:c++
601   c-file-style:"stroustrup"
602   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
603   indent-tabs-mode:nil
604   fill-column:99
605   End:
606 */
607 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :