summaryrefslogtreecommitdiffstats
path: root/src/ui/control-manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/control-manager.cpp')
-rw-r--r--src/ui/control-manager.cpp509
1 files changed, 509 insertions, 0 deletions
diff --git a/src/ui/control-manager.cpp b/src/ui/control-manager.cpp
new file mode 100644
index 0000000..0330b5b
--- /dev/null
+++ b/src/ui/control-manager.cpp
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Central facade for accessing and managing on-canvas controls.
+ *
+ * Author:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "control-manager.h"
+
+#include <algorithm>
+#include <set>
+
+#include <glib-object.h>
+
+#include "display/sodipodi-ctrl.h" // for SP_TYPE_CTRL
+#include "display/sp-ctrlline.h"
+#include "display/sp-ctrlcurve.h"
+#include "preferences.h"
+
+using Inkscape::ControlFlags;
+
+namespace {
+
+// Note: The following operator overloads are local to this file at the moment to discourage flag manipulation elsewhere.
+/*
+ControlFlags operator |(ControlFlags lhs, ControlFlags rhs)
+{
+ return static_cast<ControlFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
+}
+*/
+
+ControlFlags operator &(ControlFlags lhs, ControlFlags rhs)
+{
+ return static_cast<ControlFlags>(static_cast<int>(lhs) & static_cast<int>(rhs));
+}
+
+ControlFlags operator ^(ControlFlags lhs, ControlFlags rhs)
+{
+ return static_cast<ControlFlags>(static_cast<int>(lhs) ^ static_cast<int>(rhs));
+}
+
+ControlFlags& operator ^=(ControlFlags &lhs, ControlFlags rhs)
+{
+ lhs = lhs ^ rhs;
+ return lhs;
+}
+
+} // namespace
+
+// Default color for line:
+#define LINE_COLOR_PRIMARY 0x0000ff7f
+#define LINE_COLOR_SECONDARY 0xff00007f
+#define LINE_COLOR_TERTIARY 0xffff007f
+
+namespace Inkscape {
+
+class ControlManagerImpl
+{
+public:
+ ControlManagerImpl(ControlManager &manager);
+
+ ~ControlManagerImpl() = default;
+
+ SPCanvasItem *createControl(SPCanvasGroup *parent, ControlType type);
+
+ void setControlSize(int size, bool force = false);
+
+ void track(SPCanvasItem *anchor);
+
+ sigc::connection connectCtrlSizeChanged(const sigc::slot<void> &slot);
+
+ void updateItem(SPCanvasItem *item);
+
+ bool setControlType(SPCanvasItem *item, ControlType type);
+
+ bool setControlResize(SPCanvasItem *item, int ctrlResize);
+
+ void setSelected(SPCanvasItem *item, bool selected);
+
+private:
+ static void thingFinalized(gpointer data, GObject *wasObj);
+
+ void thingFinalized(GObject *wasObj);
+
+ class PrefListener : public Inkscape::Preferences::Observer
+ {
+ public:
+ PrefListener(ControlManagerImpl &manager) : Inkscape::Preferences::Observer("/options/grabsize/value"), _mgr(manager) {}
+ ~PrefListener() override = default;
+
+ void notify(Inkscape::Preferences::Entry const &val) override {
+ int size = val.getIntLimited(3, 1, 7);
+ _mgr.setControlSize(size);
+ }
+
+ ControlManagerImpl &_mgr;
+ };
+
+
+ ControlManager &_manager;
+ sigc::signal<void> _sizeChangedSignal;
+ PrefListener _prefHook;
+ int _size; // Size from the grabsize preference
+ int _resize; // Way size should change from grabsize
+ std::vector<SPCanvasItem *> _itemList;
+ std::map<Inkscape::ControlType, std::vector<unsigned int> > _sizeTable;
+ std::map<Inkscape::ControlType, GType> _typeTable;
+ std::map<Inkscape::ControlType, SPCtrlShapeType> _ctrlToShape;
+ std::set<Inkscape::ControlType> _resizeOnSelect;
+};
+
+ControlManagerImpl::ControlManagerImpl(ControlManager &manager) :
+ _manager(manager),
+ _sizeChangedSignal(),
+ _prefHook(*this),
+ _size(3),
+ _resize(3),
+ _itemList(),
+ _sizeTable()
+{
+ _typeTable[CTRL_TYPE_UNKNOWN] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_LPE] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_ADJ_HANDLE] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_ANCHOR] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_INVISIPOINT] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_NODE_AUTO] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_NODE_CUSP] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_NODE_SMOOTH] = SP_TYPE_CTRL;
+ _typeTable[CTRL_TYPE_NODE_SYMETRICAL] = SP_TYPE_CTRL;
+
+ _typeTable[CTRL_TYPE_LINE] = SP_TYPE_CTRLLINE;
+
+ // -------
+ _ctrlToShape[CTRL_TYPE_UNKNOWN] = SP_CTRL_SHAPE_DIAMOND;
+ _ctrlToShape[CTRL_TYPE_LPE] = SP_CTRL_SHAPE_DIAMOND;
+ _ctrlToShape[CTRL_TYPE_NODE_CUSP] = SP_CTRL_SHAPE_DIAMOND;
+ _ctrlToShape[CTRL_TYPE_NODE_SMOOTH] = SP_CTRL_SHAPE_SQUARE;
+ _ctrlToShape[CTRL_TYPE_NODE_AUTO] = SP_CTRL_SHAPE_CIRCLE;
+ _ctrlToShape[CTRL_TYPE_NODE_SYMETRICAL] = SP_CTRL_SHAPE_SQUARE;
+
+ _ctrlToShape[CTRL_TYPE_ADJ_HANDLE] = SP_CTRL_SHAPE_CIRCLE;
+ _ctrlToShape[CTRL_TYPE_INVISIPOINT] = SP_CTRL_SHAPE_SQUARE;
+
+ // -------
+
+ _resizeOnSelect.insert(CTRL_TYPE_NODE_AUTO);
+ _resizeOnSelect.insert(CTRL_TYPE_NODE_CUSP);
+ _resizeOnSelect.insert(CTRL_TYPE_NODE_SMOOTH);
+ _resizeOnSelect.insert(CTRL_TYPE_NODE_SYMETRICAL);
+
+ // -------
+
+ // The size of the controls is determined by the grabsize preference; see the "Handle size" parameter in
+ // the input/output group, on the "input devices" tab; this parameter ranges from 1 to 7; When selecting a control, we
+ // increase the size by an additional 2 pixels, if _resizeOnSelect is true (see setSelected())
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->addObserver(_prefHook);
+
+ _size = prefs->getIntLimited("/options/grabsize/value", 3, 1, 7);
+
+ // _sizeTable will have odd numbers, which allow for pixel perfect alignment (e.g. relative to grids
+ // or guides, which are 1 px wide. It is not possible to accurately center a control to them if the
+ // control has an even width).
+ {
+ unsigned int sizes[] = {7, 7, 7, 7, 7, 7, 7};
+ _sizeTable[CTRL_TYPE_UNKNOWN] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = {3, 5, 7, 9, 11, 13, 15};
+ _sizeTable[CTRL_TYPE_ADJ_HANDLE] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = {3, 5, 7, 9, 11, 13, 15};
+ _sizeTable[CTRL_TYPE_ANCHOR] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = {5, 7, 9, 11, 13, 15, 17};
+ _sizeTable[CTRL_TYPE_POINT] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ _sizeTable[CTRL_TYPE_ROTATE] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ _sizeTable[CTRL_TYPE_SIZER] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ _sizeTable[CTRL_TYPE_SHAPER] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = {5, 7, 9, 11, 13, 15, 17};
+ _sizeTable[CTRL_TYPE_NODE_AUTO] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ _sizeTable[CTRL_TYPE_NODE_CUSP] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = {3, 5, 7, 9, 11, 13, 15};
+ _sizeTable[CTRL_TYPE_NODE_SMOOTH] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ _sizeTable[CTRL_TYPE_NODE_SYMETRICAL] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = {1, 1, 1, 1, 1, 1, 1};
+ _sizeTable[CTRL_TYPE_INVISIPOINT] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+ {
+ unsigned int sizes[] = { 5, 7, 9, 11, 13, 15, 17 };
+ _sizeTable[CTRL_TYPE_LPE] = std::vector<unsigned int>(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0])));
+ }
+}
+
+
+void ControlManagerImpl::setControlSize(int size, bool force)
+{
+ if ((size < 1) || (size > 7)) {
+ g_warning("Illegal logical size set: %d", size);
+ } else if (force || (size != _size)) {
+ _size = size;
+
+ for (auto & it : _itemList)
+ {
+ if (it) {
+ updateItem(it);
+ }
+ }
+
+ //_sizeChangedSignal.emit();
+ }
+}
+
+SPCanvasItem *ControlManagerImpl::createControl(SPCanvasGroup *parent, ControlType type)
+{
+ SPCanvasItem *item = nullptr;
+ unsigned int targetSize = _sizeTable[type][_size - 1];
+ switch (type)
+ {
+ case CTRL_TYPE_ADJ_HANDLE:
+ item = sp_canvas_item_new(parent, SP_TYPE_CTRL,
+ "shape",SP_CTRL_SHAPE_CIRCLE,
+ "size", targetSize,
+ "filled", 1,
+ "fill_color", 0xffffff7f,
+ "stroked", 1,
+ "stroke_color", 0x0000ff7f,
+ NULL);
+ break;
+ case CTRL_TYPE_ANCHOR:
+ item = sp_canvas_item_new(parent, SP_TYPE_CTRL,
+ "size", targetSize,
+ "filled", 1,
+ "fill_color", 0xffffff7f,
+ "stroked", 1,
+ "stroke_color", 0x000000ff,
+ NULL);
+ break;
+ case CTRL_TYPE_NODE_AUTO:
+ case CTRL_TYPE_NODE_CUSP:
+ case CTRL_TYPE_NODE_SMOOTH:
+ case CTRL_TYPE_NODE_SYMETRICAL:
+ {
+ SPCtrlShapeType shape = _ctrlToShape[_ctrlToShape.count(type) ? type : CTRL_TYPE_UNKNOWN];
+ item = sp_canvas_item_new(parent, SP_TYPE_CTRL,
+ "shape", shape,
+ "size", targetSize,
+ NULL);
+ break;
+ }
+ case CTRL_TYPE_INVISIPOINT:
+ item = sp_canvas_item_new(parent, SP_TYPE_CTRL,
+ "shape", SP_CTRL_SHAPE_SQUARE,
+ "size", targetSize,
+ NULL);
+ break;
+ case CTRL_TYPE_UNKNOWN:
+ case CTRL_TYPE_LPE:
+ default:
+ item = sp_canvas_item_new(parent, SP_TYPE_CTRL, nullptr);
+ }
+ if (item) {
+ item->ctrlType = type;
+ }
+ return item;
+}
+
+void ControlManagerImpl::track(SPCanvasItem *item)
+{
+ g_object_weak_ref( G_OBJECT(item), ControlManagerImpl::thingFinalized, this );
+
+ _itemList.push_back(item);
+
+ setControlSize(_size, true);
+}
+
+sigc::connection ControlManagerImpl::connectCtrlSizeChanged(const sigc::slot<void> &slot)
+{
+ return _sizeChangedSignal.connect(slot);
+}
+
+void ControlManagerImpl::updateItem(SPCanvasItem *item)
+{
+ if (item) {
+ unsigned int target = _sizeTable[item->ctrlType][_size - 1] + item->ctrlResize;
+ g_object_set(item, "size", target, NULL);
+
+ sp_canvas_item_request_update(item);
+ }
+}
+
+bool ControlManagerImpl::setControlType(SPCanvasItem *item, ControlType type)
+{
+ bool accepted = false;
+ if (item && (item->ctrlType == type)) {
+ // nothing to do
+ accepted = true;
+ } else if (item) {
+ if (_ctrlToShape.count(type) && (_typeTable[type] == _typeTable[item->ctrlType])) { // compatible?
+ unsigned int targetSize = _sizeTable[type][_size - 1] + item->ctrlResize;
+ SPCtrlShapeType targetShape = _ctrlToShape[type];
+
+ g_object_set(item, "shape", targetShape, "size", targetSize, NULL);
+ item->ctrlType = type;
+ accepted = true;
+ }
+ }
+
+ return accepted;
+}
+
+bool ControlManagerImpl::setControlResize(SPCanvasItem *item, int ctrlResize)
+{
+ // _sizeTable will have odd numbers, which allow for pixel perfect alignment (e.g. relative to grids
+ // or guides, which are 1 px wide. It is not possible to accurately center a control to them if the
+ // control has an even width). ctrlResize should therefore be an even number, such that the sum (targetSize)
+ // is also odd
+ if(item) {
+ item->ctrlResize = ctrlResize;
+ unsigned int targetSize = _sizeTable[item->ctrlType][_size - 1] + item->ctrlResize;
+ g_object_set(item, "size", targetSize, NULL);
+ return true;
+ }
+ return false;
+}
+
+void ControlManagerImpl::setSelected(SPCanvasItem *item, bool selected)
+{
+ if (_manager.isSelected(item) != selected) {
+ item->ctrlFlags ^= CTRL_FLAG_SELECTED; // toggle, since we know it is different
+
+ if (selected && _resizeOnSelect.count(item->ctrlType)) {
+ item->ctrlResize = 2;
+ } else {
+ item->ctrlResize = 0;
+ }
+
+ // TODO refresh colors
+ unsigned int targetSize = _sizeTable[item->ctrlType][_size - 1] + item->ctrlResize;
+ g_object_set(item, "size", targetSize, NULL);
+ }
+}
+
+void ControlManagerImpl::thingFinalized(gpointer data, GObject *wasObj)
+{
+ if (data) {
+ reinterpret_cast<ControlManagerImpl *>(data)->thingFinalized(wasObj);
+ }
+}
+
+void ControlManagerImpl::thingFinalized(GObject *wasObj)
+{
+ SPCanvasItem *wasItem = reinterpret_cast<SPCanvasItem *>(wasObj);
+ if (wasItem)
+ {
+ std::vector<SPCanvasItem *>::iterator it = std::find(_itemList.begin(), _itemList.end(), wasItem);
+ if (it != _itemList.end())
+ {
+ _itemList.erase(it);
+ }
+ }
+}
+
+
+// ----------------------------------------------------
+
+ControlManager::ControlManager() :
+ _impl(new ControlManagerImpl(*this))
+{
+}
+
+ControlManager::~ControlManager()
+= default;
+
+ControlManager &ControlManager::getManager()
+{
+ static ControlManager instance;
+
+ return instance;
+}
+
+
+SPCanvasItem *ControlManager::createControl(SPCanvasGroup *parent, ControlType type)
+{
+ return _impl->createControl(parent, type);
+}
+
+SPCtrlLine *ControlManager::createControlLine(SPCanvasGroup *parent, CtrlLineType type)
+{
+ SPCtrlLine *line = SP_CTRLLINE(sp_canvas_item_new(parent, SP_TYPE_CTRLLINE, nullptr));
+ if (line) {
+ line->ctrlType = CTRL_TYPE_LINE;
+
+ line->setRgba32((type == CTLINE_PRIMARY) ? LINE_COLOR_PRIMARY :
+ (type == CTLINE_SECONDARY) ? LINE_COLOR_SECONDARY : LINE_COLOR_TERTIARY);
+ line->setCoords(0, 0, 0, 0);
+ }
+ return line;
+}
+
+SPCtrlLine *ControlManager::createControlLine(SPCanvasGroup *parent, Geom::Point const &p1, Geom::Point const &p2, CtrlLineType type)
+{
+ SPCtrlLine *line = createControlLine(parent, type);
+ if (line) {
+ line->setCoords(p1, p2);
+ }
+ return line;
+}
+
+SPCtrlCurve *ControlManager::createControlCurve(SPCanvasGroup *parent, Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3, CtrlLineType type)
+{
+ SPCtrlCurve *line = SP_CTRLCURVE(sp_canvas_item_new(parent, SP_TYPE_CTRLCURVE, nullptr));
+ if (line) {
+ line->ctrlType = CTRL_TYPE_LINE;
+
+ line->setRgba32((type == CTLINE_PRIMARY) ? LINE_COLOR_PRIMARY :
+ (type == CTLINE_SECONDARY) ? LINE_COLOR_SECONDARY : LINE_COLOR_TERTIARY);
+ line->setCoords(p0, p1, p2, p3);
+ }
+ return line;
+}
+
+void ControlManager::track(SPCanvasItem *item)
+{
+ _impl->track(item);
+}
+
+sigc::connection ControlManager::connectCtrlSizeChanged(const sigc::slot<void> &slot)
+{
+ return _impl->connectCtrlSizeChanged(slot);
+}
+
+void ControlManager::updateItem(SPCanvasItem *item)
+{
+ return _impl->updateItem(item);
+}
+
+bool ControlManager::setControlType(SPCanvasItem *item, ControlType type)
+{
+ return _impl->setControlType(item, type);
+}
+
+bool ControlManager::setControlResize(SPCanvasItem *item, int ctrlResize)
+{
+ return _impl->setControlResize(item, ctrlResize);
+}
+
+bool ControlManager::isActive(SPCanvasItem *item) const
+{
+ return (item->ctrlFlags & CTRL_FLAG_ACTIVE) != 0;
+}
+
+void ControlManager::setActive(SPCanvasItem *item, bool active)
+{
+ if (isActive(item) != active) {
+ item->ctrlFlags ^= CTRL_FLAG_ACTIVE; // toggle, since we know it is different
+ // TODO refresh size/colors
+ }
+}
+
+bool ControlManager::isPrelight(SPCanvasItem *item) const
+{
+ return (item->ctrlFlags & CTRL_FLAG_PRELIGHT) != 0;
+}
+
+void ControlManager::setPrelight(SPCanvasItem *item, bool prelight)
+{
+ if (isPrelight(item) != prelight) {
+ item->ctrlFlags ^= CTRL_FLAG_PRELIGHT; // toggle, since we know it is different
+ // TODO refresh size/colors
+ }
+}
+
+bool ControlManager::isSelected(SPCanvasItem *item) const
+{
+ return (item->ctrlFlags & CTRL_FLAG_SELECTED) != 0;
+}
+
+void ControlManager::setSelected(SPCanvasItem *item, bool selected)
+{
+ _impl->setSelected(item, selected);
+}
+
+} // namespace Inkscape
+
+/*
+ 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 :