17#include <glibmm/i18n.h>
18#include <glibmm/main.h>
19#include <glibmm/ustring.h>
20#include <pango/pango-utils.h>
21#include <gtkmm/builder.h>
22#include <gtkmm/button.h>
23#include <gtkmm/cellrenderer.h>
24#include <gtkmm/checkbutton.h>
25#include <gtkmm/dragsource.h>
26#include <gtkmm/droptarget.h>
27#include <gtkmm/enums.h>
28#include <gtkmm/eventcontrollerkey.h>
29#include <gtkmm/eventcontrollermotion.h>
30#include <gtkmm/gestureclick.h>
31#include <gtkmm/icontheme.h>
32#include <gtkmm/popover.h>
33#include <gtkmm/scale.h>
34#include <gtkmm/searchentry2.h>
35#include <gtkmm/separator.h>
36#include <gtkmm/togglebutton.h>
37#include <gtkmm/treestore.h>
38#include <gtkmm/treeview.h>
102void connect_on_window_when_mapped(Glib::RefPtr<Gtk::EventController> controller, Gtk::Widget &widget)
104 auto const on_map = [controller, &widget] {
105 auto& window =
dynamic_cast<Gtk::Window &
>(*widget.get_root());
106 window.add_controller(controller);
108 auto const on_unmap = [controller, &widget] {
109 auto& window =
dynamic_cast<Gtk::Window &
>(*widget.get_root());
110 window.remove_controller(controller);
112 widget.signal_map().connect(on_map);
113 widget.signal_unmap().connect(on_unmap);
122class ObjectsPanel::TreeViewWithCssChanged final
124 ,
public Gtk::TreeView
127 TreeViewWithCssChanged()
128 : Glib::ObjectBase{
"TreeViewWithCssChanged"}
134 auto connect_css_changed(sigc::slot<
void (GtkCssStyleChange *)> slot)
136 return _signal.connect(std::move(slot));
140 sigc::signal<void (GtkCssStyleChange *)> _signal;
142 void css_changed(GtkCssStyleChange *
const change)
final
144 _signal.emit(change);
151 ObjectWatcher(ObjectsPanel *panel,
SPItem *, Gtk::TreeRow *row,
bool is_filtered);
152 ~ObjectWatcher()
override;
155 void updateRowInfo();
156 void updateRowHighlight();
157 void updateRowAncestorState(
bool invisible,
bool locked);
158 void updateRowBg(
guint32 rgba = 0.0);
160 ObjectWatcher *findChild(
Node *
node);
161 void addDummyChild();
162 bool addChild(
SPItem *,
bool dummy =
true);
163 void addChildren(
SPItem *,
bool dummy =
false);
167 void rememberExtendedItems();
169 bool isFiltered()
const {
return is_filtered; }
171 Gtk::TreeNodeChildren getChildren()
const;
172 Gtk::TreeModel::iterator getChildIter(
Node *)
const;
180 void setRow(
const Gtk::TreeModel::Path &path)
183 row_ref = Gtk::TreeModel::RowReference(panel->_store, path);
185 void setRow(
const Gtk::TreeModel::Row &row)
187 setRow(panel->_store->get_path(row.get_iter()));
191 Gtk::TreeModel::Path getTreePath()
const {
194 return row_ref.get_path();
198 bool hasRow()
const {
return bool(row_ref); }
201 void transferChild(
Node *childnode)
203 auto *target = panel->getWatcher(childnode->parent());
204 assert(target !=
this);
205 auto nh = child_watchers.extract(childnode);
207 bool inserted = target->child_watchers.insert(std::move(nh)).inserted;
212 Node *getRepr()
const {
return node; }
213 std::optional<Gtk::TreeRow> getRow()
const {
214 if (
auto path = row_ref.get_path()) {
215 if(
auto iter = panel->_store->get_iter(path)) {
222 std::unordered_map<Node const *, std::unique_ptr<ObjectWatcher>> child_watchers;
226 Gtk::TreeModel::RowReference row_ref;
232class ObjectsPanel::ModelColumns final :
public Gtk::TreeModel::ColumnRecord
245 add(_colAncestorInvisible);
246 add(_colAncestorLocked);
248 add(_colItemStateSet);
255 Gtk::TreeModelColumn<Node*> _colNode;
256 Gtk::TreeModelColumn<Glib::ustring> _colLabel;
257 Gtk::TreeModelColumn<Glib::ustring> _colType;
258 Gtk::TreeModelColumn<unsigned int> _colIconColor;
259 Gtk::TreeModelColumn<unsigned int> _colClipMask;
260 Gtk::TreeModelColumn<Gdk::RGBA> _colBgColor;
261 Gtk::TreeModelColumn<bool> _colInvisible;
262 Gtk::TreeModelColumn<bool> _colLocked;
263 Gtk::TreeModelColumn<bool> _colAncestorInvisible;
264 Gtk::TreeModelColumn<bool> _colAncestorLocked;
265 Gtk::TreeModelColumn<bool> _colHover;
266 Gtk::TreeModelColumn<bool> _colItemStateSet;
267 Gtk::TreeModelColumn<SPBlendMode> _colBlendMode;
268 Gtk::TreeModelColumn<double> _colOpacity;
269 Gtk::TreeModelColumn<Glib::ustring> _colItemState;
271 Gtk::TreeModelColumn<bool> _colHoverColor;
283ObjectWatcher::ObjectWatcher(ObjectsPanel* panel,
SPItem* obj, Gtk::TreeRow *row,
bool filtered)
287 , is_filtered(filtered)
291 assert(row->children().empty());
299 if (!is<SPGroup>(obj)) {
305 addChildren(obj, (
bool)row && !obj->
isExpanded());
308ObjectWatcher::~ObjectWatcher()
311 Gtk::TreeModel::Path path;
312 if (
bool(row_ref) && (path = row_ref.get_path())) {
313 if (
auto iter = panel->_store->get_iter(path)) {
314 panel->_store->erase(iter);
317 child_watchers.clear();
320void ObjectWatcher::initRowInfo()
322 auto const _model = panel->_model.get();
323 auto row = *panel->_store->get_iter(row_ref.get_path());
324 row[_model->_colHover] =
false;
330void ObjectWatcher::updateRowInfo()
332 if (
auto item = cast<SPItem>(panel->getObject(
node))) {
334 assert(row_ref.get_path());
336 auto const _model = panel->_model.get();
337 auto row = *panel->_store->get_iter(row_ref.get_path());
338 row[_model->_colNode] =
node;
345 row[_model->_colClipMask] =
351 row[_model->_colBlendMode] = blend;
356 row[_model->_colOpacity] = opacity;
357 std::string item_state;
358 if (opacity == 0.0) {
359 item_state =
"object-transparent";
362 item_state = opacity == 1.0 ?
"object-blend-mode" :
"object-translucent-blend-mode";
364 else if (opacity < 1.0) {
365 item_state =
"object-translucent";
367 row[_model->_colItemState] = item_state;
368 row[_model->_colItemStateSet] = !item_state.empty();
370 updateRowHighlight();
371 updateRowAncestorState(row[_model->_colAncestorInvisible], row[_model->_colAncestorLocked]);
378void ObjectWatcher::updateRowHighlight() {
381 std::cerr <<
"ObjectWatcher::updateRowHighlight: no row_ref: " <<
node->
name() << std::endl;
385 if (
auto item = cast<SPItem>(panel->getObject(
node))) {
386 auto row = *panel->_store->get_iter(row_ref.get_path());
388 if (new_color != row[panel->_model->_colIconColor]) {
389 row[panel->_model->_colIconColor] = new_color;
390 updateRowBg(new_color);
391 for (
auto &watcher : child_watchers) {
392 watcher.second->updateRowHighlight();
401void ObjectWatcher::updateRowAncestorState(
bool invisible,
bool locked) {
402 auto const _model = panel->_model.get();
403 auto row = *panel->_store->get_iter(row_ref.get_path());
404 row[_model->_colAncestorInvisible] = invisible;
405 row[_model->_colAncestorLocked] = locked;
406 for (
auto &watcher : child_watchers) {
407 watcher.second->updateRowAncestorState(
408 invisible || row[_model->_colInvisible],
409 locked || row[_model->_colLocked]);
418void ObjectWatcher::updateRowBg(
guint32 rgba)
421 if (
auto row = *panel->_store->get_iter(row_ref.get_path())) {
424 row[panel->_model->_colBgColor] = Gdk::RGBA();
429 const auto gdk_color =
change_alpha(sel, sel.get_alpha() * alpha);
430 row[panel->_model->_colBgColor] = gdk_color;
440void ObjectWatcher::setSelectedBit(
SelectionState mask,
bool enabled) {
441 if (!row_ref)
return;
450 selection_state = value;
459void ObjectWatcher::setSelectedBitRecursive(
SelectionState mask,
bool enabled)
461 setSelectedBit(mask, enabled);
462 setSelectedBitChildren(mask, enabled);
464void ObjectWatcher::setSelectedBitChildren(
SelectionState mask,
bool enabled)
466 for (
auto &pair : child_watchers) {
467 pair.second->setSelectedBitRecursive(mask, enabled);
474void ObjectWatcher::rememberExtendedItems()
476 if (
auto item = cast<SPItem>(panel->getObject(
node))) {
478 panel->_tree.expand_row(row_ref.get_path(),
false);
480 for (
auto &pair : child_watchers) {
481 pair.second->rememberExtendedItems();
488ObjectWatcher *ObjectWatcher::findChild(
Node *
node)
490 auto it = child_watchers.find(
node);
491 if (it != child_watchers.end()) {
492 return it->second.get();
505bool ObjectWatcher::addChild(
SPItem *
child,
bool dummy)
507 if (is_filtered && !panel->showChildInTree(
child)) {
511 auto children = getChildren();
512 if (!is_filtered && dummy && row_ref) {
513 if (children.empty()) {
514 auto const iter = panel->_store->append(children);
515 assert(panel->isDummy(*iter));
517 }
else if (panel->isDummy(children[0])) {
524 Gtk::TreeModel::Row row = *(panel->_store->prepend(children));
527 auto const _model = panel->_model.get();
529 auto parent_row = *panel->_store->get_iter(row_ref.get_path());
530 row[_model->_colAncestorInvisible] = parent_row[_model->_colAncestorInvisible] || parent_row[_model->_colInvisible];
531 row[_model->_colAncestorLocked] = parent_row[_model->_colAncestorLocked] || parent_row[_model->_colLocked];
533 row[_model->_colAncestorInvisible] =
false;
534 row[_model->_colAncestorLocked] =
false;
537 auto &watcher = child_watchers[
node];
539 watcher.reset(
new ObjectWatcher(panel,
child, &row, is_filtered));
551void ObjectWatcher::addChildren(
SPItem *obj,
bool dummy)
553 assert(child_watchers.empty());
555 for (
auto &
child : obj->children) {
557 if (addChild(
item, dummy) && dummy) {
574 auto child_iter = getChildIter(&
child);
580 while (sibling && !is<SPItem>(panel->getObject(sibling))) {
581 sibling = sibling->prev();
584 auto sibling_iter = getChildIter(sibling);
585 panel->_store->move(child_iter, sibling_iter);
593Gtk::TreeNodeChildren ObjectWatcher::getChildren()
const
595 Gtk::TreeModel::Path path;
596 if (row_ref && (path = row_ref.get_path())) {
597 return panel->_store->get_iter(path)->children();
600 return panel->_store->children();
609Gtk::TreeModel::iterator ObjectWatcher::getChildIter(
Node *
node)
const
611 auto childrows = getChildren();
614 return childrows.end();
617 for (
auto &row : childrows) {
618 if (panel->getRepr(row) ==
node) {
619 return row.get_iter();
623 return childrows.begin();
628 assert(this->node == &
node);
630 if (
auto item = cast<SPItem>(panel->getObject(&
child))) {
632 moveChild(
child, prev);
637 assert(this->node == &
node);
639 if (child_watchers.erase(&
child) > 0) {
645 auto iter = panel->_store->get_iter(row_ref.get_path());
646 panel->removeDummyChildren(*iter);
651 assert(this->node == &
parent);
653 moveChild(
child, new_prev);
657 assert(this->node == &
node);
660 if (
this == panel->getRootWatcher()) {
669 static std::set<GQuark>
const excluded{
670 g_quark_from_static_string(
"transform"),
671 g_quark_from_static_string(
"x"),
672 g_quark_from_static_string(
"y"),
673 g_quark_from_static_string(
"d"),
674 g_quark_from_static_string(
"sodipodi:nodetypes"),
677 if (excluded.count(
name)) {
713 return parent_watcher->findChild(
node);
725 , _model{
std::make_unique<ModelColumns>()}
730 , _settings_menu(
get_widget<
Gtk::Popover>(_builder,
"settings-menu"))
731 , _object_menu(
get_widget<
Gtk::Popover>(_builder,
"object-menu"))
733 , _searchBox(
get_widget<
Gtk::SearchEntry2>(_builder,
"search"))
734 , _opacity_slider(
get_widget<
Gtk::Scale>(_builder,
"opacity-slider"))
737 ,
_tree{*
Gtk::make_managed<TreeViewWithCssChanged>()}
743 _tree.set_headers_visible(
false);
744 _tree.set_reorderable(
false);
745 _tree.set_name(
"ObjectsTreeView");
747 auto& header = get_widget<Gtk::Box>(
_builder,
"header");
752 auto& _move_up_button = get_widget<Gtk::Button>(
_builder,
"move-up");
753 auto& _move_down_button = get_widget<Gtk::Button>(
_builder,
"move-down");
754 auto& _object_delete_button = get_widget<Gtk::Button>(
_builder,
"remove-object");
755 _move_up_button.signal_clicked().connect([
this]() {
758 _move_down_button.signal_clicked().connect([
this]() {
761 _object_delete_button.signal_clicked().connect([
this]() {
766 _name_column = Gtk::make_managed<Gtk::TreeViewColumn>();
769 _text_renderer->property_ellipsize().set_value(Pango::EllipsizeMode::END);
770 _text_renderer->signal_editing_started().connect([
this](Gtk::CellEditable*,
const Glib::ustring&){
776 _text_renderer->signal_edited().connect([
this](
const Glib::ustring&,
const Glib::ustring&){
780 const int icon_col_width = 24;
781 auto const icon_renderer = Gtk::make_managed<Inkscape::UI::Widget::CellRendererItemIcon>();
782 icon_renderer->property_xpad() = 2;
783 icon_renderer->property_width() = icon_col_width;
792 _name_column->add_attribute(icon_renderer->property_clipmask(),
_model->_colClipMask);
793 _name_column->add_attribute(icon_renderer->property_cell_background_rgba(),
_model->_colBgColor);
797 INKSCAPE_ICON(
"object-blend-mode"), INKSCAPE_ICON(
"object-opaque"));
799 if (
auto col =
_tree.get_column(modeColNum)) {
804 col->set_fixed_width(icon_col_width);
808 _tree.signal_query_tooltip().connect([
this](
int x,
int y,
bool kbd,
const Glib::RefPtr<Gtk::Tooltip>& tooltip){
809 Gtk::TreeModel::iterator iter;
810 if (!
_tree.get_tooltip_context_iter(x, y, kbd, iter) || !iter) {
813 auto blend = (*iter)[
_model->_colBlendMode];
814 auto opacity = (*iter)[
_model->_colOpacity];
815 auto templt = !pango_version_check(1, 50, 0) ?
816 "<span>%1 %2%%\n</span><span line_height=\"0.5\">\n</span><span>%3\n<i>%4</i></span>" :
817 "<span>%1 %2%%\n</span><span>\n</span><span>%3\n<i>%4</i></span>";
818 auto label = Glib::ustring::compose(templt,
822 tooltip->set_markup(
label);
832 auto& modes = get_widget<Gtk::Grid>(
_builder,
"modes");
836 const int min = 0, max = 100;
837 for (
int i = min; i <= max; i += 50) {
844 os << CLAMP(value, 0.0, 1.0);
854 Gtk::CheckButton *group =
nullptr;
866 auto const sep = Gtk::make_managed<Gtk::Separator>();
867 sep->set_visible(
true);
868 modes.attach(*sep, left, top, 2, 1);
872 if (left == 1 && top == 9)
875 auto const check = Gtk::make_managed<Gtk::CheckButton>(
label);
876 if (!group) group = check;
877 else check->set_group(*group);
878 check->set_halign(Gtk::Align::START);
879 check->signal_toggled().connect([=,
this]{
880 if (!check->get_active())
return;
884 btn.second->property_active().set_value(btn.first ==
data.id);
891 check->set_visible(
true);
892 modes.attach(*check, left, top,
width, 1);
899 auto const eyeRenderer = Gtk::make_managed<UI::Widget::ImageToggler>(
900 INKSCAPE_ICON(
"object-hidden"), INKSCAPE_ICON(
"object-visible"));
901 int visibleColNum =
_tree.append_column(
"vis", *eyeRenderer) - 1;
902 if (
auto eye =
_tree.get_column(visibleColNum)) {
903 eye->add_attribute(eyeRenderer->property_active(),
_model->_colInvisible);
904 eye->add_attribute(eyeRenderer->property_cell_background_rgba(),
_model->_colBgColor);
905 eye->add_attribute(eyeRenderer->property_activatable(),
_model->_colHover);
906 eye->add_attribute(eyeRenderer->property_gossamer(),
_model->_colAncestorInvisible);
907 eye->set_fixed_width(icon_col_width);
912 auto const lockRenderer = Gtk::make_managed<UI::Widget::ImageToggler>(
913 INKSCAPE_ICON(
"object-locked"), INKSCAPE_ICON(
"object-unlocked"));
914 int lockedColNum =
_tree.append_column(
"lock", *lockRenderer) - 1;
915 if (
auto lock =
_tree.get_column(lockedColNum)) {
916 lock->add_attribute(lockRenderer->property_active(),
_model->_colLocked);
917 lock->add_attribute(lockRenderer->property_cell_background_rgba(),
_model->_colBgColor);
918 lock->add_attribute(lockRenderer->property_activatable(),
_model->_colHover);
919 lock->add_attribute(lockRenderer->property_gossamer(),
_model->_colAncestorLocked);
920 lock->set_fixed_width(icon_col_width);
925 auto const tag_renderer = Gtk::make_managed<Inkscape::UI::Widget::ColorTagRenderer>();
926 int tag_column =
_tree.append_column(
"tag", *tag_renderer) - 1;
927 if (
auto tag =
_tree.get_column(tag_column)) {
928 tag->add_attribute(tag_renderer->property_color(),
_model->_colIconColor);
929 tag->add_attribute(tag_renderer->property_hover(),
_model->_colHoverColor);
930 tag->set_fixed_width(tag_renderer->get_width());
936 _tree.set_search_column(-1);
937 _tree.set_enable_search(
false);
938 _tree.get_selection()->set_mode(Gtk::SelectionMode::NONE);
941 auto const click = Gtk::GestureClick::create();
942 click->set_button(0);
943 click->set_propagation_phase(Gtk::PropagationPhase::TARGET);
946 _tree.add_controller(click);
948 auto const key = Gtk::EventControllerKey::create();
952 auto const motion = Gtk::EventControllerMotion::create();
953 motion->set_propagation_phase(Gtk::PropagationPhase::TARGET);
956 motion->signal_motion().connect([
this, &motion = *motion](
auto &&...args) {
on_motion_motion(&motion, args...); });
957 _tree.add_controller(motion);
960 auto const window_key = Gtk::EventControllerKey::create();
961 window_key->signal_key_pressed().connect([
this, &window_key = *window_key](
auto &&...args) {
return on_window_key(window_key, args...,
EventType::pressed); },
true);
963 connect_on_window_when_mapped(window_key,
_tree);
966 _tree.signal_test_expand_row().connect([
this](
const Gtk::TreeModel::iterator &iter,
const Gtk::TreeModel::Path &) {
974 _tree.signal_row_expanded().connect([
this](
const Gtk::TreeModel::iterator &iter,
const Gtk::TreeModel::Path &) {
979 _tree.signal_row_collapsed().connect([
this](
const Gtk::TreeModel::iterator &iter,
const Gtk::TreeModel::Path &) {
985 auto const drag = Gtk::DragSource::create();
986 drag->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
987 drag->set_actions(Gdk::DragAction::MOVE);
988 drag->signal_prepare().connect([
this, &drag = *drag](
auto &&...args) {
return on_prepare(drag, args...); },
false);
991 _tree.add_controller(drag);
993 auto const drop = Gtk::DropTarget::create(Glib::Value<Glib::ustring>::value_type(), Gdk::DragAction::MOVE);
994 drop->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
997 _tree.add_controller(drop);
1006 _scroller.set_policy( Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC );
1009 Gtk::Requisition sreq;
1010 Gtk::Requisition sreq_natural;
1011 _scroller.get_preferred_size(sreq_natural, sreq);
1013 if (sreq.get_height() < minHeight) {
1015 _scroller.set_size_request(sreq.get_width(), minHeight);
1018 _page.append(header);
1024 auto const set_selection_color = [&] {
1027 set_selection_color();
1029 auto enter_layer_label_editing_mode = [
this]() {
1032 _tree.set_cursor(watcher->getTreePath(), *
_tree.get_column(0),
true);
1036 auto& add_layer_btn = get_widget<Gtk::Button>(
_builder,
"insert-layer");
1037 add_layer_btn.signal_clicked().connect(enter_layer_label_editing_mode);
1039 _tree_style =
_tree.connect_css_changed([=,
this] (GtkCssStyleChange *change) {
1040 set_selection_color();
1045 kv.second->updateRowHighlight();
1082 bool const filtered = prefs->getBool(
"/dialogs/objects/layers_only",
false) ||
_searchBox.get_text().length();
1098 bool show_child =
true;
1101 if (prefs->
getBool(
"/dialogs/objects/layers_only",
false)) {
1102 auto group = cast<SPGroup>(
item);
1109 auto term =
_searchBox.get_text().lowercase();
1110 if (show_child && term.length()) {
1112 std::stringstream source;
1115 source <<
" " <<
label;
1119 auto doc = source.str();
1120 transform(doc.begin(), doc.end(), doc.begin(), ::tolower);
1121 show_child = doc.find(term) != std::string::npos;
1130 if (
auto child = cast<SPItem>(child_obj)) {
1148 }
else if (watcher &&
1149 (watcher = watcher->findChild(
parent->getRepr())))
1151 if (
auto const row = watcher->getRow()) {
1161#define SP_DOCUMENT_UPDATE_PRIORITY (G_PRIORITY_HIGH_IDLE - 2)
1177 bool keep_current_item =
false;
1183 auto focus_watcher = watcher;
1187 if (
auto child_watcher = watcher->findChild(
item->
getRepr())) {
1190 watcher = child_watcher;
1194 if (prefs->
getBool(
"/dialogs/objects/expand_to_layer",
true)) {
1195 _tree.expand_to_path(focus_watcher->getTreePath());
1197 _tree.scroll_to_row(watcher->getTreePath(), 0.5);
1203 if (!keep_current_item) {
1221 if (!layer || !layer->
getRepr())
return;
1240 if (
selection->
isEmpty() || prefs->getBool(
"/dialogs/objects/layers_only",
false)) {
1242 win->activate_action(layerAction);
1244 Glib::RefPtr<Gio::Application> app = Gio::Application::get_default();
1245 app->activate_action(selectionAction);
1288 if (
item ==
nullptr) {
1305 btn.second->property_active().set_value(btn.first == blend);
1321 if (
item ==
nullptr) {
1325 auto color_popup = Gtk::make_managed<Gtk::Popover>();
1330 _colors->signal_changed.connect([
this]() {
1361 bool locked = !row[
_model->_colLocked];
1368 sitem->setLocked(locked);
1383 unsigned keyval,
unsigned keycode, Gdk::ModifierType state)
1390 Gtk::TreeModel::Path path;
1391 Gtk::TreeViewColumn *column;
1392 _tree.get_cursor(path, column);
1396 switch (shortcut.get_key()) {
1397 case GDK_KEY_Escape:
1404 case GDK_KEY_KP_Left:
1405 if (path &&
shift) {
1406 _tree.collapse_row(path);
1411 case GDK_KEY_KP_Right:
1412 if (path &&
shift) {
1413 _tree.expand_row(path,
false);
1422 case GDK_KEY_Delete:
1423 case GDK_KEY_KP_Delete:
1424 case GDK_KEY_BackSpace:
1428 case GDK_KEY_Page_Up:
1429 case GDK_KEY_KP_Page_Up:
1435 case GDK_KEY_Page_Down:
1436 case GDK_KEY_KP_Page_Down:
1450 case GDK_KEY_KP_Down:
1455 case GDK_KEY_Return:
1458 auto item_path = watcher->getTreePath();
1459 _tree.set_cursor(item_path, *
_tree.get_column(0),
true );
1470 unsigned keyval,
unsigned keycode,
1471 Gdk::ModifierType state,
EventType event_type)
1478 switch (shortcut.get_key()) {
1496 _(
"<b>Hold ALT</b> while hovering over item to highlight, "
1497 "<b>hold SHIFT</b> and click to hide/lock all."));
1507 double ex,
double ey)
1514 row[
_model->_colHover] =
false;
1515 row[
_model->_colHoverColor] =
false;
1528 if (controller ==
nullptr) {
1535 Gtk::TreeModel::Path path;
1536 Gtk::TreeViewColumn* col =
nullptr;
1538 if (
_tree.get_path_at_pos(ex, ey, path, col, cell_x, cell_y)) {
1547 if (
auto row = *
_store->get_iter(path)) {
1548 row[
_model->_colHover] =
true;
1551 const Gdk::RGBA color = row[
_model->_colBgColor];
1557 row[
_model->_colHoverColor] =
true;
1565 Glib::signal_idle().connect_once([
this,
item]() {
1568 }, Glib::PRIORITY_DEFAULT_IDLE);
1577 auto const state = controller->get_current_event_state();
1599 trg.setSolidItem(
item);
1603 Gtk::TreeModel::Path
const &path, Gtk::TreeViewColumn &column)
1605 auto area = Gdk::Rectangle{};
1606 tree_view.get_cell_area(path, column, area);
1611 Gtk::TreeModel::Path
const &path, Gtk::TreeViewColumn &column)
1614 return std::pair{std::lround(area.get_x() + area.get_width () / 2.0),
1615 std::lround(area.get_y() + area.get_height() / 2.0)};
1623 int const n_press,
double const ex,
double const ey,
1628 return Gtk::EventSequenceState::NONE;
1635 Gtk::TreeModel::Path path;
1636 Gtk::TreeViewColumn* col =
nullptr;
1638 if (!
_tree.get_path_at_pos(ex, ey, path, col, x, y)) {
1640 return Gtk::EventSequenceState::NONE;
1646 _tree.set_cursor(path);
1648 if (
auto row = *
_store->get_iter(path)) {
1650 auto const state = gesture.get_current_event_state();
1659 return blendModePopup(cx, cy, row) ? Gtk::EventSequenceState::CLAIMED
1660 : Gtk::EventSequenceState::NONE;
1664 return colorTagPopup(cx, cy, row) ? Gtk::EventSequenceState::CLAIMED
1665 : Gtk::EventSequenceState::NONE;
1672 return Gtk::EventSequenceState::CLAIMED;
1679 return Gtk::EventSequenceState::NONE;
1684 _tree.set_cursor(path, *col,
true);
1686 return Gtk::EventSequenceState::CLAIMED;
1691 auto row = *
_store->get_iter(path);
1694 return Gtk::EventSequenceState::NONE;
1701 auto const state = gesture.get_current_event_state();
1703 auto const should_set_current_layer = [&] {
1710 Gdk::ModifierType::CONTROL_MASK)) {
1718 auto const button = gesture.get_current_button();
1746 }
else if (should_set_current_layer()) {
1753 return Gtk::EventSequenceState::CLAIMED;
1759 return Gtk::EventSequenceState::NONE;
1770 if (
auto row = *
_store->get_iter(path)) {
1773 auto obj = cast<SPGroup>(
item);
1799 return row[
_model->_colNode];
1808 auto const this_const =
const_cast<ObjectsPanel *
>(
this);
1809 return cast<SPItem>(this_const->getObject(
getRepr(row)));
1817 for (
auto &
c : row.children()) {
1833 auto &children = row.children();
1834 if (!children.empty()) {
1835 auto const iter = row.get_iter();
1836 Gtk::TreeStore::iterator
child = children.begin();
1843 assert(
child->parent() == iter);
1856 watcher->addChildren(
getItem(row));
1873 return Gdk::DragAction{};
1876 Gtk::TreeModel::Path path;
1877 Gtk::TreeView::DropPosition pos;
1878 _tree.get_dest_row_at_pos(x, y, path, pos);
1882 std::cerr <<
"ObjectsPanel::on_drag_motion: path doesn't correspond to an item!" << std::endl;
1883 return Gdk::DragAction{};
1889 return Gdk::DragAction{};
1894 return Gdk::DragAction{};
1899 Gtk::TreeViewColumn* col =
nullptr;
1900 _tree.get_path_at_pos(x, y, path, col, cell_x, cell_y);
1902 return Gdk::DragAction{};
1907 if (
_tree.is_blank_at_pos(x, y)) {
1909 path = --
_store->children().end();
1913 return Gdk::DragAction{};
1916 std::cerr <<
"ObjectsPanel::on_drag_motion: invalid drop area!" << std::endl;
1921 return Gdk::DragAction::MOVE;
1931 Gtk::TreeModel::Path path;
1932 Gtk::TreeView::DropPosition pos;
1933 _tree.get_dest_row_at_pos(x, y, path, pos);
1936 if (
_tree.is_blank_at_pos(x, y)){
1939 path = --
_store->children().end();
1940 pos = Gtk::TreeView::DropPosition::AFTER;
1942 std::cerr <<
"ObjectsPanel::on_drag_drop: invalid drop area!" << std::endl;
1948 bool const drop_into = pos != Gtk::TreeView::DropPosition::BEFORE &&
1949 pos != Gtk::TreeView::DropPosition::AFTER;
1957 if (drop_into && is<SPGroup>(
item)) {
1961 Node *after = (pos == Gtk::TreeView::DropPosition::BEFORE ||
1962 pos == Gtk::TreeView::DropPosition::INTO_OR_BEFORE)
1963 ? drop_repr : drop_repr->prev();
1975 Gtk::TreeModel::Path path;
1976 Gtk::TreeView::DropPosition pos;
1977 _tree.get_dest_row_at_pos(x, y, path, pos);
1982 controller.set_icon(
surface, x, 12);
1986 Glib::Value<Glib::ustring> value;
1987 value.init(G_TYPE_STRING);
1988 value.set(
"ObjectsPanelDrag");
1989 auto provider = Gdk::ContentProvider::create(value);
1998 selection->set_mode(Gtk::SelectionMode::MULTIPLE);
2010 auto path = watcher->getTreePath();
2016 for (
auto item : obj_selection->items()) {
2019 auto path = watcher->getTreePath();
2031 selection->set_mode(Gtk::SelectionMode::NONE);
2048 if (gtk_tree_path_compare(
start.gobj(),
end.gobj()) > 0) {
2066 _store->foreach ([&](Gtk::TreeModel::Path
const &p, Gtk::TreeModel::const_iterator
const &it) {
2067 if ((gtk_tree_path_compare(
start.gobj(), p.gobj()) <= 0) &&
2068 (gtk_tree_path_compare(
end.gobj(), p.gobj()) >= 0)) {
2071 if (!layers.isLayer(obj)) {
2093 Gtk::TreeModel::Path path;
2094 Gtk::TreeViewColumn *column;
2095 _tree.get_cursor(path, column);
2096 if (!path || !column)
2099 auto row = *
_store->get_iter(path);
2109 auto group = cast<SPGroup>(
item);
2126 layers.setCurrentLayer(
item,
true);
2129 if (layers.currentLayer() ==
item || group) {
Cairo::RefPtr< Cairo::ImageSurface > surface
InkscapeWindow * get_active_window()
static InkscapeApplication * instance()
Singleton instance.
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void toggleLayerSolo(SPObject *object, bool force_hide=false)
Toggle the visibility of every layer except the given layer.
void setCurrentLayer(SPObject *object, bool clear=false)
Sets the current layer of the desktop.
static SPGroup * asLayer(SPObject *object)
Return the SPGroup if we have a layer object.
void toggleLockOtherLayers(SPObject *object, bool force_lock=false)
Toggle the sensitivity of every layer except the given layer.
bool isLayer(SPObject *object) const
True if object is a layer.
sigc::connection connectCurrentLayerChanged(const sigc::slot< void(SPGroup *)> &slot)
void cancel(MessageId id)
removes a message from the stack, given its id
MessageId push(MessageType type, char const *message)
pushes a message onto the stack
SPItemRange items()
Returns a range of selected SPItems.
bool isEmpty()
Returns true if no items are selected.
void toLayer(SPObject *layer)
Move selection to group moveto, after the last child of moveto (if it has any children).
Preference storage class.
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
The set of selected SPObjects for a given document and layer model.
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
void remove(XML::Node *repr)
Removes an item from the set of selected objects.
SPObject * includesAncestor(XML::Node *repr)
Returns ancestor if the given object has ancestor selected.
bool includes(XML::Node *repr, bool anyAncestor=false)
Returns true if the given item is selected.
static Gtk::AccelKey get_from(GtkEventControllerKey const *controller, unsigned keyval, unsigned keycode, GdkModifierType state, bool fix=false)
Controller provides the group. It can be nullptr; if so, we use group 0.
DialogBase is the base class for the dialog system.
Selection * getSelection() const
virtual void update()
The update() method is essential to Gtk state management.
SPDocument * getDocument() const
SPDesktop * getDesktop() const
A panel that displays objects.
UI::Widget::PopoverBin _popoverbin
Gtk::SearchEntry2 & _searchBox
void _activateAction(const std::string &layerAction, const std::string &selectionAction)
Special context-aware functions - If nothing is selected or layers-only mode is active,...
void documentReplaced() override
Glib::RefPtr< Gdk::ContentProvider > on_prepare(Gtk::DragSource &controller, double x, double y)
std::map< SPBlendMode, Glib::ustring > _blend_mode_names
bool hasDummyChildren(Gtk::TreeModel::ConstRow const &row) const
Return true if this row has dummy children.
void layerChanged(SPObject *obj)
Happens when the layer selected is changed.
Glib::RefPtr< Gtk::TreeStore > _store
Glib::RefPtr< Gtk::Builder > _builder
bool toggleLocked(Gdk::ModifierType state, Gtk::TreeModel::Row row)
Sets sensitivity of items in the tree.
Gtk::TreeView::Column * _eye_column
void selectionChanged(Selection *selected) override
bool toggleVisible(Gdk::ModifierType state, Gtk::TreeModel::Row row)
Sets visibility of items in the tree.
std::unique_ptr< ObjectWatcher > root_watcher
ObjectWatcher * getWatcher(Inkscape::XML::Node *node)
Get the object watcher from the xml node (reverse lookup), it uses a ancesstor recursive pattern to m...
Inkscape::PrefObserver _watch_object_mode
Gtk::Scale & _opacity_slider
Gtk::TreeView::Column * _lock_column
void selectRange(Gtk::TreeModel::Path start, Gtk::TreeModel::Path end)
void on_drag_end(Glib::RefPtr< Gdk::Drag > const &drag, bool delete_data)
Gtk::TreeView::Column * _blend_mode_column
bool selectCursorItem(Gdk::ModifierType state)
Select the object currently under the list-cursor (keyboard or mouse)
bool on_tree_key_pressed(Gtk::EventControllerKey const &controller, unsigned keyval, unsigned keycode, Gdk::ModifierType state)
Handles keyboard events on the TreeView.
Gtk::EventSequenceState on_click(Gtk::GestureClick const &gesture, int n_press, double x, double y, EventType type)
Handles mouse button click events.
std::unique_ptr< ModelColumns > _model
Inkscape::UI::Widget::ImageToggler * _item_state_toggler
sigc::scoped_connection layer_changed
Gtk::CellRendererText * _text_renderer
bool on_window_key(Gtk::EventControllerKey const &controller, unsigned keyval, unsigned keycode, Gdk::ModifierType state, EventType type)
Gtk::TreeModel::RowReference _hovered_row_ref
bool removeDummyChildren(Gtk::TreeModel::Row row)
If the given row has dummy children, remove them.
Gdk::RGBA _hovered_row_old_color
UI::Widget::ColorNotebook * _color_selector
std::shared_ptr< Colors::ColorSet > _colors
std::vector< SPWeakPtr< SPObject > > _prev_range
sigc::scoped_connection _idle_connection
void on_drag_begin(Glib::RefPtr< Gdk::Drag > const &drag)
bool showChildInTree(SPItem *item)
Apply any ongoing filters to the items.
Gtk::Popover & _object_menu
bool cleanDummyChildren(Gtk::TreeModel::Row row)
void desktopReplaced() override
Called when the desktop has certainly changed.
ObjectsPanel()
Constructor.
Gtk::TreeRow _clicked_item_row
void _handleTransparentHover(bool enabled)
Gdk::RGBA _hovered_row_color
bool colorTagPopup(int x, int y, Gtk::TreeModel::Row row)
Gtk::TreeModel::Path _initial_path
bool select_row(Glib::RefPtr< Gtk::TreeModel > const &model, Gtk::TreeModel::Path const &path, bool b)
Take over the select row functionality from the TreeView, this is because we have two selections (lay...
void _searchActivated()
User pressed return in search box, process search query.
SPObject * getObject(Inkscape::XML::Node *node)
Get the object from the node.
void _handleEdited(const Glib::ustring &path, const Glib::ustring &new_text)
Handle a successful item label edit.
sigc::scoped_connection _tree_style
void on_motion_motion(Gtk::EventControllerMotion const *controller, double x, double y)
ObjectWatcher * unpackToObject(SPObject *item)
This both unpacks the tree, and populates lazy loading.
bool on_drag_drop(Glib::ValueBase const &value, double x, double y)
Signal handler for "drag-drop".
bool isDummy(Gtk::TreeModel::ConstRow const &row) const
Gtk::TreeView::Column * _color_tag_column
TreeViewWithCssChanged & _tree
Inkscape::XML::Node * getRepr(Gtk::TreeModel::ConstRow const &row) const
Get the XML node which is associated with a row.
SPItem * getItem(Gtk::TreeModel::ConstRow const &row) const
Get the item which is associated with a row.
Gtk::ScrolledWindow _scroller
bool _translucency_enabled
Gtk::TreeView::Column * _name_column
Gdk::DragAction on_drag_motion(double x, double y)
Signal handler for "drag-motion".
friend class ObjectWatcher
void on_motion_enter(double x, double y)
Handles mouse movements.
std::map< SPBlendMode, Gtk::CheckButton * > _blend_items
Gtk::TreeViewColumn * _drag_column
bool blendModePopup(int x, int y, Gtk::TreeModel::Row row)
const unsigned int _length
const EnumData< E > & data(const unsigned int i) const
Interface for XML node observers.
Interface for refcounted XML nodes.
virtual Node * parent()=0
Get the parent of this node.
virtual char const * name() const =0
Get the name of the element node.
virtual Node * firstChild()=0
Get the first child of this node.
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
Inkscape::UI::Widget::Canvas * getCanvas() const
Inkscape::MessageStack * messageStack() const
Inkscape::LayerManager & layerManager()
Inkscape::Display::TranslucencyGroup & getTranslucencyGroup() const
SPRoot * getRoot()
Returns our SPRoot.
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
void setHighlight(Inkscape::Colors::Color color)
virtual Inkscape::Colors::Color highlight_color() const
SPMask * getMaskObject() const
void setLocked(bool lock, bool recursive=false)
void setHidden(bool hidden)
virtual const char * typeName() const
The item's type name, not node tag name.
void setExpanded(bool expand)
SPClipPath * getClipObject() const
bool isHighlightSet() const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
std::vector< SPObject * > childList(bool add_ref, Action action=ActionGeneral)
Retrieves the children as a std vector object, optionally ref'ing the children in the process,...
char const * getId() const
Returns the objects current ID string.
void changeCSS(SPCSSAttr *css, char const *attr)
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
std::vector< SPObject * > ancestorList(bool root_to_tip)
Retrieves a list of ancestors of the object, as an easy to use vector.
char const * defaultLabel() const
Returns a default label property for this object.
void setLabel(char const *label)
Sets the author-visible label for this object.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getTagName() const
T< SPAttr::MIX_BLEND_MODE, SPIEnum< SPBlendMode > > mix_blend_mode
T< SPAttr::OPACITY, SPIScale24 > opacity
opacity
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Utility functions to convert ascii representations to numbers.
TODO: insert short description here.
std::shared_ptr< Css const > css
Editable view implementation.
static char const *const parent
TODO: insert short description here.
constexpr auto SP_DOCUMENT_UPDATE_PRIORITY
bool set_blend_mode(SPItem *item, SPBlendMode blend_mode)
Util::TreeifyResult const & _tree
Macro for icon names used in Inkscape.
Inkscape::XML::Node * node
Inkscape - An SVG editor.
void shift(T &a, T &b, T const &c)
Raw stack of active status messages.
auto use_state(Slot &&slot)
bool has_flag(Gdk::ModifierType const state, Gdk::ModifierType const flags)
Helper to query if ModifierType state contains one or more of given flag(s).
static auto get_cell_area(Gtk::TreeView const &tree_view, Gtk::TreeModel::Path const &path, Gtk::TreeViewColumn &column)
static auto get_key(std::size_t const notebook_idx)
Gdk::RGBA selection_color
static auto get_cell_center(Gtk::TreeView const &tree_view, Gtk::TreeModel::Path const &path, Gtk::TreeViewColumn &column)
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
std::string format_number(double val, unsigned int precision=3)
static void append(std::vector< T > &target, std::vector< T > &&source)
const Util::EnumDataConverter< SPBlendMode > SPBlendModeConverter
static cairo_user_data_key_t key
static double const HOVER_ALPHA
static double const SELECTED_ALPHA[16]
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
SPRoot: SVG <svg> implementation.
Complete state of a selection, including selected objects and nodes.
SPStyle enums: named public enums that correspond to SVG property values.
SPStyle - a style object for SPItem objects.
Render some items as translucent in a document rendering stack.
Gdk::RGBA get_color_with_class(Gtk::Widget &widget, Glib::ustring const &css_class)
Gdk::RGBA change_alpha(const Gdk::RGBA &color, double new_alpha)