diff options
Diffstat (limited to 'src/ui/dialog/input.cpp')
-rw-r--r-- | src/ui/dialog/input.cpp | 1798 |
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 : |