Code

Mnemonics in "Input devices", and LPE dialogs (Bug 170765)
[inkscape.git] / src / ui / dialog / input.cpp
1 /** @file
2  * @brief Input devices dialog (new) - implementation
3  */
4 /* Author:
5  *   Jon A. Cruz
6  *
7  * Copyright (C) 2008 Author
8  * Released under GNU GPL.  Read the file 'COPYING' for more information.
9  */
11 #include <map>
12 #include <set>
13 #include <list>
14 #include <glib/gprintf.h>
15 #include <glibmm/i18n.h>
16 #include <gtkmm/alignment.h>
17 #include <gtkmm/cellrenderercombo.h>
18 #include <gtkmm/checkbutton.h>
19 #include <gtkmm/comboboxtext.h>
20 #include <gtkmm/enums.h>
21 #include <gtkmm/eventbox.h>
22 #include <gtkmm/frame.h>
23 #include <gtkmm/image.h>
24 #include <gtkmm/liststore.h>
25 #include <gtkmm/menubar.h>
26 #include <gtkmm/notebook.h>
27 #include <gtkmm/paned.h>
28 #include <gtkmm/progressbar.h>
29 #include <gtkmm/scrolledwindow.h>
30 #include <gtkmm/table.h>
31 #include <gtkmm/treemodel.h>
32 #include <gtkmm/treemodelcolumn.h>
33 #include <gtkmm/treestore.h>
34 #include <gtkmm/treeview.h>
36 #include "device-manager.h"
37 #include "preferences.h"
38 #include "ui/widget/panel.h"
40 #include "input.h"
42 /* XPM */
43 static char const * core_xpm[] = {
44 "16 16 4 1",
45 "       c None",
46 ".      c #808080",
47 "+      c #000000",
48 "@      c #FFFFFF",
49 "                ",
50 "                ",
51 "                ",
52 "    .++++++.    ",
53 "    +@+@@+@+    ",
54 "    +@+@@+@+    ",
55 "    +.+..+.+    ",
56 "    +@@@@@@+    ",
57 "    +@@@@@@+    ",
58 "    +@@@@@@+    ",
59 "    +@@@@@@+    ",
60 "    +@@@@@@+    ",
61 "    .++++++.    ",
62 "                ",
63 "                ",
64 "                "};
66 /* XPM */
67 static char const *eraser[] = {
68 /* columns rows colors chars-per-pixel */
69 "16 16 5 1",
70 "  c black",
71 ". c green",
72 "X c #808080",
73 "o c gray100",
74 "O c None",
75 /* pixels */
76 "OOOOOOOOOOOOOOOO",
77 "OOOOOOOOOOOOO OO",
78 "OOOOOOOOOOOO . O",
79 "OOOOOOOOOOO . OO",
80 "OOOOOOOOOO . OOO",
81 "OOOOOOOOO . OOOO",
82 "OOOOOOOO . OOOOO",
83 "OOOOOOOXo OOOOOO",
84 "OOOOOOXoXOOOOOOO",
85 "OOOOOXoXOOOOOOOO",
86 "OOOOXoXOOOOOOOOO",
87 "OOOXoXOOOOOOOOOO",
88 "OOXoXOOOOOOOOOOO",
89 "OOXXOOOOOOOOOOOO",
90 "OOOOOOOOOOOOOOOO",
91 "OOOOOOOOOOOOOOOO"
92 };
94 /* XPM */
95 static char const *mouse[] = {
96 /* columns rows colors chars-per-pixel */
97 "16 16 3 1",
98 "  c black",
99 ". c gray100",
100 "X c None",
101 /* pixels */
102 "XXXXXXXXXXXXXXXX",
103 "XXXXXXXXXXXXXXXX",
104 "XXXXXXXXXXXXXXXX",
105 "XXXXXXXXXXXXXXXX",
106 "XXXXXXX  XXXXXXX",
107 "XXXXX  . XXXXXXX",
108 "XXXX .... XXXXXX",
109 "XXXX .... XXXXXX",
110 "XXXXX .... XXXXX",
111 "XXXXX .... XXXXX",
112 "XXXXXX .... XXXX",
113 "XXXXXX .... XXXX",
114 "XXXXXXX .  XXXXX",
115 "XXXXXXX  XXXXXXX",
116 "XXXXXXXXXXXXXXXX",
117 "XXXXXXXXXXXXXXXX"
118 };
120 /* XPM */
121 static char const *pen[] = {
122 /* columns rows colors chars-per-pixel */
123 "16 16 3 1",
124 "  c black",
125 ". c gray100",
126 "X c None",
127 /* pixels */
128 "XXXXXXXXXXXXXXXX",
129 "XXXXXXXXXXXXX XX",
130 "XXXXXXXXXXXX . X",
131 "XXXXXXXXXXX . XX",
132 "XXXXXXXXXX . XXX",
133 "XXXXXXXXX . XXXX",
134 "XXXXXXXX . XXXXX",
135 "XXXXXXX . XXXXXX",
136 "XXXXXX . XXXXXXX",
137 "XXXXX . XXXXXXXX",
138 "XXXX . XXXXXXXXX",
139 "XXX . XXXXXXXXXX",
140 "XX . XXXXXXXXXXX",
141 "XX  XXXXXXXXXXXX",
142 "XXXXXXXXXXXXXXXX",
143 "XXXXXXXXXXXXXXXX"
144 };
146 /* XPM */
147 static char const *sidebuttons[] = {
148 /* columns rows colors chars-per-pixel */
149 "16 16 4 1",
150 "  c black",
151 ". c #808080",
152 "o c green",
153 "O c None",
154 /* pixels */
155 "OOOOOOOOOOOOOOOO",
156 "OOOOOOOOOOOOOOOO",
157 "O..............O",
158 "O.OOOOOOOOOOOO.O",
159 "O   OOOOOOOO   O",
160 "O o OOOOOOOO o O",
161 "O o OOOOOOOO o O",
162 "O   OOOOOOOO   O",
163 "O.OOOOOOOOOOOO.O",
164 "O.OOOOOOOOOOOO.O",
165 "O.OOOOOOOOOOOO.O",
166 "O.OOOOOOOOOOOO.O",
167 "O.OOOOOOOOOOOO.O",
168 "O..............O",
169 "OOOOOOOOOOOOOOOO",
170 "OOOOOOOOOOOOOOOO"
171 };
173 /* XPM */
174 static char const *tablet[] = {
175 /* columns rows colors chars-per-pixel */
176 "16 16 3 1",
177 "  c black",
178 ". c gray100",
179 "X c None",
180 /* pixels */
181 "XXXXXXXXXXXXXXXX",
182 "XXXXXXXXXXXXXXXX",
183 "X              X",
184 "X ............ X",
185 "X ............ X",
186 "X ............ X",
187 "X ............ X",
188 "X ............ X",
189 "X ............ X",
190 "X ............ X",
191 "X ............ X",
192 "X ............ X",
193 "X ............ X",
194 "X              X",
195 "XXXXXXXXXXXXXXXX",
196 "XXXXXXXXXXXXXXXX"
197 };
199 /* XPM */
200 static char const *tip[] = {
201 /* columns rows colors chars-per-pixel */
202 "16 16 5 1",
203 "  c black",
204 ". c green",
205 "X c #808080",
206 "o c gray100",
207 "O c None",
208 /* pixels */
209 "OOOOOOOOOOOOOOOO",
210 "OOOOOOOOOOOOOXOO",
211 "OOOOOOOOOOOOXoXO",
212 "OOOOOOOOOOOXoXOO",
213 "OOOOOOOOOOXoXOOO",
214 "OOOOOOOOOXoXOOOO",
215 "OOOOOOOOXoXOOOOO",
216 "OOOOOOO oXOOOOOO",
217 "OOOOOO . OOOOOOO",
218 "OOOOO . OOOOOOOO",
219 "OOOO . OOOOOOOOO",
220 "OOO . OOOOOOOOOO",
221 "OO . OOOOOOOOOOO",
222 "OO  OOOOOOOOOOOO",
223 "OOOOXXXXXOOOOOOO",
224 "OOOOOOOOOXXXXXOO"
225 };
227 /* XPM */
228 static char const *button_none[] = {
229 /* columns rows colors chars-per-pixel */
230 "8 8 3 1",
231 "  c black",
232 ". c #808080",
233 "X c None",
234 /* pixels */
235 "XXXXXXXX",
236 "XX .. XX",
237 "X .XX. X",
238 "X.XX X.X",
239 "X.X XX.X",
240 "X .XX. X",
241 "XX .. XX",
242 "XXXXXXXX"
243 };
244 /* XPM */
245 static char const *button_off[] = {
246 /* columns rows colors chars-per-pixel */
247 "8 8 4 1",
248 "  c black",
249 ". c #808080",
250 "X c gray100",
251 "o c None",
252 /* pixels */
253 "oooooooo",
254 "oo.  .oo",
255 "o. XX .o",
256 "o XXXX o",
257 "o XXXX o",
258 "o. XX .o",
259 "oo.  .oo",
260 "oooooooo"
261 };
262 /* XPM */
263 static char const *button_on[] = {
264 /* columns rows colors chars-per-pixel */
265 "8 8 3 1",
266 "  c black",
267 ". c green",
268 "X c None",
269 /* pixels */
270 "XXXXXXXX",
271 "XX    XX",
272 "X  ..  X",
273 "X .... X",
274 "X .... X",
275 "X  ..  X",
276 "XX    XX",
277 "XXXXXXXX"
278 };
280 /* XPM */
281 static char const * axis_none_xpm[] = {
282 "24 8 3 1",
283 "       c None",
284 ".      c #000000",
285 "+      c #808080",
286 "                        ",
287 "  .++++++++++++++++++.  ",
288 " .+               . .+. ",
289 " +          . . .     + ",
290 " +     . . .          + ",
291 " .+. .               +. ",
292 "  .++++++++++++++++++.  ",
293 "                        "};
294 /* XPM */
295 static char const * axis_off_xpm[] = {
296 "24 8 4 1",
297 "       c None",
298 ".      c #808080",
299 "+      c #000000",
300 "@      c #FFFFFF",
301 "                        ",
302 "  .++++++++++++++++++.  ",
303 " .+@@@@@@@@@@@@@@@@@@+. ",
304 " +@@@@@@@@@@@@@@@@@@@@+ ",
305 " +@@@@@@@@@@@@@@@@@@@@+ ",
306 " .+@@@@@@@@@@@@@@@@@@+. ",
307 "  .++++++++++++++++++.  ",
308 "                        "};
309 /* XPM */
310 static char const * axis_on_xpm[] = {
311 "24 8 3 1",
312 "       c None",
313 ".      c #000000",
314 "+      c #00FF00",
315 "                        ",
316 "  ....................  ",
317 " ..++++++++++++++++++.. ",
318 " .++++++++++++++++++++. ",
319 " .++++++++++++++++++++. ",
320 " ..++++++++++++++++++.. ",
321 "  ....................  ",
322 "                        "};
324 using Inkscape::InputDevice;
326 namespace Inkscape {
327 namespace UI {
328 namespace Dialog {
332 class DeviceModelColumns : public Gtk::TreeModel::ColumnRecord
334 public:
335     Gtk::TreeModelColumn<bool>                         toggler;
336     Gtk::TreeModelColumn<Glib::ustring>                expander;
337     Gtk::TreeModelColumn<Glib::ustring>                description;
338     Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> >   thumbnail;
339     Gtk::TreeModelColumn<Glib::RefPtr<InputDevice const> > device;
340     Gtk::TreeModelColumn<Gdk::InputMode>               mode;
342     DeviceModelColumns() { add(toggler), add(expander), add(description); add(thumbnail); add(device); add(mode); }
343 };
345 static std::map<Gdk::InputMode, Glib::ustring> &getModeToString()
347     static std::map<Gdk::InputMode, Glib::ustring> mapping;
348     if (mapping.empty()) {
349         mapping[Gdk::MODE_DISABLED] = _("Disabled");
350         mapping[Gdk::MODE_SCREEN]   = _("Screen");
351         mapping[Gdk::MODE_WINDOW]   = _("Window");
352     }
354     return mapping;
357 static std::map<Glib::ustring, Gdk::InputMode> &getStringToMode()
359     static std::map<Glib::ustring, Gdk::InputMode> mapping;
360     if (mapping.empty()) {
361         mapping[_("Disabled")] = Gdk::MODE_DISABLED;
362         mapping[_("Screen")]   = Gdk::MODE_SCREEN;
363         mapping[_("Window")]   = Gdk::MODE_WINDOW;
364     }
366     return mapping;
371 class InputDialogImpl : public InputDialog {
372 public:
373     InputDialogImpl();
374     virtual ~InputDialogImpl() {}
376 private:
377     class ConfPanel : public Gtk::VBox
378     {
379     public:
380         ConfPanel();
381         ~ConfPanel();
383         class Blink : public Preferences::Observer
384         {
385         public:
386             Blink(ConfPanel &parent);
387             virtual ~Blink();
388             virtual void notify(Preferences::Entry const &new_val);
390             ConfPanel &parent;
391         };
393         static void commitCellModeChange(Glib::ustring const &path, Glib::ustring const &newText, Glib::RefPtr<Gtk::TreeStore> store);
394         static void setModeCellString(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter);
396         static void commitCellStateChange(Glib::ustring const &path, Glib::RefPtr<Gtk::TreeStore> store);
397         static void setCellStateToggle(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter);
399         void saveSettings();
400         void useExtToggled();
402         Glib::RefPtr<Gtk::TreeStore> store;
403         Gtk::TreeIter tabletIter;
404         Gtk::TreeView tree;
405         Gtk::ScrolledWindow treeScroller;
406         Blink watcher;
407         Gtk::CheckButton useExt;
408         Gtk::Button save;
409     };
411     static DeviceModelColumns &getCols();
413     enum PixId {PIX_CORE, PIX_PEN, PIX_MOUSE, PIX_TIP, PIX_TABLET, PIX_ERASER, PIX_SIDEBUTTONS,
414                 PIX_BUTTONS_NONE, PIX_BUTTONS_ON, PIX_BUTTONS_OFF,
415                 PIX_AXIS_NONE, PIX_AXIS_ON, PIX_AXIS_OFF};
417     static Glib::RefPtr<Gdk::Pixbuf> getPix(PixId id);
419     std::map<Glib::ustring, std::set<guint> > buttonMap;
420     std::map<Glib::ustring, std::map<guint, std::pair<guint, gdouble> > > axesMap;
422     GdkInputSource lastSourceSeen;
423     Glib::ustring lastDevnameSeen;
425     Glib::RefPtr<Gtk::TreeStore> store;
426     Gtk::TreeIter tabletIter;
427     Gtk::TreeView tree;
428     Gtk::Frame frame2;
429     Gtk::Frame testFrame;
430     Gtk::ScrolledWindow treeScroller;
431     Gtk::ScrolledWindow detailScroller;
432     Gtk::HPaned splitter;
433     Gtk::VPaned split2;
434     Gtk::Label devName;
435     Gtk::Label devKeyCount;
436     Gtk::Label devAxesCount;
437     Gtk::ComboBoxText axesCombo;
438     Gtk::ProgressBar axesValues[6];
439     Gtk::ComboBoxText buttonCombo;
440     Gtk::ComboBoxText linkCombo;
441     sigc::connection linkConnection;
442     Gtk::Label keyVal;
443     Gtk::Entry keyEntry;
444     Gtk::Table devDetails;
445     Gtk::Notebook topHolder;
446     Gtk::Image testThumb;
447     Gtk::Image testButtons[24];
448     Gtk::Image testAxes[8];
449     Gtk::Table imageTable;
450     Gtk::EventBox testDetector;
452     ConfPanel cfgPanel;
454     static void setupTree( Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeIter &tablet );
455     void setupValueAndCombo( gint reported, gint actual, Gtk::Label& label, Gtk::ComboBoxText& combo );
456     void updateTestButtons( Glib::ustring const& key, gint hotButton );
457     void updateTestAxes( Glib::ustring const& key, GdkDevice* dev );
458     void mapAxesValues( Glib::ustring const& key, guint numAxes, gdouble const * axes, GdkDevice* dev);
459     Glib::ustring getKeyFor( GdkDevice* device );
460     bool eventSnoop(GdkEvent* event);
461     void linkComboChanged();
462     void resyncToSelection();
463     void handleDeviceChange(Glib::RefPtr<InputDevice const> device);
464     void updateDeviceAxes(Glib::RefPtr<InputDevice const> device);
465     void updateDeviceButtons(Glib::RefPtr<InputDevice const> device);
466     static void updateDeviceLinks(Glib::RefPtr<InputDevice const> device, Gtk::TreeIter tabletIter, Glib::RefPtr<Gtk::TreeView> tree);
468     static bool findDevice(const Gtk::TreeModel::iterator& iter,
469                            Glib::ustring id,
470                            Gtk::TreeModel::iterator* result);
471     static bool findDeviceByLink(const Gtk::TreeModel::iterator& iter,
472                                  Glib::ustring link,
473                                  Gtk::TreeModel::iterator* result);
475 }; // class InputDialogImpl
478 DeviceModelColumns &InputDialogImpl::getCols()
480     static DeviceModelColumns cols;
481     return cols;
484 Glib::RefPtr<Gdk::Pixbuf> InputDialogImpl::getPix(PixId id)
486     static std::map<PixId, Glib::RefPtr<Gdk::Pixbuf> > mappings;
488     mappings[PIX_CORE]          = Gdk::Pixbuf::create_from_xpm_data(core_xpm);
489     mappings[PIX_PEN]           = Gdk::Pixbuf::create_from_xpm_data(pen);
490     mappings[PIX_MOUSE]         = Gdk::Pixbuf::create_from_xpm_data(mouse);
491     mappings[PIX_TIP]           = Gdk::Pixbuf::create_from_xpm_data(tip);
492     mappings[PIX_TABLET]        = Gdk::Pixbuf::create_from_xpm_data(tablet);
493     mappings[PIX_ERASER]        = Gdk::Pixbuf::create_from_xpm_data(eraser);
494     mappings[PIX_SIDEBUTTONS]   = Gdk::Pixbuf::create_from_xpm_data(sidebuttons);
496     mappings[PIX_BUTTONS_NONE]  = Gdk::Pixbuf::create_from_xpm_data(button_none);
497     mappings[PIX_BUTTONS_ON]    = Gdk::Pixbuf::create_from_xpm_data(button_on);
498     mappings[PIX_BUTTONS_OFF]   = Gdk::Pixbuf::create_from_xpm_data(button_off);
500     mappings[PIX_AXIS_NONE]     = Gdk::Pixbuf::create_from_xpm_data(axis_none_xpm);
501     mappings[PIX_AXIS_ON]       = Gdk::Pixbuf::create_from_xpm_data(axis_on_xpm);
502     mappings[PIX_AXIS_OFF]      = Gdk::Pixbuf::create_from_xpm_data(axis_off_xpm);
504     Glib::RefPtr<Gdk::Pixbuf> pix;
505     if (mappings.find(id) != mappings.end()) {
506         pix = mappings[id];
507     }
509     return pix;
513 // Now that we've defined the *Impl class, we can do the method to aquire one.
514 InputDialog &InputDialog::getInstance()
516     InputDialog *dialog = new InputDialogImpl();
517     return *dialog;
521 InputDialogImpl::InputDialogImpl() :
522     InputDialog(),
524     lastSourceSeen((GdkInputSource)-1),
525     lastDevnameSeen(""),
526     store(Gtk::TreeStore::create(getCols())),
527     tabletIter(),
528     tree(store),
529     frame2(),
530     testFrame(_("Test Area")),
531     treeScroller(),
532     detailScroller(),
533     splitter(),
534     split2(),
535     linkCombo(),
536     devDetails(12, 2),
537     topHolder(),
538     imageTable(8, 7),
539     testDetector(),
540     cfgPanel()
542     Gtk::Box *contents = _getContents();
545     treeScroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
546     treeScroller.add(tree);
547     split2.pack1(testFrame);
548     split2.pack2(frame2);
549     splitter.pack1(treeScroller);
550     splitter.pack2(split2);
552     testDetector.add(imageTable);
553     testFrame.add(testDetector);
554     testThumb.set(getPix(PIX_TABLET));
555     testThumb.set_padding(24, 24);
556     imageTable.attach(testThumb, 0, 8, 0, 1, ::Gtk::EXPAND, ::Gtk::EXPAND);
557     {
558         guint col = 0;
559         guint row = 1;
560         for ( guint num = 0; num < G_N_ELEMENTS(testButtons); num++ ) {
561             testButtons[num].set(getPix(PIX_BUTTONS_NONE));
562             imageTable.attach(testButtons[num], col, col + 1, row, row + 1, ::Gtk::FILL, ::Gtk::FILL);
563             col++;
564             if (col > 7) {
565                 col = 0;
566                 row++;
567             }
568         }
570         col = 0;
571         for ( guint num = 0; num < G_N_ELEMENTS(testAxes); num++ ) {
572             testAxes[num].set(getPix(PIX_AXIS_NONE));
573             imageTable.attach(testAxes[num], col * 2, (col + 1) * 2, row, row + 1, ::Gtk::FILL, ::Gtk::FILL);
574             col++;
575             if (col > 3) {
576                 col = 0;
577                 row++;
578             }
579         }
580     }
583     topHolder.append_page(cfgPanel, _("Configuration"));
584     topHolder.append_page(splitter, _("Hardware"));
585     topHolder.show_all();
586     topHolder.set_current_page(0);
588     contents->pack_start(topHolder);
590     int rowNum = 0;
592     Gtk::Label* lbl = Gtk::manage(new Gtk::Label(_("Name:")));
593     devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
594                       ::Gtk::FILL,
595                       ::Gtk::SHRINK);
596     devDetails.attach(devName, 1, 2, rowNum, rowNum + 1,
597                       ::Gtk::SHRINK,
598                       ::Gtk::SHRINK);
600     rowNum++;
602     lbl = Gtk::manage(new Gtk::Label(_("Link:")));
603     devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
604                       ::Gtk::FILL,
605                       ::Gtk::SHRINK);
607     linkCombo.append_text(_("None"));
608     linkCombo.set_active_text(_("None"));
609     linkCombo.set_sensitive(false);
610     linkConnection = linkCombo.signal_changed().connect(sigc::mem_fun(*this, &InputDialogImpl::linkComboChanged));
612     devDetails.attach(linkCombo, 1, 2, rowNum, rowNum + 1,
613                       ::Gtk::FILL,
614                       ::Gtk::SHRINK);
615     rowNum++;
617     lbl = Gtk::manage(new Gtk::Label(_("Axes count:")));
618     devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
619                       ::Gtk::FILL,
620                       ::Gtk::SHRINK);
621     devDetails.attach(devAxesCount, 1, 2, rowNum, rowNum + 1,
622                       ::Gtk::SHRINK,
623                       ::Gtk::SHRINK);
625     rowNum++;
627 /*
628     lbl = Gtk::manage(new Gtk::Label(_("Actual axes count:")));
629     devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
630                       ::Gtk::FILL,
631                       ::Gtk::SHRINK);
632     devDetails.attach(axesCombo, 1, 2, rowNum, rowNum + 1,
633                       ::Gtk::SHRINK,
634                       ::Gtk::SHRINK);
636     rowNum++;
637 */
639     for ( guint barNum = 0; barNum < static_cast<guint>(G_N_ELEMENTS(axesValues)); barNum++ ) {
640         lbl = Gtk::manage(new Gtk::Label(_("axis:")));
641         devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
642                           ::Gtk::FILL,
643                           ::Gtk::SHRINK);
644         devDetails.attach(axesValues[barNum], 1, 2, rowNum, rowNum + 1,
645                           ::Gtk::EXPAND,
646                           ::Gtk::SHRINK);
647         axesValues[barNum].set_sensitive(false);
649         rowNum++;
650     }
652     lbl = Gtk::manage(new Gtk::Label(_("Button count:")));
653     devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
654                       ::Gtk::FILL,
655                       ::Gtk::SHRINK);
656     devDetails.attach(devKeyCount, 1, 2, rowNum, rowNum + 1,
657                       ::Gtk::SHRINK,
658                       ::Gtk::SHRINK);
660     rowNum++;
662 /*
663     lbl = Gtk::manage(new Gtk::Label(_("Actual button count:")));
664     devDetails.attach(*lbl, 0, 1, rowNum, rowNum+ 1,
665                       ::Gtk::FILL,
666                       ::Gtk::SHRINK);
667     devDetails.attach(buttonCombo, 1, 2, rowNum, rowNum + 1,
668                       ::Gtk::SHRINK,
669                       ::Gtk::SHRINK);
671     rowNum++;
672 */
674     devDetails.attach(keyVal, 0, 2, rowNum, rowNum + 1,
675                       ::Gtk::FILL,
676                       ::Gtk::SHRINK);
677     rowNum++;
680     testDetector.signal_event().connect(sigc::mem_fun(*this, &InputDialogImpl::eventSnoop));
682 //     void gdk_input_set_extension_events (GdkWindow        *window,
683 //                                          gint              mask,
684 //                                          GdkExtensionMode  mode);
686     gtk_widget_set_extension_events( GTK_WIDGET(testDetector.gobj()), GDK_EXTENSION_EVENTS_ALL );
687     testDetector.add_events(Gdk::POINTER_MOTION_MASK|Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK |Gdk::PROXIMITY_IN_MASK|Gdk::PROXIMITY_OUT_MASK|Gdk::SCROLL_MASK);
689     devDetails.attach(keyEntry, 0, 2, rowNum, rowNum + 1,
690                       ::Gtk::FILL,
691                       ::Gtk::SHRINK);
692     rowNum++;
695     devDetails.set_sensitive(false);
696     detailScroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
697     detailScroller.add(devDetails);
698     frame2.add(detailScroller);
700 //- 16x16/devices
701 // gnome-dev-mouse-optical
702 // input-mouse
703 // input-tablet
704 // mouse
708     //Add the TreeView's view columns:
709     tree.append_column("I", getCols().thumbnail);
710     tree.append_column("Bar", getCols().description);
712     tree.set_enable_tree_lines();
713     tree.set_headers_visible(false);
714     tree.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &InputDialogImpl::resyncToSelection));
717     setupTree( store, tabletIter );
719     Inkscape::DeviceManager::getManager().signalDeviceChanged().connect(sigc::mem_fun(*this, &InputDialogImpl::handleDeviceChange));
720     Inkscape::DeviceManager::getManager().signalAxesChanged().connect(sigc::mem_fun(*this, &InputDialogImpl::updateDeviceAxes));
721     Inkscape::DeviceManager::getManager().signalButtonsChanged().connect(sigc::mem_fun(*this, &InputDialogImpl::updateDeviceButtons));
722     Glib::RefPtr<Gtk::TreeView> treePtr(&tree);
723     Inkscape::DeviceManager::getManager().signalLinkChanged().connect(sigc::bind(sigc::ptr_fun(&InputDialogImpl::updateDeviceLinks), tabletIter, treePtr));
725     tree.expand_all();
726     show_all_children();
729 class TabletTmp {
730 public:
731     TabletTmp() {}
733     Glib::ustring name;
734     std::list<Glib::RefPtr<InputDevice const> > devices;
735 };
737 static Glib::ustring getCommon( std::list<Glib::ustring> const &names )
739     Glib::ustring result;
741     if ( !names.empty() ) {
742         size_t pos = 0;
743         bool match = true;
744         while ( match ) {
745             if ( names.begin()->length() > pos ) {
746                 gunichar ch = (*names.begin())[pos];
747                 for ( std::list<Glib::ustring>::const_iterator it = names.begin(); it != names.end(); ++it ) {
748                     if ( (pos >= it->length())
749                          || ((*it)[pos] != ch) ) {
750                         match = false;
751                         break;
752                     }
753                 }
754                 if (match) {
755                     result += ch;
756                     pos++;
757                 }
758             } else {
759                 match = false;
760             }
761         }
762     }
764     return result;
767 void InputDialogImpl::setupTree( Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeIter &tablet )
769     std::list<Glib::RefPtr<InputDevice const> > devList = Inkscape::DeviceManager::getManager().getDevices();
770     if ( !devList.empty() ) {
771         Gtk::TreeModel::Row row = *(store->append());
772         row[getCols().description] = _("Hardware");
774         // Let's make some tablets!!!
775         std::list<TabletTmp> tablets;
776         std::set<Glib::ustring> consumed;
778         // Phase 1 - figure out which tablets are present
779         for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it = devList.begin(); it != devList.end(); ++it ) {
780             Glib::RefPtr<InputDevice const> dev = *it;
781             if ( dev ) {
782                 if ( dev->getSource() != Gdk::SOURCE_MOUSE ) {
783                     consumed.insert( dev->getId() );
784                     if ( tablets.empty() ) {
785                         TabletTmp tmp;
786                         tablets.push_back(tmp);
787                     }
788                     tablets.back().devices.push_back(dev);
789                 }
790             } else {
791                 g_warning("Null device in list");
792             }
793         }
795         // Phase 2 - build a UI for the present devices
796         for ( std::list<TabletTmp>::iterator it = tablets.begin(); it != tablets.end(); ++it ) {
797             tablet = store->append(row.children());
798             Gtk::TreeModel::Row childrow = *tablet;
799             if ( it->name.empty() ) {
800                 // Check to see if we can derive one
801                 std::list<Glib::ustring> names;
802                 for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it2 = it->devices.begin(); it2 != it->devices.end(); ++it2 ) {
803                     names.push_back( (*it2)->getName() );
804                 }
805                 Glib::ustring common = getCommon(names);
806                 if ( !common.empty() ) {
807                     it->name = common;
808                 }
809             }
810             childrow[getCols().description] = it->name.empty() ? _("Tablet") : it->name ;
811             childrow[getCols().thumbnail] = getPix(PIX_TABLET);
813             // Check if there is an eraser we can link to a pen
814             for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it2 = it->devices.begin(); it2 != it->devices.end(); ++it2 ) {
815                 Glib::RefPtr<InputDevice const> dev = *it2;
816                 if ( dev->getSource() == Gdk::SOURCE_PEN ) {
817                     for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it3 = it->devices.begin(); it3 != it->devices.end(); ++it3 ) {
818                         Glib::RefPtr<InputDevice const> dev2 = *it3;
819                         if ( dev2->getSource() == Gdk::SOURCE_ERASER ) {
820                             DeviceManager::getManager().setLinkedTo(dev->getId(), dev2->getId());                            
821                             break; // only check the first eraser... for now
822                         }
823                         break; // only check the first pen... for now
824                     }
825                 }
826             }
828             for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it2 = it->devices.begin(); it2 != it->devices.end(); ++it2 ) {
829                 Glib::RefPtr<InputDevice const> dev = *it2;
830                 Gtk::TreeModel::Row deviceRow = *(store->append(childrow.children()));
831                 deviceRow[getCols().description] = dev->getName();
832                 deviceRow[getCols().device] = dev;
833                 deviceRow[getCols().mode] = dev->getMode();
834                 switch ( dev->getSource() ) {
835                     case GDK_SOURCE_MOUSE:
836                         deviceRow[getCols().thumbnail] = getPix(PIX_CORE);
837                         break;
838                     case GDK_SOURCE_PEN:
839                         if (deviceRow[getCols().description] == _("pad")) {
840                             deviceRow[getCols().thumbnail] = getPix(PIX_SIDEBUTTONS);
841                         } else {
842                             deviceRow[getCols().thumbnail] = getPix(PIX_TIP);
843                         }
844                         break;
845                     case GDK_SOURCE_CURSOR:
846                         deviceRow[getCols().thumbnail] = getPix(PIX_MOUSE);
847                         break;
848                     case GDK_SOURCE_ERASER:
849                         deviceRow[getCols().thumbnail] = getPix(PIX_ERASER);
850                         break;
851                     default:
852                         ; // nothing
853                 }
854             }
855         }
857         for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it = devList.begin(); it != devList.end(); ++it ) {
858             Glib::RefPtr<InputDevice const> dev = *it;
859             if ( dev && (consumed.find( dev->getId() ) == consumed.end()) ) {
860                 Gtk::TreeModel::Row deviceRow = *(store->append(row.children()));
861                 deviceRow[getCols().description] = dev->getName();
862                 deviceRow[getCols().device] = dev;
863                 deviceRow[getCols().mode] = dev->getMode();
864                 deviceRow[getCols().thumbnail] = getPix(PIX_CORE);
865             }
866         }
867     } else {
868         g_warning("No devices found");
869     }
873 InputDialogImpl::ConfPanel::ConfPanel() :
874     Gtk::VBox(),
875     store(Gtk::TreeStore::create(getCols())),
876     tabletIter(),
877     tree(store),
878     treeScroller(),
879     watcher(*this),
880     useExt(_("_Use pressure-sensitive tablet (requires restart)"), true),
881     save(_("_Save"), true)
883     pack_start(treeScroller);
885     treeScroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
886     treeScroller.add(tree);
888     class Foo : public Gtk::TreeModel::ColumnRecord {
889     public :
890         Gtk::TreeModelColumn<Glib::ustring> one;
891         Foo() {add(one);}
892     };
893     static Foo foo;
894     Glib::RefPtr<Gtk::ListStore> poppers = Gtk::ListStore::create(foo);
895     poppers->reference();
897     Gtk::TreeModel::Row row = *(poppers->append());
898     row[foo.one] = getModeToString()[Gdk::MODE_DISABLED];
899     row = *(poppers->append());
900     row[foo.one] = getModeToString()[Gdk::MODE_SCREEN];
901     row = *(poppers->append());
902     row[foo.one] = getModeToString()[Gdk::MODE_WINDOW];
904     //Add the TreeView's view columns:
905     {
906         Gtk::CellRendererToggle *rendr = new Gtk::CellRendererToggle();
907         Gtk::TreeViewColumn *col = new Gtk::TreeViewColumn("xx", *rendr);
908         if (col) {
909             tree.append_column(*col);
910             col->set_cell_data_func(*rendr, sigc::ptr_fun(setCellStateToggle));
911             rendr->signal_toggled().connect(sigc::bind(sigc::ptr_fun(commitCellStateChange), store));
912         }
913     }
915     int expPos = tree.append_column("", getCols().expander);
917     tree.append_column("I", getCols().thumbnail);
918     tree.append_column("Bar", getCols().description);
920     {
921         Gtk::CellRendererCombo *rendr = new Gtk::CellRendererCombo();
922         rendr->property_model().set_value(poppers);
923         rendr->property_text_column().set_value(0);
924         rendr->property_has_entry() = false;
925         Gtk::TreeViewColumn *col = new Gtk::TreeViewColumn("X", *rendr);
926         if (col) {
927             tree.append_column(*col);
928             col->set_cell_data_func(*rendr, sigc::ptr_fun(setModeCellString));
929             rendr->signal_edited().connect(sigc::bind(sigc::ptr_fun(commitCellModeChange), store));
930             rendr->property_editable() = true;
931         }
932     }
934     tree.set_enable_tree_lines();
935     tree.set_headers_visible(false);
936     tree.set_expander_column( *tree.get_column(expPos - 1) );
938     setupTree( store, tabletIter );
940     Glib::RefPtr<Gtk::TreeView> treePtr(&tree);
941     Inkscape::DeviceManager::getManager().signalLinkChanged().connect(sigc::bind(sigc::ptr_fun(&InputDialogImpl::updateDeviceLinks), tabletIter, treePtr));
943     tree.expand_all();
945     useExt.set_active(Preferences::get()->getBool("/options/useextinput/value"));
946     useExt.signal_toggled().connect(sigc::mem_fun(*this, &InputDialogImpl::ConfPanel::useExtToggled));
947     pack_start(useExt, Gtk::PACK_SHRINK);
949     save.signal_clicked().connect(sigc::mem_fun(*this, &InputDialogImpl::ConfPanel::saveSettings));
950     Gtk::Alignment *align = new Gtk::Alignment(Gtk::ALIGN_RIGHT, Gtk::ALIGN_TOP, 0, 0);
951     align->add(save);
952     pack_start(*Gtk::manage(align), Gtk::PACK_SHRINK);
955 InputDialogImpl::ConfPanel::~ConfPanel()
959 void InputDialogImpl::ConfPanel::setModeCellString(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter)
961     if (iter) {
962         Gtk::CellRendererCombo *combo = dynamic_cast<Gtk::CellRendererCombo *>(rndr);
963         if (combo) {
964             Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
965             Gdk::InputMode mode = (*iter)[getCols().mode];
966             if (dev && (getModeToString().find(mode) != getModeToString().end())) {
967                 combo->property_text() = getModeToString()[mode];
968             } else {
969                 combo->property_text() = "";
970             }
971         }
972     }
975 void InputDialogImpl::ConfPanel::commitCellModeChange(Glib::ustring const &path, Glib::ustring const &newText, Glib::RefPtr<Gtk::TreeStore> store)
977     Gtk::TreeIter iter = store->get_iter(path);
978     if (iter) {
979         Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
980         if (dev && (getStringToMode().find(newText) != getStringToMode().end())) {
981             Gdk::InputMode mode = getStringToMode()[newText];
982             Inkscape::DeviceManager::getManager().setMode( dev->getId(), mode );
983         }
984     }
987 void InputDialogImpl::ConfPanel::setCellStateToggle(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter)
989     if (iter) {
990         Gtk::CellRendererToggle *toggle = dynamic_cast<Gtk::CellRendererToggle *>(rndr);
991         if (toggle) {
992             Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
993             if (dev) {
994                 Gdk::InputMode mode = (*iter)[getCols().mode];
995                 toggle->set_active(mode != Gdk::MODE_DISABLED);
996             } else {
997                 toggle->set_active(false);
998             }
999         }
1000     }
1003 void InputDialogImpl::ConfPanel::commitCellStateChange(Glib::ustring const &path, Glib::RefPtr<Gtk::TreeStore> store)
1005     Gtk::TreeIter iter = store->get_iter(path);
1006     if (iter) {
1007         Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
1008         if (dev) {
1009             Gdk::InputMode mode = (*iter)[getCols().mode];
1010             if (mode == Gdk::MODE_DISABLED) {
1011                 Inkscape::DeviceManager::getManager().setMode( dev->getId(), Gdk::MODE_SCREEN );
1012             } else {
1013                 Inkscape::DeviceManager::getManager().setMode( dev->getId(), Gdk::MODE_DISABLED );
1014             }
1015         }
1016     }
1019 void InputDialogImpl::ConfPanel::saveSettings()
1021     Inkscape::DeviceManager::getManager().saveConfig();
1024 void InputDialogImpl::ConfPanel::useExtToggled()
1026     bool active = useExt.get_active();
1027     if (active != Preferences::get()->getBool("/options/useextinput/value")) {
1028         Preferences::get()->setBool("/options/useextinput/value", active);
1029         if (active) {
1030             // As a work-around for a common problem, enable tablet toggles on the calligraphic tool.
1031             // Covered in Launchpad bug #196195.
1032             Preferences::get()->setBool("/tools/tweak/usepressure", true);
1033             Preferences::get()->setBool("/tools/calligraphic/usepressure", true);
1034             Preferences::get()->setBool("/tools/calligraphic/usetilt", true);
1035         }
1036     }
1039 InputDialogImpl::ConfPanel::Blink::Blink(ConfPanel &parent) :
1040     Preferences::Observer("/options/useextinput/value"),
1041     parent(parent)
1043     Preferences::get()->addObserver(*this);
1046 InputDialogImpl::ConfPanel::Blink::~Blink()
1048     Preferences::get()->removeObserver(*this);
1051 void InputDialogImpl::ConfPanel::Blink::notify(Preferences::Entry const &new_val)
1053     parent.useExt.set_active(new_val.getBool());
1056 void InputDialogImpl::handleDeviceChange(Glib::RefPtr<InputDevice const> device)
1058 //     g_message("OUCH!!!! for %p  hits %s", &device, device->getId().c_str());
1059     std::vector<Glib::RefPtr<Gtk::TreeStore> > stores;
1060     stores.push_back(store);
1061     stores.push_back(cfgPanel.store);
1063     for (std::vector<Glib::RefPtr<Gtk::TreeStore> >::iterator it = stores.begin(); it != stores.end(); ++it) {
1064         Gtk::TreeModel::iterator deviceIter;
1065         (*it)->foreach_iter( sigc::bind<Glib::ustring, Gtk::TreeModel::iterator*>(
1066                                  sigc::ptr_fun(&InputDialogImpl::findDevice),
1067                                  device->getId(),
1068                                  &deviceIter) );
1069         if ( deviceIter ) {
1070             Gdk::InputMode mode = device->getMode();
1071             Gtk::TreeModel::Row row = *deviceIter;
1072             if (row[getCols().mode] != mode) {
1073                 row[getCols().mode] = mode;
1074             }
1075         }
1076     }
1079 void InputDialogImpl::updateDeviceAxes(Glib::RefPtr<InputDevice const> device)
1081     gint live = device->getLiveAxes();
1083     std::map<guint, std::pair<guint, gdouble> > existing = axesMap[device->getId()];
1084     gint mask = 0x1;
1085     for ( gint num = 0; num < 32; num++, mask <<= 1) {
1086         if ( (mask & live) != 0 ) {
1087             if ( (existing.find(num) == existing.end()) || (existing[num].first < 2) ) {
1088                 axesMap[device->getId()][num].first = 2;
1089                 axesMap[device->getId()][num].second = 0.0;
1090             }
1091         }
1092     }
1093     updateTestAxes( device->getId(), 0 );
1096 void InputDialogImpl::updateDeviceButtons(Glib::RefPtr<InputDevice const> device)
1098     gint live = device->getLiveButtons();
1099     std::set<guint> existing = buttonMap[device->getId()];
1100     gint mask = 0x1;
1101     for ( gint num = 0; num < 32; num++, mask <<= 1) {
1102         if ( (mask & live) != 0 ) {
1103             if ( existing.find(num) == existing.end() ) {
1104                 buttonMap[device->getId()].insert(num);
1105             }
1106         }
1107     }
1108     updateTestButtons(device->getId(), -1);
1112 bool InputDialogImpl::findDevice(const Gtk::TreeModel::iterator& iter,
1113                                  Glib::ustring id,
1114                                  Gtk::TreeModel::iterator* result)
1116     bool stop = false;
1117     Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
1118     if ( dev && (dev->getId() == id) ) {
1119         if ( result ) {
1120             *result = iter;
1121         }
1122         stop = true;
1123     }
1124     return stop;
1127 bool InputDialogImpl::findDeviceByLink(const Gtk::TreeModel::iterator& iter,
1128                                        Glib::ustring link,
1129                                        Gtk::TreeModel::iterator* result)
1131     bool stop = false;
1132     Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
1133     if ( dev && (dev->getLink() == link) ) {
1134         if ( result ) {
1135             *result = iter;
1136         }
1137         stop = true;
1138     }
1139     return stop;
1142 void InputDialogImpl::updateDeviceLinks(Glib::RefPtr<InputDevice const> device, Gtk::TreeIter tabletIter, Glib::RefPtr<Gtk::TreeView> tree)
1144     Glib::RefPtr<Gtk::TreeStore> store = Glib::RefPtr<Gtk::TreeStore>::cast_dynamic(tree->get_model());
1146 //     g_message("Links!!!! for %p  hits [%s]  with link of [%s]", &device, device->getId().c_str(), device->getLink().c_str());
1147     Gtk::TreeModel::iterator deviceIter;
1148     store->foreach_iter( sigc::bind<Glib::ustring, Gtk::TreeModel::iterator*>(
1149                              sigc::ptr_fun(&InputDialogImpl::findDevice),
1150                              device->getId(),
1151                              &deviceIter) );
1153     if ( deviceIter ) {
1154         // Found the device concerned. Can proceed.
1156         if ( device->getLink().empty() ) {
1157             // is now unlinked
1158 //             g_message("Item %s is unlinked", device->getId().c_str());
1159             if ( deviceIter->parent() != tabletIter ) {
1160                 // Not the child of the tablet. move on up
1162                 Glib::RefPtr<InputDevice const> dev = (*deviceIter)[getCols().device];
1163                 Glib::ustring descr = (*deviceIter)[getCols().description];
1164                 Glib::RefPtr<Gdk::Pixbuf> thumb = (*deviceIter)[getCols().thumbnail];
1166                 Gtk::TreeModel::Row deviceRow = *store->append(tabletIter->children());
1167                 deviceRow[getCols().description] = descr;
1168                 deviceRow[getCols().thumbnail] = thumb;
1169                 deviceRow[getCols().device] = dev;
1170                 deviceRow[getCols().mode] = dev->getMode();
1172                 Gtk::TreeModel::iterator oldParent = deviceIter->parent();
1173                 store->erase(deviceIter);
1174                 if ( oldParent->children().empty() ) {
1175                     store->erase(oldParent);
1176                 }
1177             }
1178         } else {
1179             // is linking
1180             if ( deviceIter->parent() == tabletIter ) {
1181                 // Simple case. Not already linked
1183                 Gtk::TreeIter newGroup = store->append(tabletIter->children());
1184                 (*newGroup)[getCols().description] = _("Pen");
1185                 (*newGroup)[getCols().thumbnail] = getPix(PIX_PEN);
1187                 Glib::RefPtr<InputDevice const> dev = (*deviceIter)[getCols().device];
1188                 Glib::ustring descr = (*deviceIter)[getCols().description];
1189                 Glib::RefPtr<Gdk::Pixbuf> thumb = (*deviceIter)[getCols().thumbnail];
1191                 Gtk::TreeModel::Row deviceRow = *store->append(newGroup->children());
1192                 deviceRow[getCols().description] = descr;
1193                 deviceRow[getCols().thumbnail] = thumb;
1194                 deviceRow[getCols().device] = dev;
1195                 deviceRow[getCols().mode] = dev->getMode();
1198                 Gtk::TreeModel::iterator linkIter;
1199                 store->foreach_iter( sigc::bind<Glib::ustring, Gtk::TreeModel::iterator*>(
1200                                          sigc::ptr_fun(&InputDialogImpl::findDeviceByLink),
1201                                          device->getId(),
1202                                          &linkIter) );
1203                 if ( linkIter ) {
1204                     dev = (*linkIter)[getCols().device];
1205                     descr = (*linkIter)[getCols().description];
1206                     thumb = (*linkIter)[getCols().thumbnail];
1208                     deviceRow = *store->append(newGroup->children());
1209                     deviceRow[getCols().description] = descr;
1210                     deviceRow[getCols().thumbnail] = thumb;
1211                     deviceRow[getCols().device] = dev;
1212                     deviceRow[getCols().mode] = dev->getMode();
1213                     Gtk::TreeModel::iterator oldParent = linkIter->parent();
1214                     store->erase(linkIter);
1215                     if ( oldParent->children().empty() ) {
1216                         store->erase(oldParent);
1217                     }
1218                 }
1220                 Gtk::TreeModel::iterator oldParent = deviceIter->parent();
1221                 store->erase(deviceIter);
1222                 if ( oldParent->children().empty() ) {
1223                     store->erase(oldParent);
1224                 }
1225                 tree->expand_row(Gtk::TreePath(newGroup), true);
1226             }
1227         }
1228     }
1231 void InputDialogImpl::linkComboChanged() {
1232     Glib::RefPtr<Gtk::TreeSelection> treeSel = tree.get_selection();
1233     Gtk::TreeModel::iterator iter = treeSel->get_selected();
1234     if (iter) {
1235         Gtk::TreeModel::Row row = *iter;
1236         Glib::ustring val = row[getCols().description];
1237         Glib::RefPtr<InputDevice const> dev = row[getCols().device];
1238         if ( dev ) {
1239             if ( linkCombo.get_active_row_number() == 0 ) {
1240                 // It is the "None" entry
1241                 DeviceManager::getManager().setLinkedTo(dev->getId(), "");
1242             } else {
1243                 Glib::ustring linkName = linkCombo.get_active_text();
1244                 std::list<Glib::RefPtr<InputDevice const> > devList = Inkscape::DeviceManager::getManager().getDevices();
1245                 for ( std::list<Glib::RefPtr<InputDevice const> >::const_iterator it = devList.begin(); it != devList.end(); ++it ) {
1246                     if ( linkName == (*it)->getName() ) {
1247                         DeviceManager::getManager().setLinkedTo(dev->getId(), (*it)->getId());
1248                         break;
1249                     }
1250                 }
1251             }
1252         }
1253     }
1256 void InputDialogImpl::resyncToSelection() {
1257     bool clear = true;
1258     Glib::RefPtr<Gtk::TreeSelection> treeSel = tree.get_selection();
1259     Gtk::TreeModel::iterator iter = treeSel->get_selected();
1260     if (iter) {
1261         Gtk::TreeModel::Row row = *iter;
1262         Glib::ustring val = row[getCols().description];
1263         Glib::RefPtr<InputDevice const> dev = row[getCols().device];
1264         if ( dev ) {
1265             devDetails.set_sensitive(true);
1267             linkConnection.block();
1268             linkCombo.clear_items();
1269             linkCombo.append_text(_("None"));
1270             linkCombo.set_active(0);
1271             if ( dev->getSource() != Gdk::SOURCE_MOUSE ) {
1272                 Glib::ustring linked = dev->getLink();
1273                 std::list<Glib::RefPtr<InputDevice const> > devList = Inkscape::DeviceManager::getManager().getDevices();
1274                 for ( std::list<Glib::RefPtr<InputDevice const> >::const_iterator it = devList.begin(); it != devList.end(); ++it ) {
1275                     if ( ((*it)->getSource() != Gdk::SOURCE_MOUSE) && ((*it) != dev) ) {
1276                         linkCombo.append_text((*it)->getName().c_str());
1277                         if ( (linked.length() > 0) && (linked == (*it)->getId()) ) {
1278                             linkCombo.set_active_text((*it)->getName().c_str());
1279                         }
1280                     }
1281                 }
1282                 linkCombo.set_sensitive(true);
1283             } else {
1284                 linkCombo.set_sensitive(false);
1285             }
1286             linkConnection.unblock();
1288             clear = false;
1289             devName.set_label(row[getCols().description]);
1290             setupValueAndCombo( dev->getNumAxes(), dev->getNumAxes(), devAxesCount, axesCombo);
1291             setupValueAndCombo( dev->getNumKeys(), dev->getNumKeys(), devKeyCount, buttonCombo);
1292         }
1293     }
1295     devDetails.set_sensitive(!clear);
1296     if (clear) {
1297         devName.set_label("");
1298         devAxesCount.set_label("");
1299         devKeyCount.set_label("");
1300     }
1303 void InputDialogImpl::setupValueAndCombo( gint reported, gint actual, Gtk::Label& label, Gtk::ComboBoxText& combo )
1305     gchar *tmp = g_strdup_printf("%d", reported);
1306     label.set_label(tmp);
1307     g_free(tmp);
1309     combo.clear_items();
1310     for ( gint i = 1; i <= reported; ++i ) {
1311         tmp = g_strdup_printf("%d", i);
1312         combo.append_text(tmp);
1313         g_free(tmp);
1314     }
1316     if ( (1 <= actual) && (actual <= reported) ) {
1317         combo.set_active(actual - 1);
1318     }
1321 void InputDialogImpl::updateTestButtons( Glib::ustring const& key, gint hotButton )
1323     for ( gint i = 0; i < static_cast<gint>(G_N_ELEMENTS(testButtons)); i++ ) {
1324         if ( buttonMap[key].find(i) != buttonMap[key].end() ) {
1325             if ( i == hotButton ) {
1326                 testButtons[i].set(getPix(PIX_BUTTONS_ON));
1327             } else {
1328                 testButtons[i].set(getPix(PIX_BUTTONS_OFF));
1329             }
1330         } else {
1331             testButtons[i].set(getPix(PIX_BUTTONS_NONE));
1332         }
1333     }
1336 void InputDialogImpl::updateTestAxes( Glib::ustring const& key, GdkDevice* dev )
1338     static gdouble epsilon = 0.0001;
1339     {
1340         Glib::RefPtr<Gtk::TreeSelection> treeSel = tree.get_selection();
1341         Gtk::TreeModel::iterator iter = treeSel->get_selected();
1342         if (iter) {
1343             Gtk::TreeModel::Row row = *iter;
1344             Glib::ustring val = row[getCols().description];
1345             Glib::RefPtr<InputDevice const> idev = row[getCols().device];
1346             if ( !idev || (idev->getId() != key) ) {
1347                 dev = 0;
1348             }
1349         }
1350     }
1353     for ( gint i = 0; i < static_cast<gint>(G_N_ELEMENTS(testAxes)); i++ ) {
1354         if ( axesMap[key].find(i) != axesMap[key].end() ) {
1355             switch ( axesMap[key][i].first ) {
1356                 case 0:
1357                 case 1:
1358                     testAxes[i].set(getPix(PIX_AXIS_NONE));
1359                     if ( dev && (i < static_cast<gint>(G_N_ELEMENTS(axesValues)) ) ) {
1360                         axesValues[i].set_sensitive(false);
1361                     }
1362                     break;
1363                 case 2:
1364                     testAxes[i].set(getPix(PIX_AXIS_OFF));
1365                     axesValues[i].set_sensitive(true);
1366                     if ( dev && (i < static_cast<gint>(G_N_ELEMENTS(axesValues)) ) ) {
1367                         if ( (dev->axes[i].max - dev->axes[i].min) > epsilon ) {
1368                             axesValues[i].set_sensitive(true);
1369                             axesValues[i].set_fraction( (axesMap[key][i].second- dev->axes[i].min) / (dev->axes[i].max - dev->axes[i].min) );
1370                         }
1371                         gchar* str = g_strdup_printf("%f", axesMap[key][i].second);
1372                         axesValues[i].set_text(str);
1373                         g_free(str);
1374                     }
1375                     break;
1376                 case 3:
1377                     testAxes[i].set(getPix(PIX_AXIS_ON));
1378                     axesValues[i].set_sensitive(true);
1379                     if ( dev && (i < static_cast<gint>(G_N_ELEMENTS(axesValues)) ) ) {
1380                         if ( (dev->axes[i].max - dev->axes[i].min) > epsilon ) {
1381                             axesValues[i].set_sensitive(true);
1382                             axesValues[i].set_fraction( (axesMap[key][i].second- dev->axes[i].min) / (dev->axes[i].max - dev->axes[i].min) );
1383                         }
1384                         gchar* str = g_strdup_printf("%f", axesMap[key][i].second);
1385                         axesValues[i].set_text(str);
1386                         g_free(str);
1387                     }
1388             }
1390         } else {
1391             testAxes[i].set(getPix(PIX_AXIS_NONE));
1392         }
1393     }
1394     if ( !dev ) {
1395         for ( gint i = 0; i < static_cast<gint>(G_N_ELEMENTS(axesValues)); i++ ) {
1396             axesValues[i].set_fraction(0.0);
1397             axesValues[i].set_text("");
1398             axesValues[i].set_sensitive(false);
1399         }
1400     }
1403 void InputDialogImpl::mapAxesValues( Glib::ustring const& key, guint numAxes, gdouble const * axes, GdkDevice* dev )
1405     static gdouble epsilon = 0.0001;
1406     if ( (numAxes > 0) && axes) {
1407         for ( guint axisNum = 0; axisNum < numAxes; axisNum++ ) {
1408             // 0 == new, 1 == set value, 2 == changed value, 3 == active
1409             gdouble diff = axesMap[key][axisNum].second - axes[axisNum];
1410             switch(axesMap[key][axisNum].first) {
1411                 case 0:
1412                 {
1413                     axesMap[key][axisNum].first = 1;
1414                     axesMap[key][axisNum].second = axes[axisNum];
1415                 }
1416                 break;
1417                 case 1:
1418                 {
1419                     if ( (diff > epsilon) || (diff < -epsilon) ) {
1420 //                         g_message("Axis %d changed on %s]", axisNum, key.c_str());
1421                         axesMap[key][axisNum].first = 3;
1422                         axesMap[key][axisNum].second = axes[axisNum];
1423                         updateTestAxes(key, dev);
1424                         DeviceManager::getManager().addAxis(key, axisNum);
1425                     }
1426                 }
1427                 break;
1428                 case 2:
1429                 {
1430                     if ( (diff > epsilon) || (diff < -epsilon) ) {
1431                         axesMap[key][axisNum].first = 3;
1432                         axesMap[key][axisNum].second = axes[axisNum];
1433                         updateTestAxes(key, dev);
1434                     }
1435                 }
1436                 break;
1437                 case 3:
1438                 {
1439                     if ( (diff > epsilon) || (diff < -epsilon) ) {
1440                         axesMap[key][axisNum].second = axes[axisNum];
1441                     } else {
1442                         axesMap[key][axisNum].first = 2;
1443                         updateTestAxes(key, dev);
1444                     }
1445                 }
1446             }
1447         }
1448     }
1449     // std::map<Glib::ustring, std::map<guint, std::pair<guint, gdouble> > > axesMap;
1452 Glib::ustring InputDialogImpl::getKeyFor( GdkDevice* device )
1454     Glib::ustring key;
1455     switch ( device->source ) {
1456         case GDK_SOURCE_MOUSE:
1457             key = "M:";
1458             break;
1459         case GDK_SOURCE_CURSOR:
1460             key = "C:";
1461             break;
1462         case GDK_SOURCE_PEN:
1463             key = "P:";
1464             break;
1465         case GDK_SOURCE_ERASER:
1466             key = "E:";
1467             break;
1468         default:
1469             key = "?:";
1470     }
1471     key += device->name;
1473     return key;
1476 bool InputDialogImpl::eventSnoop(GdkEvent* event)
1478     int modmod = 0;
1480     GdkInputSource source = lastSourceSeen;
1481     Glib::ustring devName = lastDevnameSeen;
1482     Glib::ustring key;
1483     gint hotButton = -1;
1485     switch ( event->type ) {
1486         case GDK_KEY_PRESS:
1487         case GDK_KEY_RELEASE:
1488         {
1489             GdkEventKey* keyEvt = reinterpret_cast<GdkEventKey*>(event);
1490             gchar* name = gtk_accelerator_name(keyEvt->keyval, static_cast<GdkModifierType>(keyEvt->state));
1491             keyVal.set_label(name);
1492 //             g_message("%d KEY    state:0x%08x  0x%04x [%s]", keyEvt->type, keyEvt->state, keyEvt->keyval, name);
1493             g_free(name);
1494         }
1495         break;
1496         case GDK_BUTTON_PRESS:
1497             modmod = 1;
1498             // fallthrough
1499         case GDK_BUTTON_RELEASE:
1500         {
1501             GdkEventButton* btnEvt = reinterpret_cast<GdkEventButton*>(event);
1502             if ( btnEvt->device ) {
1503                 key = getKeyFor(btnEvt->device);
1504                 source = btnEvt->device->source;
1505                 devName = btnEvt->device->name;
1507                 mapAxesValues(key, btnEvt->device->num_axes, btnEvt->axes, btnEvt->device);
1508                 if ( buttonMap[key].find(btnEvt->button) == buttonMap[key].end() ) {
1509 //                     g_message("New button found for %s = %d", key.c_str(), btnEvt->button);
1510                     buttonMap[key].insert(btnEvt->button);
1511                     DeviceManager::getManager().addButton(key, btnEvt->button);
1512                 }
1513                 hotButton = modmod ? btnEvt->button : -1;
1514                 updateTestButtons(key, hotButton);
1515             }
1516             gchar* name = gtk_accelerator_name(0, static_cast<GdkModifierType>(btnEvt->state));
1517             keyVal.set_label(name);
1518 //             g_message("%d BTN    state:0x%08x %c  %4d [%s] dev:%p [%s]  ",
1519 //                       btnEvt->type, btnEvt->state,
1520 //                       (modmod ? '+':'-'),
1521 //                       btnEvt->button, name, btnEvt->device,
1522 //                       (btnEvt->device ? btnEvt->device->name : "null")
1524 //                 );
1525             g_free(name);
1526         }
1527         break;
1528         case GDK_MOTION_NOTIFY:
1529         {
1530             GdkEventMotion* btnMtn = reinterpret_cast<GdkEventMotion*>(event);
1531             if ( btnMtn->device ) {
1532                 key = getKeyFor(btnMtn->device);
1533                 source = btnMtn->device->source;
1534                 devName = btnMtn->device->name;
1535                 mapAxesValues(key, btnMtn->device->num_axes, btnMtn->axes, btnMtn->device);
1536             }
1537             gchar* name = gtk_accelerator_name(0, static_cast<GdkModifierType>(btnMtn->state));
1538             keyVal.set_label(name);
1539 //             g_message("%d MOV    state:0x%08x         [%s] dev:%p [%s] %3.2f %3.2f %3.2f %3.2f %3.2f %3.2f", btnMtn->type, btnMtn->state,
1540 //                       name, btnMtn->device,
1541 //                       (btnMtn->device ? btnMtn->device->name : "null"),
1542 //                       ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 0)) ? btnMtn->axes[0]:0),
1543 //                       ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 1)) ? btnMtn->axes[1]:0),
1544 //                       ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 2)) ? btnMtn->axes[2]:0),
1545 //                       ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 3)) ? btnMtn->axes[3]:0),
1546 //                       ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 4)) ? btnMtn->axes[4]:0),
1547 //                       ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 5)) ? btnMtn->axes[5]:0)
1548 //                 );
1549             g_free(name);
1550         }
1551         break;
1552         default:
1553             ;// nothing
1554     }
1557     if ( (lastSourceSeen != source) || (lastDevnameSeen != devName) ) {
1558         switch (source) {
1559             case GDK_SOURCE_MOUSE:
1560             {
1561                 testThumb.set(getPix(PIX_CORE));
1562             }
1563             break;
1564             case GDK_SOURCE_CURSOR:
1565             {
1566 //                 g_message("flip to cursor");
1567                 testThumb.set(getPix(PIX_MOUSE));
1568             }
1569             break;
1570             case GDK_SOURCE_PEN:
1571             {
1572                 if (devName == _("pad")) {
1573 //                     g_message("flip to pad");
1574                     testThumb.set(getPix(PIX_SIDEBUTTONS));
1575                 } else {
1576 //                     g_message("flip to pen");
1577                     testThumb.set(getPix(PIX_TIP));
1578                 }
1579             }
1580             break;
1581             case GDK_SOURCE_ERASER:
1582             {
1583 //                 g_message("flip to eraser");
1584                 testThumb.set(getPix(PIX_ERASER));
1585             }
1586             break;
1587 //             default:
1588 //                 g_message("gurgle");
1589         }
1590         updateTestButtons(key, hotButton);
1591         lastSourceSeen = source;
1592         lastDevnameSeen = devName;
1593     }
1595     return false;
1599 } // end namespace Inkscape
1600 } // end namespace UI
1601 } // end namespace Dialog
1604 /*
1605   Local Variables:
1606   mode:c++
1607   c-file-style:"stroustrup"
1608   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1609   indent-tabs-mode:nil
1610   fill-column:99
1611   End:
1612 */
1613 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :