1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** @file
3  * @brief A dialog for CSS selectors
4  */
5 /* Authors:
6  *   Kamalpreet Kaur Grewal
7  *   Tavmjong Bah
8  *
9  * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
10  * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
11  *
12  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13  */
14 
15 #ifndef SELECTORSDIALOG_H
16 #define SELECTORSDIALOG_H
17 
18 #include <gtkmm/dialog.h>
19 #include <gtkmm/paned.h>
20 #include <gtkmm/radiobutton.h>
21 #include <gtkmm/scrolledwindow.h>
22 #include <gtkmm/switch.h>
23 #include <gtkmm/treemodelfilter.h>
24 #include <gtkmm/treeselection.h>
25 #include <gtkmm/treestore.h>
26 #include <gtkmm/treeview.h>
27 #include <memory>
28 #include <vector>
29 
30 #include "ui/dialog/dialog-base.h"
31 #include "ui/dialog/styledialog.h"
32 #include "xml/helper-observer.h"
33 
34 namespace Inkscape {
35 namespace UI {
36 namespace Dialog {
37 
38 /**
39  * @brief The SelectorsDialog class
40  * A list of CSS selectors will show up in this dialog. This dialog allows one to
41  * add and delete selectors. Elements can be added to and removed from the selectors
42  * in the dialog. Selection of any selector row selects the matching  objects in
43  * the drawing and vice-versa. (Only simple selectors supported for now.)
44  *
45  * This class must keep two things in sync:
46  *   1. The text node of the style element.
47  *   2. The Gtk::TreeModel.
48  */
49 class SelectorsDialog : public DialogBase
50 {
51 public:
52     ~SelectorsDialog() override;
53     // No default constructor, noncopyable, nonassignable
54     SelectorsDialog();
55     SelectorsDialog(SelectorsDialog const &d) = delete;
56     SelectorsDialog operator=(SelectorsDialog const &d) = delete;
getInstance()57     static SelectorsDialog &getInstance() { return *new SelectorsDialog(); }
58 
59   private:
60     // Monitor <style> element for changes.
61     class NodeObserver;
62 
63     // Monitor all objects for addition/removal/attribute change
64     class NodeWatcher;
65     enum SelectorType { CLASS, ID, TAG };
66     void _nodeAdded(   Inkscape::XML::Node &repr );
67     void _nodeRemoved( Inkscape::XML::Node &repr );
68     void _nodeChanged( Inkscape::XML::Node &repr );
69     // Data structure
70     enum coltype { OBJECT, SELECTOR, OTHER };
71     class ModelColumns : public Gtk::TreeModel::ColumnRecord {
72     public:
ModelColumns()73         ModelColumns() {
74             add(_colSelector);
75             add(_colExpand);
76             add(_colType);
77             add(_colObj);
78             add(_colProperties);
79             add(_colVisible);
80             add(_colSelected);
81         }
82         Gtk::TreeModelColumn<Glib::ustring> _colSelector;       // Selector or matching object id.
83         Gtk::TreeModelColumn<bool> _colExpand;                  // Open/Close store row.
84         Gtk::TreeModelColumn<gint> _colType;                    // Selector row or child object row.
85         Gtk::TreeModelColumn<SPObject *> _colObj;               // Matching object (if any).
86         Gtk::TreeModelColumn<Glib::ustring> _colProperties;     // List of properties.
87         Gtk::TreeModelColumn<bool> _colVisible;                 // Make visible or not.
88         Gtk::TreeModelColumn<gint> _colSelected;                // Make selected.
89     };
90     ModelColumns _mColumns;
91 
92     // Override Gtk::TreeStore to control drag-n-drop (only allow dragging and dropping of selectors).
93     // See: https://developer.gnome.org/gtkmm-tutorial/stable/sec-treeview-examples.html.en
94     //
95     // TreeStore implements simple drag and drop (DND) but there appears no way to know when a DND
96     // has been completed (other than doing the whole DND ourselves). As a hack, we use
97     // on_row_deleted to trigger write of style element.
98     class TreeStore : public Gtk::TreeStore {
99     protected:
100         TreeStore();
101         bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override;
102         bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& path,
103                                      const Gtk::SelectionData& selection_data) const override;
104         void on_row_deleted(const TreeModel::Path& path) override;
105 
106     public:
107       static Glib::RefPtr<SelectorsDialog::TreeStore> create(SelectorsDialog *styledialog);
108 
109     private:
110       SelectorsDialog *_selectorsdialog;
111     };
112 
113     // TreeView
114     Glib::RefPtr<Gtk::TreeModelFilter> _modelfilter;
115     Glib::RefPtr<TreeStore> _store;
116     Gtk::TreeView _treeView;
117     Gtk::TreeModel::Path _lastpath;
118     // Widgets
119     StyleDialog *_style_dialog;
120     Gtk::Paned _paned;
121     Glib::RefPtr<Gtk::Adjustment> _vadj;
122     Gtk::Box _button_box;
123     Gtk::Box _selectors_box;
124     Gtk::ScrolledWindow _scrolled_window_selectors;
125 
126     Gtk::Button _del;
127     Gtk::Button _create;
128     // Reading and writing the style element.
129     Inkscape::XML::Node *_getStyleTextNode(bool create_if_missing = false);
130     void _readStyleElement();
131     void _writeStyleElement();
132 
133     // Update watchers
134     std::unique_ptr<Inkscape::XML::NodeObserver> m_nodewatcher;
135     std::unique_ptr<Inkscape::XML::NodeObserver> m_styletextwatcher;
136     void _updateWatchers(SPDesktop *);
137 
138     // Manipulate Tree
139     void _addToSelector(Gtk::TreeModel::Row row);
140     void _removeFromSelector(Gtk::TreeModel::Row row);
141     Glib::ustring _getIdList(std::vector<SPObject *>);
142     std::vector<SPObject *> _getObjVec(Glib::ustring selector);
143     void _insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className);
144     void _insertClass(SPObject *obj, const Glib::ustring &className);
145     void _removeClass(const std::vector<SPObject *> &objVec, const Glib::ustring &className, bool all = false);
146     void _removeClass(SPObject *obj, const Glib::ustring &className, bool all = false);
147     void _toggleDirection(Gtk::RadioButton *vertical);
148     void _showWidgets();
149     void _resized();
150     void _childresized();
151     void _panedresized(Gtk::Allocation allocation);
152 
153     void _selectObjects(int, int);
154     // Variables
155     double _scroolpos;
156     bool _scroollock;
157     bool _updating;                 // Prevent cyclic actions: read <-> write, select via dialog <-> via desktop
158     Inkscape::XML::Node *m_root = nullptr;
159     Inkscape::XML::Node *_textNode; // Track so we know when to add a NodeObserver.
160 
161     void update() override;
162     void _handleSelectionChanged();
163     void _rowExpand(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
164     void _rowCollapse(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
165     void _closeDialog(Gtk::Dialog *textDialogPtr);
166 
167     Inkscape::XML::SignalObserver _objObserver; // Track object in selected row (for style change).
168 
169     // Signal and handlers - Internal
170     void _addSelector();
171     void _delSelector();
172     bool _handleButtonEvent(GdkEventButton *event);
173     void _buttonEventsSelectObjs(GdkEventButton *event);
174     void _selectRow(); // Select row in tree when selection changed.
175     void _vscrool();
176 
177     // GUI
178     void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip);
179 };
180 
181 } // namespace Dialogc
182 } // namespace UI
183 } // namespace Inkscape
184 
185 #endif // SELECTORSDIALOG_H
186 
187 /*
188   Local Variables:
189   mode:c++
190   c-file-style:"stroustrup"
191   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
192   indent-tabs-mode:nil
193   fill-column:99
194   End:
195 */
196 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
197