// 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 #include #include #include "ui/widget/frame.h" #include "ui/widget/scrollprotected.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 toggler; Gtk::TreeModelColumn expander; Gtk::TreeModelColumn description; Gtk::TreeModelColumn > thumbnail; Gtk::TreeModelColumn > device; Gtk::TreeModelColumn mode; DeviceModelColumns() { add(toggler), add(expander), add(description); add(thumbnail); add(device); add(mode); } }; static std::map &getModeToString() { static std::map 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 &getStringToMode() { static std::map 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 store); static void setModeCellString(Gtk::CellRenderer *rndr, Gtk::TreeIter const &iter); static void commitCellStateChange(Glib::ustring const &path, Glib::RefPtr 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 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 modeCombo; Gtk::Label modeLabel; Gtk::Box modeBox; class KeysColumns : public Gtk::TreeModel::ColumnRecord { public: KeysColumns() { add(name); add(value); } ~KeysColumns() override = default; Gtk::TreeModelColumn name; Gtk::TreeModelColumn value; }; KeysColumns keysColumns; KeysColumns axisColumns; Glib::RefPtr axisStore; Gtk::TreeView axisTree; Gtk::ScrolledWindow axisScroll; Glib::RefPtr 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 getPix(PixId id); std::map > buttonMap; std::map > > axesMap; Gdk::InputSource lastSourceSeen; Glib::ustring lastDevnameSeen; Glib::RefPtr 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 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 device); void updateDeviceAxes(Glib::RefPtr device); void updateDeviceButtons(Glib::RefPtr device); static void updateDeviceLinks(Glib::RefPtr 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 InputDialogImpl::getPix(PixId id) { static std::map > 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 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(-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 > devices; }; static Glib::ustring getCommon( std::list 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 sel = confDeviceTree.get_selection(); Gtk::TreeModel::iterator iter = sel->get_selected(); if (iter) { Glib::RefPtr 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 store, Gtk::TreeIter &tablet ) { std::list > 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 tablets; std::set 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 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 >::iterator it2 = it.devices.begin(); it2 != it.devices.end(); ++it2 ) { Glib::RefPtr 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 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(rndr); if (combo) { Glib::RefPtr 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 store) { Gtk::TreeIter iter = store->get_iter(path); if (iter) { Glib::RefPtr 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(rndr); if (toggle) { Glib::RefPtr 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 store) { Gtk::TreeIter iter = store->get_iter(path); if (iter) { Glib::RefPtr 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 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 dev = row[getCols().device]; Gdk::InputMode mode = (*iter)[getCols().mode]; modeCombo.set_active(getModeId(mode)); titleLabel.set_markup("" + row[getCols().description] + ""); 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 device) { // g_message("OUCH!!!! for %p hits %s", &device, device->getId().c_str()); std::vector > stores; stores.push_back(deviceStore); stores.push_back(cfgPanel.confDeviceStore); for (auto & store : stores) { Gtk::TreeModel::iterator deviceIter; store->foreach_iter( sigc::bind( 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 device) { gint live = device->getLiveAxes(); std::map > 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 device) { gint live = device->getLiveButtons(); std::set 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 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 dev = (*iter)[getCols().device]; if ( dev && (dev->getLink() == link) ) { if ( result ) { *result = iter; } stop = true; } return stop; } void InputDialogImpl::updateDeviceLinks(Glib::RefPtr device, Gtk::TreeIter tabletIter, Gtk::TreeView *tree) { Glib::RefPtr deviceStore = Glib::RefPtr::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( 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 dev = (*deviceIter)[getCols().device]; Glib::ustring descr = (*deviceIter)[getCols().description]; Glib::RefPtr 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 dev = (*deviceIter)[getCols().device]; Glib::ustring descr = (*deviceIter)[getCols().description]; Glib::RefPtr 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( 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 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 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 > devList = Inkscape::DeviceManager::getManager().getDevices(); for ( std::list >::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 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 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 > devList = Inkscape::DeviceManager::getManager().getDevices(); for ( std::list >::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(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(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 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 idev = row[getCols().device]; if ( !idev || (idev->getId() != key) ) { dev = nullptr; } } } for ( gint i = 0; i < static_cast(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(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(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(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 > > 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(event); auto name = Gtk::AccelGroup::name(keyEvt->keyval, static_cast(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(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(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(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(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 :