23#include <gtkmm/dragsource.h>
24#include <gtkmm/droptarget.h>
25#include <gtkmm/treemodel.h>
26#include <gtkmm/treerowreference.h>
27#include <gtkmm/treestore.h>
57class ModelColumns final :
public Gtk::TreeModel::ColumnRecord
67 Gtk::TreeModelColumn<Inkscape::XML::Node*>
node;
68 Gtk::TreeModelColumn<Glib::ustring> markup;
69 Gtk::TreeModelColumn<Glib::ustring> text;
77 NodeWatcher() =
delete;
79 ~NodeWatcher()
override;
82 std::unique_ptr<NodeWatcher>> child_watchers;
109 assert (this->node == &node);
112 move_child(
child, prev);
119 assert (this->node == &node);
121 if (child_watchers.erase(&
child) > 0) {
125 std::cerr <<
"NodeWatcher::notifyChildRemoved: failed to remove child!"
134 assert (this->node == &
parent);
136 move_child(
child, new_prev);
145 auto const attribute = g_quark_to_string(
key);
146 if (std::strcmp(attribute,
"id") == 0 ||
147 std::strcmp(attribute,
"inkscape:label") == 0) {
161 XmlTreeView *xml_tree_view;
162 Gtk::TreeModel::RowReference row_ref;
166 : xml_tree_view(xml_tree_view)
170 if (row !=
nullptr) {
171 assert(row->children().empty());
173 auto store = xml_tree_view->store;
175 auto row_iter = row->get_iter();
176 auto path =
store->get_path(row_iter);
177 row_ref = Gtk::TreeModel::RowReference(
store, path);
182 node->addObserver(*
this);
187NodeWatcher::~NodeWatcher()
189 node->removeObserver(*
this);
190 Gtk::TreeModel::Path path;
191 if (
bool(row_ref) && (path = row_ref.get_path())) {
192 if (
auto iter = xml_tree_view->store->get_iter(path)) {
193 xml_tree_view->store->erase(iter);
196 child_watchers.clear();
200NodeWatcher::update_row()
205 switch (
node->type()) {
206 case NodeType::ELEMENT_NODE:
start =
"<";
end =
">";
break;
207 case NodeType::TEXT_NODE:
start =
"\"";
end =
"\"";
break;
208 case NodeType::COMMENT_NODE:
start =
"<!--";
end =
"-->";
break;
209 case NodeType::PI_NODE:
start =
"<?";
end =
"?>";
break;
210 case NodeType::DOCUMENT_NODE:
break;
211 default: std::cerr <<
"NodeWatcher::NodeWatcher: unhandled NodeType!" << std::endl;
214 Glib::ustring content;
216 Glib::ustring markup;
217 switch (
node->type()) {
218 case NodeType::ELEMENT_NODE: {
220 content =
node->name();
223 auto pos = content.find(
"svg:");
224 if (pos != Glib::ustring::npos) {
225 content.erase(pos, 4);
229 xml_tree_view->formatter->openTag(content.c_str());
232 if (
char const *
id =
node->attribute(
"id")) {
236 xml_tree_view->formatter->addAttribute(
"id",
id);
239 if (
char const *
label =
node->attribute(
"inkscape::label")) {
240 content +=
" inkscape:label=\"";
243 xml_tree_view->formatter->addAttribute(
"inkscape:label",
label);
247 markup = xml_tree_view->formatter->finishTag();
251 case NodeType::TEXT_NODE:
252 case NodeType::COMMENT_NODE:
253 case NodeType::PI_NODE:
256 if (simple_node->content()) {
257 content = simple_node->content();
261 markup = xml_tree_view->formatter->formatContent(text.c_str(),
false);
264 case NodeType::DOCUMENT_NODE:
break;
265 default: std::cerr <<
"NodeWatcher::NodeWatcher: unhandled NodeType!" << std::endl;
268 auto path = row_ref.get_path();
270 std::cerr <<
"NodeWatcher::update_row: no path!" << std::endl;
273 auto row_iter = xml_tree_view->store->get_iter(path);
275 std::cerr <<
"NodeWatcher::update_row: no row_iter!" << std::endl;
279 (*row_iter)[xml_tree_view->model_columns->node ] =
node;
280 (*row_iter)[xml_tree_view->model_columns->text ] = text;
281 (*row_iter)[xml_tree_view->model_columns->markup] = markup;
286NodeWatcher::get_children()
const
288 Gtk::TreeModel::Path path;
289 if (row_ref && (path = row_ref.get_path())) {
290 return xml_tree_view->store->get_iter(path)->children();
293 return xml_tree_view->store->children();
302 Gtk::TreeModel::Row row = *(xml_tree_view->store->append(children));
304 auto &watcher = child_watchers[
child];
306 watcher.reset(
new NodeWatcher(xml_tree_view,
child, &row));
310NodeWatcher::add_children()
323 auto child_iter = get_child_iterator(&
child);
325 std::cerr <<
"NodeWatcher::move_child: no child iterator!" << std::endl;
329 auto sibling_iter = get_child_iterator(sibling);
336 xml_tree_view->store->move( child_iter, sibling_iter);
339Gtk::TreeModel::iterator
345 return child_rows.end();
348 for (
auto &row : child_rows) {
349 if (xml_tree_view->get_repr(row) ==
node) {
350 return row.get_iter();
354 std::cerr <<
"NodeWatcher::get_child_iterator: failed to find interator!" << std::endl;
356 return child_rows.begin();
361class NodeRenderer :
public Gtk::CellRendererText {
364 :
Glib::ObjectBase(typeid(CellRendererText))
365 ,
Gtk::CellRendererText()
366 , property_plain_text(*this,
"plain",
"-") {}
368 Glib::Property<Glib::ustring> property_plain_text;
370 void snapshot_vfunc(Glib::RefPtr<Gtk::Snapshot>
const &snapshot,
372 const Gdk::Rectangle &background_area,
373 const Gdk::Rectangle &cell_area,
374 Gtk::CellRendererState flags)
override
376 if ((
bool)(flags & Gtk::CellRendererState::SELECTED)) {
378 property_text() = property_plain_text.get_value();
380 Gtk::CellRendererText::snapshot_vfunc(snapshot, widget, background_area, cell_area, flags);
386XmlTreeView::XmlTreeView()
388 set_name(
"XmlTreeView");
389 set_headers_visible(
false);
390 set_reorderable(
false);
391 set_enable_search(
true);
393 model_columns = std::make_unique<ModelColumns>();
394 store = Gtk::TreeStore::create(*model_columns);
399 text_renderer = Gtk::make_managed<NodeRenderer>();
400 auto text_column = Gtk::make_managed<Gtk::TreeViewColumn>();
401 text_column->pack_start(*text_renderer,
true);
402 text_column->set_expand(
true);
403 text_column->add_attribute(*text_renderer,
"markup", model_columns->markup);
404 text_column->add_attribute(*text_renderer,
"plain", model_columns->text);
405 append_column(*text_column);
407 enable_model_drag_source ();
408 auto const drag = Gtk::DragSource::create();
409 drag->set_actions(Gdk::DragAction::MOVE);
410 drag->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
411 drag->signal_prepare().connect([
this, &drag = *drag](
auto &&...args) {
return on_prepare(drag, args...); },
false);
412 add_controller(drag);
414 auto const drop = Gtk::DropTarget::create(GlibValue::type<XMLDnDRow>(), Gdk::DragAction::MOVE);
415 drop->set_propagation_phase(Gtk::PropagationPhase::CAPTURE);
416 drop->signal_motion().connect(sigc::mem_fun(*
this, &XmlTreeView::on_drag_motion),
false);
417 drop->signal_drop().connect(sigc::mem_fun(*
this, &XmlTreeView::on_drag_drop),
false);
418 add_controller(drop);
425 document = document_in;
427 root_watcher.reset();
435 std::cerr <<
"XMLTreeView::set_root_watcher: No XML root!" << std::endl;
439 Gtk::TreeModel::Row row = *(
store->prepend());
440 root_watcher = std::make_unique<NodeWatcher>(
this,
root, &row);
444XmlTreeView::get_repr(Gtk::TreeModel::ConstRow
const &row)
const
446 return row[model_columns->node];
455 auto selection = get_selection();
458 store->foreach_iter([
this,
node, edit, selection](
const Gtk::TreeModel::iterator &it) {
459 if ((*it)[model_columns->node] ==
node) {
462 auto path = store->get_path(it);
463 expand_to_path(path);
464 auto column = get_column(0);
465 scroll_to_cell(path, *column, 0.66, 0.0);
467 selection->unselect_all();
468 selection->select(it);
469 set_cursor(path, *column, edit);
476 selection->unselect_all();
487 formatter->setStyle(new_style);
491Glib::RefPtr<Gdk::ContentProvider>
492XmlTreeView::on_prepare(Gtk::DragSource &controller,
double x,
double y)
494 Gtk::TreeModel::Path path;
495 Gtk::TreeView::DropPosition pos;
496 get_dest_row_at_pos(x, y, path, pos);
503 if (path.to_string() ==
"0") {
507 static GQuark const CODE_sodipodi_namedview = g_quark_from_static_string(
"sodipodi:namedview");
508 static GQuark const CODE_svg_defs = g_quark_from_static_string(
"svg:defs");
510 if (
auto row_iter =
store->get_iter(path)) {
511 node = (*row_iter)[model_columns->node];
514 if (
node->code() == CODE_sodipodi_namedview ||
515 node->code() == CODE_svg_defs) {
524 auto surface = create_row_drag_icon(path);
525 controller.set_icon(
surface, x, 12);
528 return Gdk::ContentProvider::create(GlibValue::create<XMLDnDRow>(XMLDnDRow{
node}));
531Gdk::DragAction XmlTreeView::on_drag_motion(
double const x,
double const y)
533 Gtk::TreeModel::Path path;
534 Gtk::TreeView::DropPosition pos;
535 get_dest_row_at_pos(x, y, path, pos);
538 if (
auto row_iter =
store->get_iter(path)) {
542 bool const drop_into =
543 pos != Gtk::TreeView::DropPosition::BEFORE &&
544 pos != Gtk::TreeView::DropPosition::AFTER;
548 unset_drag_dest_row();
549 return Gdk::DragAction{};
552 return Gdk::DragAction::MOVE;
556 return Gdk::DragAction::MOVE;
559bool XmlTreeView::on_drag_drop(Glib::ValueBase
const &value,
double x,
double y)
561 auto pointer = GlibValue::get<XMLDnDRow>(value);
563 auto node = pointer->node;
566 Glib:: ustring
id = (
node->attribute(
"id") ?
node->attribute(
"id") :
"Not element");
568 Gtk::TreeModel::Path path;
569 Gtk::TreeView::DropPosition pos;
570 get_dest_row_at_pos(x, y, path, pos);
573 if (is_blank_at_pos(x, y)) {
577 path = Gtk::TreePath(
"0:0");
578 auto row_iter =
store->get_iter(path);
582 while (child_node->
next()) {
583 child_node = child_node->
next();
586 Glib::ustring path_index =
"0:" + std::to_string(n);
587 path = Gtk::TreePath(path_index);
588 pos = Gtk::TreeView::DropPosition::AFTER;
594 auto row_iter =
store->get_iter(path);
599 if (
node == drop_node) {
604 bool const drop_into =
605 pos != Gtk::TreeView::DropPosition::BEFORE &&
606 pos != Gtk::TreeView::DropPosition::AFTER;
609 auto drop_parent_node = drop_node->
parent();
623 auto item = document->getObjectByRepr(drop_node);
626 is<SPGlyph> (
item) ||
627 is<SPGroup> (
item) ||
629 is<SPPattern>(
item) ||
630 is<SPTSpan> (
item) ||
637 if (pos == Gtk::TreeView::DropPosition::BEFORE) {
638 drop_node = drop_node->
prev();
640 if (parent_node == drop_parent_node) {
644 drop_parent_node->addChild(
node, drop_node);
Cairo::RefPtr< Cairo::ImageSurface > surface
Hand-rolled LLVM-style RTTI system for class hierarchies where dynamic_cast isn't fast enough.
Interface for XML node observers.
Interface for refcounted XML nodes.
virtual Node * parent()=0
Get the parent of this node.
virtual Node * next()=0
Get the next sibling of this node.
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
virtual void changeOrder(Node *child, Node *after)=0
Move a given node in this node's child order.
virtual void removeChild(Node *child)=0
Remove a child of this node.
Default implementation of the XML node stored in memory.
Typed SVG document implementation.
Inkscape::XML::Node * getReprRoot()
static char const *const parent
Inkscape::XML::Node * node
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Miscellaneous supporting code.
NodeType
Enumeration containing all supported node types.
@ ELEMENT_NODE
Regular element node, e.g. <group />.
Interface for XML node observers.
static cairo_user_data_key_t key
GC-managed XML node implementation.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
SVG <pattern> implementation.
SPRoot: SVG <svg> implementation.
TODO: insert short description here.
The styles used for simple XML syntax highlighting.
Wrapper for the GLib value API.