// SPDX-License-Identifier: GPL-2.0-or-later /* * Central facade for accessing and managing on-canvas controls. * * Author: * Jon A. Cruz * * Copyright 2012 Authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "control-manager.h" #include #include #include #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(static_cast(lhs) | static_cast(rhs)); } */ ControlFlags operator &(ControlFlags lhs, ControlFlags rhs) { return static_cast(static_cast(lhs) & static_cast(rhs)); } ControlFlags operator ^(ControlFlags lhs, ControlFlags rhs) { return static_cast(static_cast(lhs) ^ static_cast(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 &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 _sizeChangedSignal; PrefListener _prefHook; int _size; // Size from the grabsize preference int _resize; // Way size should change from grabsize std::vector _itemList; std::map > _sizeTable; std::map _typeTable; std::map _ctrlToShape; std::set _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(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = {3, 5, 7, 9, 11, 13, 15}; _sizeTable[CTRL_TYPE_ADJ_HANDLE] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = {3, 5, 7, 9, 11, 13, 15}; _sizeTable[CTRL_TYPE_ANCHOR] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = {5, 7, 9, 11, 13, 15, 17}; _sizeTable[CTRL_TYPE_POINT] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); _sizeTable[CTRL_TYPE_ROTATE] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); _sizeTable[CTRL_TYPE_SIZER] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); _sizeTable[CTRL_TYPE_SHAPER] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = {5, 7, 9, 11, 13, 15, 17}; _sizeTable[CTRL_TYPE_NODE_AUTO] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); _sizeTable[CTRL_TYPE_NODE_CUSP] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = {3, 5, 7, 9, 11, 13, 15}; _sizeTable[CTRL_TYPE_NODE_SMOOTH] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); _sizeTable[CTRL_TYPE_NODE_SYMETRICAL] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = {1, 1, 1, 1, 1, 1, 1}; _sizeTable[CTRL_TYPE_INVISIPOINT] = std::vector(sizes, sizes + (sizeof(sizes) / sizeof(sizes[0]))); } { unsigned int sizes[] = { 5, 7, 9, 11, 13, 15, 17 }; _sizeTable[CTRL_TYPE_LPE] = std::vector(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 &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(data)->thingFinalized(wasObj); } } void ControlManagerImpl::thingFinalized(GObject *wasObj) { SPCanvasItem *wasItem = reinterpret_cast(wasObj); if (wasItem) { std::vector::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 &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 :