summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/input.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/dialog/input.cpp')
-rw-r--r--src/ui/dialog/input.cpp1798
1 files changed, 1798 insertions, 0 deletions
diff --git a/src/ui/dialog/input.cpp b/src/ui/dialog/input.cpp
new file mode 100644
index 0000000..df071c7
--- /dev/null
+++ b/src/ui/dialog/input.cpp
@@ -0,0 +1,1798 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Input devices dialog (new) - implementation.
+ */
+/* Author:
+ * Jon A. Cruz
+ *
+ * Copyright (C) 2008 Author
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <map>
+#include <set>
+#include <list>
+#include "ui/widget/frame.h"
+#include "ui/widget/scrollprotected.h"
+
+#include <glibmm/i18n.h>
+
+#include <gtkmm/buttonbox.h>
+#include <gtkmm/cellrenderercombo.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/grid.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/menubar.h>
+#include <gtkmm/notebook.h>
+#include <gtkmm/paned.h>
+#include <gtkmm/progressbar.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/eventbox.h>
+
+#include "device-manager.h"
+#include "preferences.h"
+
+#include "input.h"
+
+// clang-format off
+/* XPM */
+static char const * core_xpm[] = {
+"16 16 4 1",
+" c None",
+". c #808080",
+"+ c #000000",
+"@ c #FFFFFF",
+" ",
+" ",
+" ",
+" .++++++. ",
+" +@+@@+@+ ",
+" +@+@@+@+ ",
+" +.+..+.+ ",
+" +@@@@@@+ ",
+" +@@@@@@+ ",
+" +@@@@@@+ ",
+" +@@@@@@+ ",
+" +@@@@@@+ ",
+" .++++++. ",
+" ",
+" ",
+" "};
+
+/* XPM */
+static char const *eraser[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 5 1",
+" c black",
+". c green",
+"X c #808080",
+"o c gray100",
+"O c None",
+/* pixels */
+"OOOOOOOOOOOOOOOO",
+"OOOOOOOOOOOOO OO",
+"OOOOOOOOOOOO . O",
+"OOOOOOOOOOO . OO",
+"OOOOOOOOOO . OOO",
+"OOOOOOOOO . OOOO",
+"OOOOOOOO . OOOOO",
+"OOOOOOOXo OOOOOO",
+"OOOOOOXoXOOOOOOO",
+"OOOOOXoXOOOOOOOO",
+"OOOOXoXOOOOOOOOO",
+"OOOXoXOOOOOOOOOO",
+"OOXoXOOOOOOOOOOO",
+"OOXXOOOOOOOOOOOO",
+"OOOOOOOOOOOOOOOO",
+"OOOOOOOOOOOOOOOO"
+};
+
+/* XPM */
+static char const *mouse[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+" c black",
+". c gray100",
+"X c None",
+/* pixels */
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"XXXXXXX XXXXXXX",
+"XXXXX . XXXXXXX",
+"XXXX .... XXXXXX",
+"XXXX .... XXXXXX",
+"XXXXX .... XXXXX",
+"XXXXX .... XXXXX",
+"XXXXXX .... XXXX",
+"XXXXXX .... XXXX",
+"XXXXXXX . XXXXX",
+"XXXXXXX XXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX"
+};
+
+/* XPM */
+static char const *pen[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+" c black",
+". c gray100",
+"X c None",
+/* pixels */
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXX XX",
+"XXXXXXXXXXXX . X",
+"XXXXXXXXXXX . XX",
+"XXXXXXXXXX . XXX",
+"XXXXXXXXX . XXXX",
+"XXXXXXXX . XXXXX",
+"XXXXXXX . XXXXXX",
+"XXXXXX . XXXXXXX",
+"XXXXX . XXXXXXXX",
+"XXXX . XXXXXXXXX",
+"XXX . XXXXXXXXXX",
+"XX . XXXXXXXXXXX",
+"XX XXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX"
+};
+
+/* XPM */
+static char const *sidebuttons[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 4 1",
+" c black",
+". c #808080",
+"o c green",
+"O c None",
+/* pixels */
+"OOOOOOOOOOOOOOOO",
+"OOOOOOOOOOOOOOOO",
+"O..............O",
+"O.OOOOOOOOOOOO.O",
+"O OOOOOOOO O",
+"O o OOOOOOOO o O",
+"O o OOOOOOOO o O",
+"O OOOOOOOO O",
+"O.OOOOOOOOOOOO.O",
+"O.OOOOOOOOOOOO.O",
+"O.OOOOOOOOOOOO.O",
+"O.OOOOOOOOOOOO.O",
+"O.OOOOOOOOOOOO.O",
+"O..............O",
+"OOOOOOOOOOOOOOOO",
+"OOOOOOOOOOOOOOOO"
+};
+
+/* XPM */
+static char const *tablet[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 3 1",
+" c black",
+". c gray100",
+"X c None",
+/* pixels */
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX",
+"X X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X ............ X",
+"X X",
+"XXXXXXXXXXXXXXXX",
+"XXXXXXXXXXXXXXXX"
+};
+
+/* XPM */
+static char const *tip[] = {
+/* columns rows colors chars-per-pixel */
+"16 16 5 1",
+" c black",
+". c green",
+"X c #808080",
+"o c gray100",
+"O c None",
+/* pixels */
+"OOOOOOOOOOOOOOOO",
+"OOOOOOOOOOOOOXOO",
+"OOOOOOOOOOOOXoXO",
+"OOOOOOOOOOOXoXOO",
+"OOOOOOOOOOXoXOOO",
+"OOOOOOOOOXoXOOOO",
+"OOOOOOOOXoXOOOOO",
+"OOOOOOO oXOOOOOO",
+"OOOOOO . OOOOOOO",
+"OOOOO . OOOOOOOO",
+"OOOO . OOOOOOOOO",
+"OOO . OOOOOOOOOO",
+"OO . OOOOOOOOOOO",
+"OO OOOOOOOOOOOO",
+"OOOOXXXXXOOOOOOO",
+"OOOOOOOOOXXXXXOO"
+};
+
+/* XPM */
+static char const *button_none[] = {
+/* columns rows colors chars-per-pixel */
+"8 8 3 1",
+" c black",
+". c #808080",
+"X c None",
+/* pixels */
+"XXXXXXXX",
+"XX .. XX",
+"X .XX. X",
+"X.XX X.X",
+"X.X XX.X",
+"X .XX. X",
+"XX .. XX",
+"XXXXXXXX"
+};
+/* XPM */
+static char const *button_off[] = {
+/* columns rows colors chars-per-pixel */
+"8 8 4 1",
+" c black",
+". c #808080",
+"X c gray100",
+"o c None",
+/* pixels */
+"oooooooo",
+"oo. .oo",
+"o. XX .o",
+"o XXXX o",
+"o XXXX o",
+"o. XX .o",
+"oo. .oo",
+"oooooooo"
+};
+/* XPM */
+static char const *button_on[] = {
+/* columns rows colors chars-per-pixel */
+"8 8 3 1",
+" c black",
+". c green",
+"X c None",
+/* pixels */
+"XXXXXXXX",
+"XX XX",
+"X .. X",
+"X .... X",
+"X .... X",
+"X .. X",
+"XX XX",
+"XXXXXXXX"
+};
+
+/* XPM */
+static char const * axis_none_xpm[] = {
+"24 8 3 1",
+" c None",
+". c #000000",
+"+ c #808080",
+" ",
+" .++++++++++++++++++. ",
+" .+ . .+. ",
+" + . . . + ",
+" + . . . + ",
+" .+. . +. ",
+" .++++++++++++++++++. ",
+" "};
+/* XPM */
+static char const * axis_off_xpm[] = {
+"24 8 4 1",
+" c None",
+". c #808080",
+"+ c #000000",
+"@ c #FFFFFF",
+" ",
+" .++++++++++++++++++. ",
+" .+@@@@@@@@@@@@@@@@@@+. ",
+" +@@@@@@@@@@@@@@@@@@@@+ ",
+" +@@@@@@@@@@@@@@@@@@@@+ ",
+" .+@@@@@@@@@@@@@@@@@@+. ",
+" .++++++++++++++++++. ",
+" "};
+/* XPM */
+static char const * axis_on_xpm[] = {
+"24 8 3 1",
+" c None",
+". c #000000",
+"+ c #00FF00",
+" ",
+" .................... ",
+" ..++++++++++++++++++.. ",
+" .++++++++++++++++++++. ",
+" .++++++++++++++++++++. ",
+" ..++++++++++++++++++.. ",
+" .................... ",
+" "};
+// clang-format on
+
+using Inkscape::InputDevice;
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+
+
+class DeviceModelColumns : public Gtk::TreeModel::ColumnRecord
+{
+public:
+ Gtk::TreeModelColumn<bool> toggler;
+ Gtk::TreeModelColumn<Glib::ustring> expander;
+ Gtk::TreeModelColumn<Glib::ustring> description;
+ Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > thumbnail;
+ Gtk::TreeModelColumn<Glib::RefPtr<InputDevice const> > device;
+ Gtk::TreeModelColumn<Gdk::InputMode> mode;
+
+ DeviceModelColumns() { add(toggler), add(expander), add(description); add(thumbnail); add(device); add(mode); }
+};
+
+static std::map<Gdk::InputMode, Glib::ustring> &getModeToString()
+{
+ static std::map<Gdk::InputMode, Glib::ustring> mapping;
+ if (mapping.empty()) {
+ mapping[Gdk::MODE_DISABLED] = _("Disabled");
+ mapping[Gdk::MODE_SCREEN] = C_("Input device", "Screen");
+ mapping[Gdk::MODE_WINDOW] = _("Window");
+ }
+
+ return mapping;
+}
+
+static int getModeId(Gdk::InputMode im)
+{
+ if (im == Gdk::MODE_DISABLED) return 0;
+ if (im == Gdk::MODE_SCREEN) return 1;
+ if (im == Gdk::MODE_WINDOW) return 2;
+
+ return 0;
+}
+
+static std::map<Glib::ustring, Gdk::InputMode> &getStringToMode()
+{
+ static std::map<Glib::ustring, Gdk::InputMode> mapping;
+ if (mapping.empty()) {
+ mapping[_("Disabled")] = Gdk::MODE_DISABLED;
+ mapping[_("Screen")] = Gdk::MODE_SCREEN;
+ mapping[_("Window")] = Gdk::MODE_WINDOW;
+ }
+
+ return mapping;
+}
+
+
+
+class InputDialogImpl : public InputDialog {
+public:
+ InputDialogImpl();
+ ~InputDialogImpl() override = default;
+
+private:
+ class ConfPanel : public Gtk::Box
+ {
+ public:
+ ConfPanel();
+ ~ConfPanel() override;
+
+ class Blink : public Preferences::Observer
+ {
+ public:
+ Blink(ConfPanel &parent);
+ ~Blink() override;
+ void notify(Preferences::Entry const &new_val) override;
+
+ ConfPanel &parent;
+ };
+
+ static void commitCellModeChange(Glib::ustring const &path, Glib::ustring const &newText, Glib::RefPtr<Gtk::TreeStore> store);
+ static void setModeCellString(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter);
+
+ static void commitCellStateChange(Glib::ustring const &path, Glib::RefPtr<Gtk::TreeStore> store);
+ static void setCellStateToggle(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter);
+
+ void saveSettings();
+ void onTreeSelect();
+ void useExtToggled();
+
+ void onModeChange();
+ void setKeys(gint count);
+ void setAxis(gint count);
+
+ Glib::RefPtr<Gtk::TreeStore> confDeviceStore;
+ Gtk::TreeIter confDeviceIter;
+ Gtk::TreeView confDeviceTree;
+ Gtk::ScrolledWindow confDeviceScroller;
+ Blink watcher;
+ Gtk::CheckButton useExt;
+ Gtk::Button save;
+ Gtk::Paned pane;
+ Gtk::Box detailsBox;
+ Gtk::Box titleFrame;
+ Gtk::Label titleLabel;
+ Inkscape::UI::Widget::Frame axisFrame;
+ Inkscape::UI::Widget::Frame keysFrame;
+ Gtk::Box axisVBox;
+ Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> modeCombo;
+ Gtk::Label modeLabel;
+ Gtk::Box modeBox;
+
+ class KeysColumns : public Gtk::TreeModel::ColumnRecord
+ {
+ public:
+ KeysColumns()
+ {
+ add(name);
+ add(value);
+ }
+ ~KeysColumns() override = default;
+
+ Gtk::TreeModelColumn<Glib::ustring> name;
+ Gtk::TreeModelColumn<Glib::ustring> value;
+ };
+
+ KeysColumns keysColumns;
+ KeysColumns axisColumns;
+
+ Glib::RefPtr<Gtk::ListStore> axisStore;
+ Gtk::TreeView axisTree;
+ Gtk::ScrolledWindow axisScroll;
+
+ Glib::RefPtr<Gtk::ListStore> keysStore;
+ Gtk::TreeView keysTree;
+ Gtk::ScrolledWindow keysScroll;
+ Gtk::CellRendererAccel _kb_shortcut_renderer;
+
+
+ };
+
+ static DeviceModelColumns &getCols();
+
+ enum PixId {PIX_CORE, PIX_PEN, PIX_MOUSE, PIX_TIP, PIX_TABLET, PIX_ERASER, PIX_SIDEBUTTONS,
+ PIX_BUTTONS_NONE, PIX_BUTTONS_ON, PIX_BUTTONS_OFF,
+ PIX_AXIS_NONE, PIX_AXIS_ON, PIX_AXIS_OFF};
+
+ static Glib::RefPtr<Gdk::Pixbuf> getPix(PixId id);
+
+ std::map<Glib::ustring, std::set<guint> > buttonMap;
+ std::map<Glib::ustring, std::map<guint, std::pair<guint, gdouble> > > axesMap;
+
+ Gdk::InputSource lastSourceSeen;
+ Glib::ustring lastDevnameSeen;
+
+ Glib::RefPtr<Gtk::TreeStore> deviceStore;
+ Gtk::TreeIter deviceIter;
+ Gtk::TreeView deviceTree;
+ Inkscape::UI::Widget::Frame testFrame;
+ Inkscape::UI::Widget::Frame axisFrame;
+ Gtk::ScrolledWindow treeScroller;
+ Gtk::ScrolledWindow detailScroller;
+ Gtk::Paned splitter;
+ Gtk::Paned split2;
+ Gtk::Label devName;
+ Gtk::Label devKeyCount;
+ Gtk::Label devAxesCount;
+ Gtk::ComboBoxText axesCombo;
+ Gtk::ProgressBar axesValues[6];
+ Gtk::Grid axisTable;
+ Gtk::ComboBoxText buttonCombo;
+ Gtk::ComboBoxText linkCombo;
+ sigc::connection linkConnection;
+ Gtk::Label keyVal;
+ Gtk::Entry keyEntry;
+ Gtk::Notebook topHolder;
+ Gtk::Image testThumb;
+ Gtk::Image testButtons[24];
+ Gtk::Image testAxes[8];
+ Gtk::Grid imageTable;
+ Gtk::EventBox testDetector;
+
+ ConfPanel cfgPanel;
+
+
+ static void setupTree( Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeIter &tablet );
+ void setupValueAndCombo( gint reported, gint actual, Gtk::Label& label, Gtk::ComboBoxText& combo );
+ void updateTestButtons( Glib::ustring const& key, gint hotButton );
+ void updateTestAxes( Glib::ustring const& key, GdkDevice* dev );
+ void mapAxesValues( Glib::ustring const& key, gdouble const * axes, GdkDevice* dev);
+ Glib::ustring getKeyFor( GdkDevice* device );
+ bool eventSnoop(GdkEvent* event);
+ void linkComboChanged();
+ void resyncToSelection();
+ void handleDeviceChange(Glib::RefPtr<InputDevice const> device);
+ void updateDeviceAxes(Glib::RefPtr<InputDevice const> device);
+ void updateDeviceButtons(Glib::RefPtr<InputDevice const> device);
+ static void updateDeviceLinks(Glib::RefPtr<InputDevice const> device, Gtk::TreeIter tabletIter, Gtk::TreeView *tree);
+
+ static bool findDevice(const Gtk::TreeModel::iterator& iter,
+ Glib::ustring id,
+ Gtk::TreeModel::iterator* result);
+ static bool findDeviceByLink(const Gtk::TreeModel::iterator& iter,
+ Glib::ustring link,
+ Gtk::TreeModel::iterator* result);
+
+}; // class InputDialogImpl
+
+
+DeviceModelColumns &InputDialogImpl::getCols()
+{
+ static DeviceModelColumns cols;
+ return cols;
+}
+
+Glib::RefPtr<Gdk::Pixbuf> InputDialogImpl::getPix(PixId id)
+{
+ static std::map<PixId, Glib::RefPtr<Gdk::Pixbuf> > mappings;
+
+ mappings[PIX_CORE] = Gdk::Pixbuf::create_from_xpm_data(core_xpm);
+ mappings[PIX_PEN] = Gdk::Pixbuf::create_from_xpm_data(pen);
+ mappings[PIX_MOUSE] = Gdk::Pixbuf::create_from_xpm_data(mouse);
+ mappings[PIX_TIP] = Gdk::Pixbuf::create_from_xpm_data(tip);
+ mappings[PIX_TABLET] = Gdk::Pixbuf::create_from_xpm_data(tablet);
+ mappings[PIX_ERASER] = Gdk::Pixbuf::create_from_xpm_data(eraser);
+ mappings[PIX_SIDEBUTTONS] = Gdk::Pixbuf::create_from_xpm_data(sidebuttons);
+
+ mappings[PIX_BUTTONS_NONE] = Gdk::Pixbuf::create_from_xpm_data(button_none);
+ mappings[PIX_BUTTONS_ON] = Gdk::Pixbuf::create_from_xpm_data(button_on);
+ mappings[PIX_BUTTONS_OFF] = Gdk::Pixbuf::create_from_xpm_data(button_off);
+
+ mappings[PIX_AXIS_NONE] = Gdk::Pixbuf::create_from_xpm_data(axis_none_xpm);
+ mappings[PIX_AXIS_ON] = Gdk::Pixbuf::create_from_xpm_data(axis_on_xpm);
+ mappings[PIX_AXIS_OFF] = Gdk::Pixbuf::create_from_xpm_data(axis_off_xpm);
+
+ Glib::RefPtr<Gdk::Pixbuf> pix;
+ if (mappings.find(id) != mappings.end()) {
+ pix = mappings[id];
+ }
+
+ return pix;
+}
+
+
+// Now that we've defined the *Impl class, we can do the method to acquire one.
+InputDialog &InputDialog::getInstance()
+{
+ InputDialog *dialog = new InputDialogImpl();
+ return *dialog;
+}
+
+
+InputDialogImpl::InputDialogImpl() :
+ InputDialog(),
+ lastSourceSeen(static_cast<Gdk::InputSource>(-1)),
+ lastDevnameSeen(""),
+ deviceStore(Gtk::TreeStore::create(getCols())),
+ deviceIter(),
+ deviceTree(deviceStore),
+ testFrame(_("Test Area")),
+ axisFrame(_("Axis")),
+ treeScroller(),
+ detailScroller(),
+ splitter(),
+ split2(Gtk::ORIENTATION_VERTICAL),
+ axisTable(),
+ linkCombo(),
+ topHolder(),
+ imageTable(),
+ testDetector(),
+ cfgPanel()
+{
+ treeScroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ treeScroller.set_shadow_type(Gtk::SHADOW_IN);
+ treeScroller.add(deviceTree);
+ treeScroller.set_size_request(50, 0);
+
+ split2.pack1(axisFrame, false, false);
+ split2.pack2(testFrame, true, true);
+
+ splitter.pack1(treeScroller);
+ splitter.pack2(split2);
+
+ testDetector.add(imageTable);
+ testFrame.add(testDetector);
+ testThumb.set(getPix(PIX_TABLET));
+ testThumb.set_margin_top(24);
+ testThumb.set_margin_bottom(24);
+ testThumb.set_margin_start(24);
+ testThumb.set_margin_end(24);
+ testThumb.set_hexpand();
+ testThumb.set_vexpand();
+ imageTable.attach(testThumb, 0, 0, 8, 1);
+
+ {
+ guint col = 0;
+ guint row = 1;
+ for (auto & testButton : testButtons) {
+ testButton.set(getPix(PIX_BUTTONS_NONE));
+ imageTable.attach(testButton, col, row, 1, 1);
+ col++;
+ if (col > 7) {
+ col = 0;
+ row++;
+ }
+ }
+
+ col = 0;
+ for (auto & testAxe : testAxes) {
+ testAxe.set(getPix(PIX_AXIS_NONE));
+ imageTable.attach(testAxe, col * 2, row, 2, 1);
+ col++;
+ if (col > 3) {
+ col = 0;
+ row++;
+ }
+ }
+ }
+
+
+ // This is a hidden preference to enable the "hardware" details in a separate tab
+ // By default this is not available to users
+ if (Preferences::get()->getBool("/dialogs/inputdevices/test")) {
+ topHolder.append_page(cfgPanel, _("Configuration"));
+ topHolder.append_page(splitter, _("Hardware"));
+ topHolder.show_all();
+ topHolder.set_current_page(0);
+ pack_start(topHolder);
+ } else {
+ pack_start(cfgPanel);
+ }
+
+
+ int rowNum = 0;
+
+ axisFrame.add(axisTable);
+
+ Gtk::Label *lbl = Gtk::manage(new Gtk::Label(_("Link:")));
+ axisTable.attach(*lbl, 0, rowNum, 1, 1);
+ linkCombo.append(_("None"));
+ linkCombo.set_active_text(_("None"));
+ linkCombo.set_sensitive(false);
+ linkConnection = linkCombo.signal_changed().connect(sigc::mem_fun(*this, &InputDialogImpl::linkComboChanged));
+ axisTable.attach(linkCombo, 1, rowNum, 1, 1);
+ rowNum++;
+
+ lbl = Gtk::manage(new Gtk::Label(_("Axes count:")));
+ axisTable.attach(*lbl, 0, rowNum, 1, 1);
+ axisTable.attach(devAxesCount, 1, rowNum, 1, 1);
+ rowNum++;
+
+ for (auto & axesValue : axesValues) {
+ lbl = Gtk::manage(new Gtk::Label(_("axis:")));
+ lbl->set_hexpand();
+ axisTable.attach(*lbl, 0, rowNum, 1, 1);
+
+ axesValue.set_hexpand();
+ axisTable.attach(axesValue, 1, rowNum, 1, 1);
+ axesValue.set_sensitive(false);
+
+ rowNum++;
+
+
+ }
+
+ lbl = Gtk::manage(new Gtk::Label(_("Button count:")));
+
+ axisTable.attach(*lbl, 0, rowNum, 1, 1);
+ axisTable.attach(devKeyCount, 1, rowNum, 1, 1);
+
+ rowNum++;
+
+ axisTable.attach(keyVal, 0, rowNum, 2, 1);
+
+ rowNum++;
+
+ testDetector.signal_event().connect(sigc::mem_fun(*this, &InputDialogImpl::eventSnoop));
+
+ // TODO: Extension event stuff has been removed from public API in GTK+ 3
+ // Need to check that this hasn't broken anything
+ 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);
+
+ axisTable.attach(keyEntry, 0, rowNum, 2, 1);
+
+ rowNum++;
+
+
+ axisTable.set_sensitive(false);
+
+//- 16x16/devices
+// gnome-dev-mouse-optical
+// input-mouse
+// input-tablet
+// mouse
+
+ //Add the TreeView's view columns:
+ deviceTree.append_column("I", getCols().thumbnail);
+ deviceTree.append_column("Bar", getCols().description);
+
+ deviceTree.set_enable_tree_lines();
+ deviceTree.set_headers_visible(false);
+ deviceTree.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &InputDialogImpl::resyncToSelection));
+
+
+ setupTree( deviceStore, deviceIter );
+
+ Inkscape::DeviceManager::getManager().signalDeviceChanged().connect(sigc::mem_fun(*this, &InputDialogImpl::handleDeviceChange));
+ Inkscape::DeviceManager::getManager().signalAxesChanged().connect(sigc::mem_fun(*this, &InputDialogImpl::updateDeviceAxes));
+ Inkscape::DeviceManager::getManager().signalButtonsChanged().connect(sigc::mem_fun(*this, &InputDialogImpl::updateDeviceButtons));
+ Inkscape::DeviceManager::getManager().signalLinkChanged().connect(sigc::bind(sigc::ptr_fun(&InputDialogImpl::updateDeviceLinks), deviceIter, &deviceTree));
+
+ deviceTree.expand_all();
+ show_all_children();
+}
+
+class TabletTmp {
+public:
+ TabletTmp() = default;
+
+ Glib::ustring name;
+ std::list<Glib::RefPtr<InputDevice const> > devices;
+};
+
+static Glib::ustring getCommon( std::list<Glib::ustring> const &names )
+{
+ Glib::ustring result;
+
+ if ( !names.empty() ) {
+ size_t pos = 0;
+ bool match = true;
+ while ( match ) {
+ if ( names.begin()->length() > pos ) {
+ gunichar ch = (*names.begin())[pos];
+ for (const auto & name : names) {
+ if ( (pos >= name.length())
+ || (name[pos] != ch) ) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ result += ch;
+ pos++;
+ }
+ } else {
+ match = false;
+ }
+ }
+ }
+
+ return result;
+}
+
+
+void InputDialogImpl::ConfPanel::onModeChange()
+{
+ Glib::ustring newText = modeCombo.get_active_text();
+
+ Glib::RefPtr<Gtk::TreeSelection> sel = confDeviceTree.get_selection();
+ Gtk::TreeModel::iterator iter = sel->get_selected();
+ if (iter) {
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ if (dev && (getStringToMode().find(newText) != getStringToMode().end())) {
+ Gdk::InputMode mode = getStringToMode()[newText];
+ Inkscape::DeviceManager::getManager().setMode( dev->getId(), mode );
+ }
+ }
+
+}
+
+
+void InputDialogImpl::setupTree( Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeIter &tablet )
+{
+ std::list<Glib::RefPtr<InputDevice const> > devList = Inkscape::DeviceManager::getManager().getDevices();
+ if ( !devList.empty() ) {
+ //Gtk::TreeModel::Row row = *(store->append());
+ //row[getCols().description] = _("Hardware");
+
+ // Let's make some tablets!!!
+ std::list<TabletTmp> tablets;
+ std::set<Glib::ustring> consumed;
+
+ // Phase 1 - figure out which tablets are present
+ for (auto dev : devList) {
+ if ( dev ) {
+ if ( dev->getSource() != Gdk::SOURCE_MOUSE ) {
+ consumed.insert( dev->getId() );
+ if ( tablets.empty() ) {
+ TabletTmp tmp;
+ tablets.push_back(tmp);
+ }
+ tablets.back().devices.push_back(dev);
+ }
+ } else {
+ g_warning("Null device in list");
+ }
+ }
+
+ // Phase 2 - build a UI for the present devices
+ for (auto & it : tablets) {
+ tablet = store->prepend(/*row.children()*/);
+ Gtk::TreeModel::Row childrow = *tablet;
+ if ( it.name.empty() ) {
+ // Check to see if we can derive one
+ std::list<Glib::ustring> names;
+ for (auto & device : it.devices) {
+ names.push_back( device->getName() );
+ }
+ Glib::ustring common = getCommon(names);
+ if ( !common.empty() ) {
+ it.name = common;
+ }
+ }
+ childrow[getCols().description] = it.name.empty() ? _("Tablet") : it.name ;
+ childrow[getCols().thumbnail] = getPix(PIX_TABLET);
+
+ // Check if there is an eraser we can link to a pen
+ for ( std::list<Glib::RefPtr<InputDevice const> >::iterator it2 = it.devices.begin(); it2 != it.devices.end(); ++it2 ) {
+ Glib::RefPtr<InputDevice const> dev = *it2;
+ if ( dev->getSource() == Gdk::SOURCE_PEN ) {
+ for (auto dev2 : it.devices) {
+ if ( dev2->getSource() == Gdk::SOURCE_ERASER ) {
+ DeviceManager::getManager().setLinkedTo(dev->getId(), dev2->getId());
+ break; // only check the first eraser... for now
+ }
+ break; // only check the first pen... for now
+ }
+ }
+ }
+
+ for (auto dev : it.devices) {
+ Gtk::TreeModel::Row deviceRow = *(store->append(childrow.children()));
+ deviceRow[getCols().description] = dev->getName();
+ deviceRow[getCols().device] = dev;
+ deviceRow[getCols().mode] = dev->getMode();
+ switch ( dev->getSource() ) {
+ case Gdk::SOURCE_MOUSE:
+ deviceRow[getCols().thumbnail] = getPix(PIX_CORE);
+ break;
+ case Gdk::SOURCE_PEN:
+ if (deviceRow[getCols().description] == _("pad")) {
+ deviceRow[getCols().thumbnail] = getPix(PIX_SIDEBUTTONS);
+ } else {
+ deviceRow[getCols().thumbnail] = getPix(PIX_TIP);
+ }
+ break;
+ case Gdk::SOURCE_CURSOR:
+ deviceRow[getCols().thumbnail] = getPix(PIX_MOUSE);
+ break;
+ case Gdk::SOURCE_ERASER:
+ deviceRow[getCols().thumbnail] = getPix(PIX_ERASER);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ }
+
+ for (auto dev : devList) {
+ if ( dev && (consumed.find( dev->getId() ) == consumed.end()) ) {
+ Gtk::TreeModel::Row deviceRow = *(store->prepend(/*row.children()*/));
+ deviceRow[getCols().description] = dev->getName();
+ deviceRow[getCols().device] = dev;
+ deviceRow[getCols().mode] = dev->getMode();
+ deviceRow[getCols().thumbnail] = getPix(PIX_CORE);
+ }
+ }
+
+ } else {
+ g_warning("No devices found");
+ }
+}
+
+
+InputDialogImpl::ConfPanel::ConfPanel() :
+ Gtk::Box(Gtk::ORIENTATION_VERTICAL),
+ confDeviceStore(Gtk::TreeStore::create(getCols())),
+ confDeviceIter(),
+ confDeviceTree(confDeviceStore),
+ confDeviceScroller(),
+ watcher(*this),
+ useExt(_("_Use pressure-sensitive tablet (requires restart)"), true),
+ save(_("_Save"), true),
+ detailsBox(Gtk::ORIENTATION_VERTICAL, 4),
+ titleFrame(Gtk::ORIENTATION_HORIZONTAL, 4),
+ titleLabel(""),
+ axisFrame(_("Axes")),
+ keysFrame(_("Keys")),
+ modeLabel(_("Mode:")),
+ modeBox(Gtk::ORIENTATION_HORIZONTAL, 4),
+ axisVBox(Gtk::ORIENTATION_VERTICAL)
+
+{
+
+
+ confDeviceScroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ confDeviceScroller.set_shadow_type(Gtk::SHADOW_IN);
+ confDeviceScroller.add(confDeviceTree);
+ confDeviceScroller.set_size_request(120, 0);
+
+ /* class Foo : public Gtk::TreeModel::ColumnRecord {
+ public :
+ Gtk::TreeModelColumn<Glib::ustring> one;
+ Foo() {add(one);}
+ };
+ static Foo foo;
+
+ //Add the TreeView's view columns:
+ {
+ Gtk::CellRendererToggle *rendr = new Gtk::CellRendererToggle();
+ Gtk::TreeViewColumn *col = new Gtk::TreeViewColumn("xx", *rendr);
+ if (col) {
+ confDeviceTree.append_column(*col);
+ col->set_cell_data_func(*rendr, sigc::ptr_fun(setCellStateToggle));
+ rendr->signal_toggled().connect(sigc::bind(sigc::ptr_fun(commitCellStateChange), confDeviceStore));
+ }
+ }*/
+
+ //int expPos = confDeviceTree.append_column("", getCols().expander);
+
+ confDeviceTree.append_column("I", getCols().thumbnail);
+ confDeviceTree.append_column("Bar", getCols().description);
+
+ //confDeviceTree.get_column(0)->set_fixed_width(100);
+ //confDeviceTree.get_column(1)->set_expand();
+
+/* {
+ Gtk::TreeViewColumn *col = new Gtk::TreeViewColumn("X", *rendr);
+ if (col) {
+ confDeviceTree.append_column(*col);
+ col->set_cell_data_func(*rendr, sigc::ptr_fun(setModeCellString));
+ rendr->signal_edited().connect(sigc::bind(sigc::ptr_fun(commitCellModeChange), confDeviceStore));
+ rendr->property_editable() = true;
+ }
+ }*/
+
+ //confDeviceTree.set_enable_tree_lines();
+ confDeviceTree.property_enable_tree_lines() = false;
+ confDeviceTree.property_enable_grid_lines() = false;
+ confDeviceTree.set_headers_visible(false);
+ //confDeviceTree.set_expander_column( *confDeviceTree.get_column(expPos - 1) );
+
+ confDeviceTree.get_selection()->signal_changed().connect(sigc::mem_fun(*this, &InputDialogImpl::ConfPanel::onTreeSelect));
+
+ setupTree( confDeviceStore, confDeviceIter );
+
+ Inkscape::DeviceManager::getManager().signalLinkChanged().connect(sigc::bind(sigc::ptr_fun(&InputDialogImpl::updateDeviceLinks), confDeviceIter, &confDeviceTree));
+
+ confDeviceTree.expand_all();
+
+ useExt.set_active(Preferences::get()->getBool("/options/useextinput/value"));
+ useExt.signal_toggled().connect(sigc::mem_fun(*this, &InputDialogImpl::ConfPanel::useExtToggled));
+
+ auto buttonBox = Gtk::manage(new Gtk::ButtonBox);
+ buttonBox->set_layout (Gtk::BUTTONBOX_END);
+ //Gtk::Alignment *align = new Gtk::Alignment(Gtk::ALIGN_END, Gtk::ALIGN_START, 0, 0);
+ buttonBox->add(save);
+ save.signal_clicked().connect(sigc::mem_fun(*this, &InputDialogImpl::ConfPanel::saveSettings));
+
+ titleFrame.pack_start(titleLabel, true, true);
+ //titleFrame.set_shadow_type(Gtk::SHADOW_IN);
+
+ modeCombo.append(getModeToString()[Gdk::MODE_DISABLED]);
+ modeCombo.append(getModeToString()[Gdk::MODE_SCREEN]);
+ modeCombo.append(getModeToString()[Gdk::MODE_WINDOW]);
+ modeCombo.set_tooltip_text(_("A device can be 'Disabled', its coordinates mapped to the whole 'Screen', or to a single (usually focused) 'Window'"));
+ modeCombo.signal_changed().connect(sigc::mem_fun(*this, &InputDialogImpl::ConfPanel::onModeChange));
+
+ modeBox.pack_start(modeLabel, false, false);
+ modeBox.pack_start(modeCombo, true, true);
+
+ axisVBox.add(axisScroll);
+ axisFrame.add(axisVBox);
+
+ keysFrame.add(keysScroll);
+
+ /**
+ * Scrolled Window
+ */
+ keysScroll.add(keysTree);
+ keysScroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ keysScroll.set_shadow_type(Gtk::SHADOW_IN);
+ keysScroll.set_size_request(120, 80);
+
+ keysStore = Gtk::ListStore::create(keysColumns);
+
+ _kb_shortcut_renderer.property_editable() = true;
+
+ keysTree.set_model(keysStore);
+ keysTree.set_headers_visible(false);
+ keysTree.append_column("Name", keysColumns.name);
+ keysTree.append_column("Value", keysColumns.value);
+
+ //keysTree.append_column("Value", _kb_shortcut_renderer);
+ //keysTree.get_column(1)->add_attribute(_kb_shortcut_renderer.property_text(), keysColumns.value);
+ //_kb_shortcut_renderer.signal_accel_edited().connect( sigc::mem_fun(*this, &InputDialogImpl::onKBTreeEdited) );
+ //_kb_shortcut_renderer.signal_accel_cleared().connect( sigc::mem_fun(*this, &InputDialogImpl::onKBTreeCleared) );
+
+ axisStore = Gtk::ListStore::create(axisColumns);
+
+ axisTree.set_model(axisStore);
+ axisTree.set_headers_visible(false);
+ axisTree.append_column("Name", axisColumns.name);
+ axisTree.append_column("Value", axisColumns.value);
+
+ /**
+ * Scrolled Window
+ */
+ axisScroll.add(axisTree);
+ axisScroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ axisScroll.set_shadow_type(Gtk::SHADOW_IN);
+ axisScroll.set_size_request(0, 150);
+
+ pane.pack1(confDeviceScroller);
+ pane.pack2(detailsBox);
+
+ detailsBox.pack_start(titleFrame, false, false, 6);
+ detailsBox.pack_start(modeBox, false, false, 6);
+ detailsBox.pack_start(axisFrame, false, false);
+ detailsBox.pack_start(keysFrame, false, false);
+ detailsBox.set_border_width(4);
+
+ pack_start(pane, true, true);
+ pack_start(useExt, Gtk::PACK_SHRINK);
+ pack_start(*buttonBox, false, false);
+
+ // Select the first device
+ confDeviceTree.get_selection()->select(confDeviceStore->get_iter("0"));
+
+}
+
+InputDialogImpl::ConfPanel::~ConfPanel()
+= default;
+
+void InputDialogImpl::ConfPanel::setModeCellString(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter)
+{
+ if (iter) {
+ Gtk::CellRendererCombo *combo = dynamic_cast<Gtk::CellRendererCombo *>(rndr);
+ if (combo) {
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ Gdk::InputMode mode = (*iter)[getCols().mode];
+ if (dev && (getModeToString().find(mode) != getModeToString().end())) {
+ combo->property_text() = getModeToString()[mode];
+ } else {
+ combo->property_text() = "";
+ }
+ }
+ }
+}
+
+void InputDialogImpl::ConfPanel::commitCellModeChange(Glib::ustring const &path, Glib::ustring const &newText, Glib::RefPtr<Gtk::TreeStore> store)
+{
+ Gtk::TreeIter iter = store->get_iter(path);
+ if (iter) {
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ if (dev && (getStringToMode().find(newText) != getStringToMode().end())) {
+ Gdk::InputMode mode = getStringToMode()[newText];
+ Inkscape::DeviceManager::getManager().setMode( dev->getId(), mode );
+ }
+ }
+
+
+}
+
+void InputDialogImpl::ConfPanel::setCellStateToggle(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter)
+{
+ if (iter) {
+ Gtk::CellRendererToggle *toggle = dynamic_cast<Gtk::CellRendererToggle *>(rndr);
+ if (toggle) {
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ if (dev) {
+ Gdk::InputMode mode = (*iter)[getCols().mode];
+ toggle->set_active(mode != Gdk::MODE_DISABLED);
+ } else {
+ toggle->set_active(false);
+ }
+ }
+ }
+}
+
+void InputDialogImpl::ConfPanel::commitCellStateChange(Glib::ustring const &path, Glib::RefPtr<Gtk::TreeStore> store)
+{
+ Gtk::TreeIter iter = store->get_iter(path);
+ if (iter) {
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ if (dev) {
+ Gdk::InputMode mode = (*iter)[getCols().mode];
+ if (mode == Gdk::MODE_DISABLED) {
+ Inkscape::DeviceManager::getManager().setMode( dev->getId(), Gdk::MODE_SCREEN );
+ } else {
+ Inkscape::DeviceManager::getManager().setMode( dev->getId(), Gdk::MODE_DISABLED );
+ }
+ }
+ }
+}
+
+void InputDialogImpl::ConfPanel::onTreeSelect()
+{
+ Glib::RefPtr<Gtk::TreeSelection> treeSel = confDeviceTree.get_selection();
+ Gtk::TreeModel::iterator iter = treeSel->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Glib::ustring val = row[getCols().description];
+ Glib::RefPtr<InputDevice const> dev = row[getCols().device];
+ Gdk::InputMode mode = (*iter)[getCols().mode];
+ modeCombo.set_active(getModeId(mode));
+
+ titleLabel.set_markup("<b>" + row[getCols().description] + "</b>");
+
+ if (dev) {
+ setKeys(dev->getNumKeys());
+ setAxis(dev->getNumAxes());
+ }
+ }
+}
+void InputDialogImpl::ConfPanel::saveSettings()
+{
+ Inkscape::DeviceManager::getManager().saveConfig();
+}
+
+void InputDialogImpl::ConfPanel::useExtToggled()
+{
+ bool active = useExt.get_active();
+ if (active != Preferences::get()->getBool("/options/useextinput/value")) {
+ Preferences::get()->setBool("/options/useextinput/value", active);
+ if (active) {
+ // As a work-around for a common problem, enable tablet toggles on the calligraphic tool.
+ // Covered in Launchpad bug #196195.
+ Preferences::get()->setBool("/tools/tweak/usepressure", true);
+ Preferences::get()->setBool("/tools/calligraphic/usepressure", true);
+ Preferences::get()->setBool("/tools/calligraphic/usetilt", true);
+ }
+ }
+}
+
+InputDialogImpl::ConfPanel::Blink::Blink(ConfPanel &parent) :
+ Preferences::Observer("/options/useextinput/value"),
+ parent(parent)
+{
+ Preferences::get()->addObserver(*this);
+}
+
+InputDialogImpl::ConfPanel::Blink::~Blink()
+{
+ Preferences::get()->removeObserver(*this);
+}
+
+void InputDialogImpl::ConfPanel::Blink::notify(Preferences::Entry const &new_val)
+{
+ parent.useExt.set_active(new_val.getBool());
+}
+
+void InputDialogImpl::handleDeviceChange(Glib::RefPtr<InputDevice const> device)
+{
+// g_message("OUCH!!!! for %p hits %s", &device, device->getId().c_str());
+ std::vector<Glib::RefPtr<Gtk::TreeStore> > stores;
+ stores.push_back(deviceStore);
+ stores.push_back(cfgPanel.confDeviceStore);
+
+ for (auto & store : stores) {
+ Gtk::TreeModel::iterator deviceIter;
+ store->foreach_iter( sigc::bind<Glib::ustring, Gtk::TreeModel::iterator*>(
+ sigc::ptr_fun(&InputDialogImpl::findDevice),
+ device->getId(),
+ &deviceIter) );
+ if ( deviceIter ) {
+ Gdk::InputMode mode = device->getMode();
+ Gtk::TreeModel::Row row = *deviceIter;
+ if (row[getCols().mode] != mode) {
+ row[getCols().mode] = mode;
+ }
+ }
+ }
+}
+
+void InputDialogImpl::updateDeviceAxes(Glib::RefPtr<InputDevice const> device)
+{
+ gint live = device->getLiveAxes();
+
+ std::map<guint, std::pair<guint, gdouble> > existing = axesMap[device->getId()];
+ gint mask = 0x1;
+ for ( gint num = 0; num < 32; num++, mask <<= 1) {
+ if ( (mask & live) != 0 ) {
+ if ( (existing.find(num) == existing.end()) || (existing[num].first < 2) ) {
+ axesMap[device->getId()][num].first = 2;
+ axesMap[device->getId()][num].second = 0.0;
+ }
+ }
+ }
+ updateTestAxes( device->getId(), nullptr );
+}
+
+void InputDialogImpl::updateDeviceButtons(Glib::RefPtr<InputDevice const> device)
+{
+ gint live = device->getLiveButtons();
+ std::set<guint> existing = buttonMap[device->getId()];
+ gint mask = 0x1;
+ for ( gint num = 0; num < 32; num++, mask <<= 1) {
+ if ( (mask & live) != 0 ) {
+ if ( existing.find(num) == existing.end() ) {
+ buttonMap[device->getId()].insert(num);
+ }
+ }
+ }
+ updateTestButtons(device->getId(), -1);
+}
+
+
+bool InputDialogImpl::findDevice(const Gtk::TreeModel::iterator& iter,
+ Glib::ustring id,
+ Gtk::TreeModel::iterator* result)
+{
+ bool stop = false;
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ if ( dev && (dev->getId() == id) ) {
+ if ( result ) {
+ *result = iter;
+ }
+ stop = true;
+ }
+ return stop;
+}
+
+bool InputDialogImpl::findDeviceByLink(const Gtk::TreeModel::iterator& iter,
+ Glib::ustring link,
+ Gtk::TreeModel::iterator* result)
+{
+ bool stop = false;
+ Glib::RefPtr<InputDevice const> dev = (*iter)[getCols().device];
+ if ( dev && (dev->getLink() == link) ) {
+ if ( result ) {
+ *result = iter;
+ }
+ stop = true;
+ }
+ return stop;
+}
+
+void InputDialogImpl::updateDeviceLinks(Glib::RefPtr<InputDevice const> device, Gtk::TreeIter tabletIter, Gtk::TreeView *tree)
+{
+ Glib::RefPtr<Gtk::TreeStore> deviceStore = Glib::RefPtr<Gtk::TreeStore>::cast_dynamic(tree->get_model());
+
+// g_message("Links!!!! for %p hits [%s] with link of [%s]", &device, device->getId().c_str(), device->getLink().c_str());
+ Gtk::TreeModel::iterator deviceIter;
+ deviceStore->foreach_iter( sigc::bind<Glib::ustring, Gtk::TreeModel::iterator*>(
+ sigc::ptr_fun(&InputDialogImpl::findDevice),
+ device->getId(),
+ &deviceIter) );
+
+ if ( deviceIter ) {
+ // Found the device concerned. Can proceed.
+
+ if ( device->getLink().empty() ) {
+ // is now unlinked
+// g_message("Item %s is unlinked", device->getId().c_str());
+ if ( deviceIter->parent() != tabletIter ) {
+ // Not the child of the tablet. move on up
+
+ Glib::RefPtr<InputDevice const> dev = (*deviceIter)[getCols().device];
+ Glib::ustring descr = (*deviceIter)[getCols().description];
+ Glib::RefPtr<Gdk::Pixbuf> thumb = (*deviceIter)[getCols().thumbnail];
+
+ Gtk::TreeModel::Row deviceRow = *deviceStore->append(tabletIter->children());
+ deviceRow[getCols().description] = descr;
+ deviceRow[getCols().thumbnail] = thumb;
+ deviceRow[getCols().device] = dev;
+ deviceRow[getCols().mode] = dev->getMode();
+
+ Gtk::TreeModel::iterator oldParent = deviceIter->parent();
+ deviceStore->erase(deviceIter);
+ if ( oldParent->children().empty() ) {
+ deviceStore->erase(oldParent);
+ }
+ }
+ } else {
+ // is linking
+ if ( deviceIter->parent() == tabletIter ) {
+ // Simple case. Not already linked
+
+ Gtk::TreeIter newGroup = deviceStore->append(tabletIter->children());
+ (*newGroup)[getCols().description] = _("Pen");
+ (*newGroup)[getCols().thumbnail] = getPix(PIX_PEN);
+
+ Glib::RefPtr<InputDevice const> dev = (*deviceIter)[getCols().device];
+ Glib::ustring descr = (*deviceIter)[getCols().description];
+ Glib::RefPtr<Gdk::Pixbuf> thumb = (*deviceIter)[getCols().thumbnail];
+
+ Gtk::TreeModel::Row deviceRow = *deviceStore->append(newGroup->children());
+ deviceRow[getCols().description] = descr;
+ deviceRow[getCols().thumbnail] = thumb;
+ deviceRow[getCols().device] = dev;
+ deviceRow[getCols().mode] = dev->getMode();
+
+
+ Gtk::TreeModel::iterator linkIter;
+ deviceStore->foreach_iter( sigc::bind<Glib::ustring, Gtk::TreeModel::iterator*>(
+ sigc::ptr_fun(&InputDialogImpl::findDeviceByLink),
+ device->getId(),
+ &linkIter) );
+ if ( linkIter ) {
+ dev = (*linkIter)[getCols().device];
+ descr = (*linkIter)[getCols().description];
+ thumb = (*linkIter)[getCols().thumbnail];
+
+ deviceRow = *deviceStore->append(newGroup->children());
+ deviceRow[getCols().description] = descr;
+ deviceRow[getCols().thumbnail] = thumb;
+ deviceRow[getCols().device] = dev;
+ deviceRow[getCols().mode] = dev->getMode();
+ Gtk::TreeModel::iterator oldParent = linkIter->parent();
+ deviceStore->erase(linkIter);
+ if ( oldParent->children().empty() ) {
+ deviceStore->erase(oldParent);
+ }
+ }
+
+ Gtk::TreeModel::iterator oldParent = deviceIter->parent();
+ deviceStore->erase(deviceIter);
+ if ( oldParent->children().empty() ) {
+ deviceStore->erase(oldParent);
+ }
+ tree->expand_row(Gtk::TreePath(newGroup), true);
+ }
+ }
+ }
+}
+
+void InputDialogImpl::linkComboChanged() {
+ Glib::RefPtr<Gtk::TreeSelection> treeSel = deviceTree.get_selection();
+ Gtk::TreeModel::iterator iter = treeSel->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Glib::ustring val = row[getCols().description];
+ Glib::RefPtr<InputDevice const> dev = row[getCols().device];
+ if ( dev ) {
+ if ( linkCombo.get_active_row_number() == 0 ) {
+ // It is the "None" entry
+ DeviceManager::getManager().setLinkedTo(dev->getId(), "");
+ } else {
+ Glib::ustring linkName = linkCombo.get_active_text();
+ std::list<Glib::RefPtr<InputDevice const> > devList = Inkscape::DeviceManager::getManager().getDevices();
+ for ( std::list<Glib::RefPtr<InputDevice const> >::const_iterator it = devList.begin(); it != devList.end(); ++it ) {
+ if ( linkName == (*it)->getName() ) {
+ DeviceManager::getManager().setLinkedTo(dev->getId(), (*it)->getId());
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void InputDialogImpl::resyncToSelection() {
+ bool clear = true;
+ Glib::RefPtr<Gtk::TreeSelection> treeSel = deviceTree.get_selection();
+ Gtk::TreeModel::iterator iter = treeSel->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Glib::ustring val = row[getCols().description];
+ Glib::RefPtr<InputDevice const> dev = row[getCols().device];
+
+ if ( dev ) {
+ axisTable.set_sensitive(true);
+
+ linkConnection.block();
+ linkCombo.remove_all();
+ linkCombo.append(_("None"));
+ linkCombo.set_active(0);
+ if ( dev->getSource() != Gdk::SOURCE_MOUSE ) {
+ Glib::ustring linked = dev->getLink();
+ std::list<Glib::RefPtr<InputDevice const> > devList = Inkscape::DeviceManager::getManager().getDevices();
+ for ( std::list<Glib::RefPtr<InputDevice const> >::const_iterator it = devList.begin(); it != devList.end(); ++it ) {
+ if ( ((*it)->getSource() != Gdk::SOURCE_MOUSE) && ((*it) != dev) ) {
+ linkCombo.append((*it)->getName().c_str());
+ if ( (linked.length() > 0) && (linked == (*it)->getId()) ) {
+ linkCombo.set_active_text((*it)->getName().c_str());
+ }
+ }
+ }
+ linkCombo.set_sensitive(true);
+ } else {
+ linkCombo.set_sensitive(false);
+ }
+ linkConnection.unblock();
+
+ clear = false;
+ devName.set_label(row[getCols().description]);
+ axisFrame.set_label(row[getCols().description]);
+ setupValueAndCombo( dev->getNumAxes(), dev->getNumAxes(), devAxesCount, axesCombo);
+ setupValueAndCombo( dev->getNumKeys(), dev->getNumKeys(), devKeyCount, buttonCombo);
+
+
+ }
+ }
+
+ axisTable.set_sensitive(!clear);
+ if (clear) {
+ axisFrame.set_label("");
+ devName.set_label("");
+ devAxesCount.set_label("");
+ devKeyCount.set_label("");
+ }
+}
+
+void InputDialogImpl::ConfPanel::setAxis(gint count)
+{
+ /*
+ * TODO - Make each axis editable
+ */
+ axisStore->clear();
+
+ static Glib::ustring axesLabels[6] = {_("X"), _("Y"), _("Pressure"), _("X tilt"), _("Y tilt"), _("Wheel")};
+
+ for ( gint barNum = 0; barNum < static_cast<gint>(G_N_ELEMENTS(axesLabels)); barNum++ ) {
+
+ Gtk::TreeModel::Row row = *(axisStore->append());
+ row[axisColumns.name] = axesLabels[barNum];
+ if (barNum < count) {
+ row[axisColumns.value] = Glib::ustring::format(barNum+1);
+ } else {
+ row[axisColumns.value] = C_("Input device axe", "None");
+ }
+ }
+
+}
+void InputDialogImpl::ConfPanel::setKeys(gint count)
+{
+ /*
+ * TODO - Make each key assignable
+ */
+
+ keysStore->clear();
+
+ for (gint i = 0; i < count; i++) {
+ Gtk::TreeModel::Row row = *(keysStore->append());
+ row[keysColumns.name] = Glib::ustring::format(i+1);
+ row[keysColumns.value] = _("Disabled");
+ }
+
+
+}
+void InputDialogImpl::setupValueAndCombo( gint reported, gint actual, Gtk::Label& label, Gtk::ComboBoxText& combo )
+{
+ gchar *tmp = g_strdup_printf("%d", reported);
+ label.set_label(tmp);
+ g_free(tmp);
+
+ combo.remove_all();
+ for ( gint i = 1; i <= reported; ++i ) {
+ tmp = g_strdup_printf("%d", i);
+ combo.append(tmp);
+ g_free(tmp);
+ }
+
+ if ( (1 <= actual) && (actual <= reported) ) {
+ combo.set_active(actual - 1);
+ }
+}
+
+void InputDialogImpl::updateTestButtons( Glib::ustring const& key, gint hotButton )
+{
+ for ( gint i = 0; i < static_cast<gint>(G_N_ELEMENTS(testButtons)); i++ ) {
+ if ( buttonMap[key].find(i) != buttonMap[key].end() ) {
+ if ( i == hotButton ) {
+ testButtons[i].set(getPix(PIX_BUTTONS_ON));
+ } else {
+ testButtons[i].set(getPix(PIX_BUTTONS_OFF));
+ }
+ } else {
+ testButtons[i].set(getPix(PIX_BUTTONS_NONE));
+ }
+ }
+}
+
+void InputDialogImpl::updateTestAxes( Glib::ustring const& key, GdkDevice* dev )
+{
+ //static gdouble epsilon = 0.0001;
+ {
+ Glib::RefPtr<Gtk::TreeSelection> treeSel = deviceTree.get_selection();
+ Gtk::TreeModel::iterator iter = treeSel->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Glib::ustring val = row[getCols().description];
+ Glib::RefPtr<InputDevice const> idev = row[getCols().device];
+ if ( !idev || (idev->getId() != key) ) {
+ dev = nullptr;
+ }
+ }
+ }
+
+ for ( gint i = 0; i < static_cast<gint>(G_N_ELEMENTS(testAxes)); i++ ) {
+ if ( axesMap[key].find(i) != axesMap[key].end() ) {
+ switch ( axesMap[key][i].first ) {
+ case 0:
+ case 1:
+ testAxes[i].set(getPix(PIX_AXIS_NONE));
+ if ( dev && (i < static_cast<gint>(G_N_ELEMENTS(axesValues)) ) ) {
+ axesValues[i].set_sensitive(false);
+ }
+ break;
+ case 2:
+ testAxes[i].set(getPix(PIX_AXIS_OFF));
+ axesValues[i].set_sensitive(true);
+ if ( dev && (i < static_cast<gint>(G_N_ELEMENTS(axesValues)) ) ) {
+ // FIXME: Device axis ranges are inaccessible in GTK+ 3 and
+ // are deprecated in GTK+ 2. Progress-bar ranges are disabled
+ // until we find an alternative solution
+
+ // if ( (dev->axes[i].max - dev->axes[i].min) > epsilon ) {
+ axesValues[i].set_sensitive(true);
+ // axesValues[i].set_fraction( (axesMap[key][i].second- dev->axes[i].min) / (dev->axes[i].max - dev->axes[i].min) );
+ // }
+
+ gchar* str = g_strdup_printf("%f", axesMap[key][i].second);
+ axesValues[i].set_text(str);
+ g_free(str);
+ }
+ break;
+ case 3:
+ testAxes[i].set(getPix(PIX_AXIS_ON));
+ axesValues[i].set_sensitive(true);
+ if ( dev && (i < static_cast<gint>(G_N_ELEMENTS(axesValues)) ) ) {
+
+ // FIXME: Device axis ranges are inaccessible in GTK+ 3 and
+ // are deprecated in GTK+ 2. Progress-bar ranges are disabled
+ // until we find an alternative solution
+
+ // if ( (dev->axes[i].max - dev->axes[i].min) > epsilon ) {
+ axesValues[i].set_sensitive(true);
+ // axesValues[i].set_fraction( (axesMap[key][i].second- dev->axes[i].min) / (dev->axes[i].max - dev->axes[i].min) );
+ // }
+
+ gchar* str = g_strdup_printf("%f", axesMap[key][i].second);
+ axesValues[i].set_text(str);
+ g_free(str);
+ }
+ }
+
+ } else {
+ testAxes[i].set(getPix(PIX_AXIS_NONE));
+ }
+ }
+ if ( !dev ) {
+ for (auto & axesValue : axesValues) {
+ axesValue.set_fraction(0.0);
+ axesValue.set_text("");
+ axesValue.set_sensitive(false);
+ }
+ }
+}
+
+void InputDialogImpl::mapAxesValues( Glib::ustring const& key, gdouble const * axes, GdkDevice* dev )
+{
+ auto device = Glib::wrap(dev);
+ auto numAxes = device->get_n_axes();
+
+ static gdouble epsilon = 0.0001;
+ if ( (numAxes > 0) && axes) {
+ for ( guint axisNum = 0; axisNum < numAxes; axisNum++ ) {
+ // 0 == new, 1 == set value, 2 == changed value, 3 == active
+ gdouble diff = axesMap[key][axisNum].second - axes[axisNum];
+ switch(axesMap[key][axisNum].first) {
+ case 0:
+ {
+ axesMap[key][axisNum].first = 1;
+ axesMap[key][axisNum].second = axes[axisNum];
+ }
+ break;
+ case 1:
+ {
+ if ( (diff > epsilon) || (diff < -epsilon) ) {
+// g_message("Axis %d changed on %s]", axisNum, key.c_str());
+ axesMap[key][axisNum].first = 3;
+ axesMap[key][axisNum].second = axes[axisNum];
+ updateTestAxes(key, dev);
+ DeviceManager::getManager().addAxis(key, axisNum);
+ }
+ }
+ break;
+ case 2:
+ {
+ if ( (diff > epsilon) || (diff < -epsilon) ) {
+ axesMap[key][axisNum].first = 3;
+ axesMap[key][axisNum].second = axes[axisNum];
+ updateTestAxes(key, dev);
+ }
+ }
+ break;
+ case 3:
+ {
+ if ( (diff > epsilon) || (diff < -epsilon) ) {
+ axesMap[key][axisNum].second = axes[axisNum];
+ } else {
+ axesMap[key][axisNum].first = 2;
+ updateTestAxes(key, dev);
+ }
+ }
+ }
+ }
+ }
+ // std::map<Glib::ustring, std::map<guint, std::pair<guint, gdouble> > > axesMap;
+}
+
+Glib::ustring InputDialogImpl::getKeyFor( GdkDevice* device )
+{
+ Glib::ustring key;
+ auto devicemm = Glib::wrap(device);
+
+ auto source = devicemm->get_source();
+ const auto name = devicemm->get_name();
+
+ switch ( source ) {
+ case Gdk::SOURCE_MOUSE:
+ key = "M:";
+ break;
+ case Gdk::SOURCE_CURSOR:
+ key = "C:";
+ break;
+ case Gdk::SOURCE_PEN:
+ key = "P:";
+ break;
+ case Gdk::SOURCE_ERASER:
+ key = "E:";
+ break;
+ default:
+ key = "?:";
+ }
+ key += name;
+
+ return key;
+}
+
+bool InputDialogImpl::eventSnoop(GdkEvent* event)
+{
+ int modmod = 0;
+
+ auto source = lastSourceSeen;
+ Glib::ustring devName = lastDevnameSeen;
+ Glib::ustring key;
+ gint hotButton = -1;
+
+ /* Code for determining which input device caused the event fails with GTK3
+ * because event->device gives a "generic" input device, not the one that
+ * actually caused the event. See snoop_extended in desktop-events.cpp
+ */
+
+ switch ( event->type ) {
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ {
+ auto keyEvt = reinterpret_cast<GdkEventKey*>(event);
+ auto name = Gtk::AccelGroup::name(keyEvt->keyval, static_cast<Gdk::ModifierType>(keyEvt->state));
+ keyVal.set_label(name);
+// g_message("%d KEY state:0x%08x 0x%04x [%s]", keyEvt->type, keyEvt->state, keyEvt->keyval, name);
+ }
+ break;
+ case GDK_BUTTON_PRESS:
+ modmod = 1;
+ // fallthrough
+ case GDK_BUTTON_RELEASE:
+ {
+ auto btnEvt = reinterpret_cast<GdkEventButton*>(event);
+ auto device = Glib::wrap(btnEvt->device);
+ if (device) {
+ key = getKeyFor(btnEvt->device);
+ source = device->get_source();
+ devName = device->get_name();
+ mapAxesValues(key, btnEvt->axes, btnEvt->device);
+
+ if ( buttonMap[key].find(btnEvt->button) == buttonMap[key].end() ) {
+// g_message("New button found for %s = %d", key.c_str(), btnEvt->button);
+ buttonMap[key].insert(btnEvt->button);
+ DeviceManager::getManager().addButton(key, btnEvt->button);
+ }
+ hotButton = modmod ? btnEvt->button : -1;
+ updateTestButtons(key, hotButton);
+ }
+ auto name = Gtk::AccelGroup::name(0, static_cast<Gdk::ModifierType>(btnEvt->state));
+ keyVal.set_label(name);
+// g_message("%d BTN state:0x%08x %c %4d [%s] dev:%p [%s] ",
+// btnEvt->type, btnEvt->state,
+// (modmod ? '+':'-'),
+// btnEvt->button, name, btnEvt->device,
+// (btnEvt->device ? btnEvt->device->name : "null")
+
+// );
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ {
+ GdkEventMotion* btnMtn = reinterpret_cast<GdkEventMotion*>(event);
+ auto device = Glib::wrap(btnMtn->device);
+ if (device) {
+ key = getKeyFor(btnMtn->device);
+ source = device->get_source();
+ devName = device->get_name();
+ mapAxesValues(key, btnMtn->axes, btnMtn->device);
+ }
+ auto name = Gtk::AccelGroup::name(0, static_cast<Gdk::ModifierType>(btnMtn->state));
+ keyVal.set_label(name);
+// 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,
+// name, btnMtn->device,
+// (btnMtn->device ? btnMtn->device->name : "null"),
+// ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 0)) ? btnMtn->axes[0]:0),
+// ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 1)) ? btnMtn->axes[1]:0),
+// ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 2)) ? btnMtn->axes[2]:0),
+// ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 3)) ? btnMtn->axes[3]:0),
+// ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 4)) ? btnMtn->axes[4]:0),
+// ((btnMtn->device && btnMtn->axes && (btnMtn->device->num_axes > 5)) ? btnMtn->axes[5]:0)
+// );
+ }
+ break;
+ default:
+ ;// nothing
+ }
+
+
+ if ( (lastSourceSeen != source) || (lastDevnameSeen != devName) ) {
+ switch (source) {
+ case Gdk::SOURCE_MOUSE: {
+ testThumb.set(getPix(PIX_CORE));
+ break;
+ }
+ case Gdk::SOURCE_CURSOR: {
+// g_message("flip to cursor");
+ testThumb.set(getPix(PIX_MOUSE));
+ break;
+ }
+ case Gdk::SOURCE_PEN: {
+ if (devName == _("pad")) {
+// g_message("flip to pad");
+ testThumb.set(getPix(PIX_SIDEBUTTONS));
+ } else {
+// g_message("flip to pen");
+ testThumb.set(getPix(PIX_TIP));
+ }
+ break;
+ }
+ case Gdk::SOURCE_ERASER: {
+// g_message("flip to eraser");
+ testThumb.set(getPix(PIX_ERASER));
+ break;
+ }
+ /// \fixme GTK3 added new GDK_SOURCEs that should be handled here!
+ case Gdk::SOURCE_KEYBOARD:
+ case Gdk::SOURCE_TOUCHSCREEN:
+ case Gdk::SOURCE_TOUCHPAD:
+ case Gdk::SOURCE_TRACKPOINT:
+ case Gdk::SOURCE_TABLET_PAD:
+ g_warning("InputDialogImpl::eventSnoop : unhandled GDK_SOURCE type!");
+ break;
+ }
+
+ updateTestButtons(key, hotButton);
+ lastSourceSeen = source;
+ lastDevnameSeen = devName;
+ }
+
+ return false;
+}
+
+
+} // end namespace Inkscape
+} // end namespace UI
+} // end namespace Dialog
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :