summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/dialog')
-rw-r--r--src/ui/dialog/objects.cpp224
-rw-r--r--src/ui/dialog/objects.h15
2 files changed, 153 insertions, 86 deletions
diff --git a/src/ui/dialog/objects.cpp b/src/ui/dialog/objects.cpp
index 553f4f909..730ad48c0 100644
--- a/src/ui/dialog/objects.cpp
+++ b/src/ui/dialog/objects.cpp
@@ -288,7 +288,10 @@ void ObjectsPanel::_removeWatchers() {
}
}
-void ObjectsPanel::_objectsChangedWrapper(SPObject *obj) {
+/**
+ * Call function for asynchronous invocation of _objectsChanged
+ */
+void ObjectsPanel::_objectsChangedWrapper(SPObject */*obj*/) {
// We used to call _objectsChanged with a reference to _obj,
// but since _obj wasn't used, I'm dropping that for now
_takeAction(UPDATE_TREE);
@@ -298,10 +301,9 @@ void ObjectsPanel::_objectsChangedWrapper(SPObject *obj) {
* Callback function for when an object changes. Essentially refreshes the entire tree
* @param obj Object which was changed (currently not used as the entire tree is recreated)
*/
-
void ObjectsPanel::_objectsChanged(SPObject */*obj*/)
{
- //First, unattach the watchers
+ //First, detach the watchers
_removeWatchers();
if (_desktop) {
@@ -309,19 +311,24 @@ void ObjectsPanel::_objectsChanged(SPObject */*obj*/)
SPDocument* document = _desktop->doc();
SPRoot* root = document->getRoot();
if ( root ) {
- _selectedConnection.block();
+ _selectedConnection.block(); // Will be unblocked after the queue has been processed fully
_documentChangedCurrentLayer.block();
+
//Clear the tree store
- _store->clear();
- _tree_cache.clear();
- //Add all items recursively
- _addObject( root, nullptr );
- _selectedConnection.unblock();
- _documentChangedCurrentLayer.unblock();
- //Set the tree selection
- _objectsSelected(_desktop->selection);
- //Handle button sensitivity
- _checkTreeSelection();
+ _store->clear(); // This will increment it's stamp, making all old iterators
+ _tree_cache.clear(); // invalid. So we will also clear our own cache, as well
+ _tree_update_queue.clear(); // as any remaining update queue
+
+ _tree.unset_model(); // temporarily detach the TreeStore from the TreeView for prevent flickering, and to speed up
+
+ //Add all items recursively; we will do this asynchronously, by first filling a queue, which is rather fast
+ _queueObject( root, nullptr );
+ //However, the processing of this queue is slow, so this is done at a low priority and in small chunks. Using
+ //only small chunks keeps Inkscape responsive, for example while using the spray tool. After processing each
+ //of the chunks, Inkscape will check if there are other tasks with a high priority, for example when user is
+ //spraying. If so, the sprayed objects will be added first, and the whole updating will be restarted before
+ //it even finished.
+ _processQueue_sig = Glib::signal_timeout().connect( sigc::mem_fun(*this, &ObjectsPanel::_processQueue), 0, Glib::PRIORITY_DEFAULT_IDLE+100);
}
}
}
@@ -331,59 +338,98 @@ void ObjectsPanel::_objectsChanged(SPObject */*obj*/)
* @param obj Root object to add to the tree
* @param parentRow Parent tree row (or NULL if adding to tree root)
*/
-void ObjectsPanel::_addObject(SPObject* obj, Gtk::TreeModel::Row* parentRow)
+void ObjectsPanel::_queueObject(SPObject* obj, Gtk::TreeModel::Row* parentRow)
{
- if ( _desktop && obj ) {
- for(auto& child: obj->children) {
- if (SP_IS_ITEM(&child))
- {
- SPItem * item = SP_ITEM(&child);
- SPGroup * group = SP_IS_GROUP(&child) ? SP_GROUP(&child) : nullptr;
-
- //Add the item to the tree and set the column information
- Gtk::TreeModel::iterator iter = parentRow ? _store->prepend(parentRow->children()) : _store->prepend();
+ for(auto& child: obj->children) {
+ if (SP_IS_ITEM(&child)) {
+ //Add the item to the tree, basically only creating an empty row in the tree view
+ Gtk::TreeModel::iterator iter = parentRow ? _store->prepend(parentRow->children()) : _store->prepend();
+
+ //Add the item to a queue, so we can fill in the data in each row asynchronously
+ //at a later stage. See the comments in _objectsChanged() for more details
+ bool expanded = SP_IS_GROUP(obj) && SP_GROUP(obj)->expanded();
+ _tree_update_queue.emplace_back(SP_ITEM(&child), iter, expanded);
+
+ //If the item is a group, recursively add its children
+ if (SP_IS_GROUP(&child)) {
Gtk::TreeModel::Row row = *iter;
- row[_model->_colObject] = item;
+ _queueObject(&child, &row);
+ }
+ }
+ }
+}
- //g_assert(_store->get_flags() & Gtk::TREE_MODEL_ITERS_PERSIST);
- _tree_cache.emplace(item, iter); // Use a row reference instead of an iter maybe?
+/**
+ * Walks through the queue in small chunks, and fills in the rows in the tree view accordingly
+ * @return False if the queue has been fully emptied
+ */
+bool ObjectsPanel::_processQueue() {
+ auto queue_iter = _tree_update_queue.begin();
+ auto queue_end = _tree_update_queue.end();
+ int count = 0;
+
+ while (queue_iter != queue_end) {
+ //The queue is a list of tuples; expand the tuples
+ SPItem *item = std::get<0>(*queue_iter);
+ Gtk::TreeModel::iterator iter = std::get<1>(*queue_iter);
+ bool expanded = std::get<2>(*queue_iter);
+ //Add the object to the tree view and tree cache
+ _addObject(item, *iter, expanded);
+ _tree_cache.emplace(item, *iter);
+
+ //Add an object watcher to the item
+ ObjectsPanel::ObjectWatcher *w = new ObjectsPanel::ObjectWatcher(this, item);
+ item->getRepr()->addObserver(*w);
+ _objectWatchers.push_back(w);
+
+ queue_iter = _tree_update_queue.erase(queue_iter);
+ count++;
+ if (count == 100) {
+ return true; // we have not yet reached the end, so return true to keep the timeout signal alive
+ }
+ }
- //this seems to crash on convert stroke to path then undo (probably no ID?)
- try {
- row[_model->_colLabel] = item->label() ? item->label() : item->getId();
- } catch (...) {
- row[_model->_colLabel] = Glib::ustring("getId_failure");
- g_critical("item->getId() failed, using \"getId_failure\"");
- }
- row[_model->_colVisible] = !item->isHidden();
- row[_model->_colLocked] = !item->isSensitive();
- row[_model->_colType] = group ? (group->layerMode() == SPGroup::LAYER ? 2 : 1) : 0;
- row[_model->_colHighlight] = item->isHighlightSet() ? item->highlight_color() : item->highlight_color() & 0xffffff00;
- row[_model->_colClipMask] = item ? (
- (item->clip_ref && item->clip_ref->getObject() ? 1 : 0) |
- (item->mask_ref && item->mask_ref->getObject() ? 2 : 0)
- ) : 0;
- //row[_model->_colInsertOrder] = group ? (group->insertBottom() ? 2 : 1) : 0;
-
- //If our parent object is a group and it's expanded, expand the tree
- if (SP_IS_GROUP(obj) && SP_GROUP(obj)->expanded())
- {
- _tree.expand_to_path( _store->get_path(iter) );
- _tree.collapse_row( _store->get_path(iter) );
- }
+ //We have reached the end of the queue, so we can now bring the tree view back to life safely
+ _tree.set_model(_store); // Attach the store again to the tree view
+ _blockAllSignals(false);
+ _objectsSelected(_desktop->selection); //Set the tree selection
+ _checkTreeSelection(); //Handle button sensitivity
+ return false; // Return false to kill the timeout signal that kept calling _processQueue
+}
- //Add an object watcher to the item
- ObjectsPanel::ObjectWatcher *w = new ObjectsPanel::ObjectWatcher(this, &child);
- child.getRepr()->addObserver(*w);
- _objectWatchers.push_back(w);
+/**
+ * Fills in the details of an item in the already existing row of the tree view
+ * @param item Item of which the name, visibility, lock status, etc, will be filled in
+ * @param row Row where the item is residing
+ * @param expanded True if the item is part of a group that is shown as expanded in the tree view
+ */
+void ObjectsPanel::_addObject(SPItem* item, const Gtk::TreeModel::Row &row, bool expanded)
+{
+ SPGroup * group = SP_IS_GROUP(item) ? SP_GROUP(item) : nullptr;
- //If the item is a group, recursively add its children
- if (group)
- {
- _addObject( &child, &row );
- }
- }
- }
+ row[_model->_colObject] = item;
+
+ //this seems to crash on convert stroke to path then undo (probably no ID?)
+ try {
+ row[_model->_colLabel] = item->label() ? item->label() : item->getId();
+ } catch (...) {
+ row[_model->_colLabel] = Glib::ustring("getId_failure");
+ g_critical("item->getId() failed, using \"getId_failure\"");
+ }
+ row[_model->_colVisible] = !item->isHidden();
+ row[_model->_colLocked] = !item->isSensitive();
+ row[_model->_colType] = group ? (group->layerMode() == SPGroup::LAYER ? 2 : 1) : 0;
+ row[_model->_colHighlight] = item->isHighlightSet() ? item->highlight_color() : item->highlight_color() & 0xffffff00;
+ row[_model->_colClipMask] = item ? (
+ (item->clip_ref && item->clip_ref->getObject() ? 1 : 0) |
+ (item->mask_ref && item->mask_ref->getObject() ? 2 : 0)
+ ) : 0;
+ //row[_model->_colInsertOrder] = group ? (group->insertBottom() ? 2 : 1) : 0;
+
+ //If our parent object is a group and it's expanded, expand the tree
+ if (expanded) {
+ _tree.expand_to_path( _store->get_path(row) );
+ _tree.collapse_row( _store->get_path(row) );
}
}
@@ -544,6 +590,13 @@ void ObjectsPanel::_setCompositingValues(SPItem *item)
_opacityConnection.unblock();
}
+// See the comment in objects.h for _tree_cache
+/**
+ * Find the specified item in the tree cache
+ * @param iter Current tree item
+ * @param tree_iter Tree_iter will point to the row in which the tree item was found
+ * @return True if found
+ */
bool ObjectsPanel::_findInTreeCache(SPItem* item, Gtk::TreeModel::iterator &tree_iter) {
if (not item) {
return false;
@@ -553,8 +606,10 @@ bool ObjectsPanel::_findInTreeCache(SPItem* item, Gtk::TreeModel::iterator &tree
tree_iter = _tree_cache.at(item);
}
catch (std::out_of_range) {
- // Apparently, item cannot be found in the tree_cache, which could mean that the tree and/or tree_cache
- // are out-dated? Could this happen when a tree update is pending?
+ // Apparently, item cannot be found in the tree_cache, which could mean that
+ // - the tree and/or tree_cache are out-dated or in the process of being updated.
+ // - a layer is selected, which is not visible in the objects panel (see _objectsSelected())
+ // Anyway, this doesn't seem all that critical, so no warnings; just return false
return false;
}
@@ -574,10 +629,9 @@ bool ObjectsPanel::_findInTreeCache(SPItem* item, Gtk::TreeModel::iterator &tree
/**
* Find the specified item in the tree store and (de)select it, optionally scrolling to the item
- * @param path Current tree path
- * @param iter Current tree item
* @param item Item to select in the tree
* @param scrollto Whether to scroll to the item
+ * @param expand If true, the path in the tree towards item will be expanded
*/
void ObjectsPanel::_updateObjectSelected(SPItem* item, bool scrollto, bool expand)
{
@@ -1130,6 +1184,9 @@ void ObjectsPanel::_doTreeMove( )
_("Moved objects"));
}
+/**
+ * Prevents the treeview from emiting and responding to most signals; needed when it's not up to date
+ */
void ObjectsPanel::_blockAllSignals(bool should_block = true) {
// incoming signals
@@ -1143,14 +1200,13 @@ void ObjectsPanel::_blockAllSignals(bool should_block = true) {
// become unpredictable after the tree has been updated
_pending->_signal.disconnect();
}
- _removeWatchers();
_selectionChangedConnection.block(should_block);
// outgoing signal
_selectedConnection.block(should_block);
- // These are not blocked
+ // These are not blocked; this is ok because they will invoke _objectsChanged immediately (synchronously), and update the tree
// desktopChangeConn, _documentChangedConnection;
}
@@ -1171,9 +1227,7 @@ void ObjectsPanel::_fireAction( unsigned int code )
}
bool ObjectsPanel::_executeUpdate() {
- _blockAllSignals(false);
_objectsChanged(nullptr);
- _pendingUpdateTree = false;
return false;
}
@@ -1183,11 +1237,20 @@ bool ObjectsPanel::_executeUpdate() {
void ObjectsPanel::_takeAction( int val )
{
if (val == UPDATE_TREE) {
- if (not _pendingUpdateTree) {
- _pendingUpdateTree = true;
- _blockAllSignals(true);
- Glib::signal_timeout().connect( sigc::mem_fun(*this, &ObjectsPanel::_executeUpdate), 0 );
- }
+ // We might already have been updating the tree, but new data is available now
+ // so we will then first cancel the old update before scheduling a new one
+ _processQueue_sig.disconnect();
+ _executeUpdate_sig.disconnect();
+ _blockAllSignals(true);
+ _removeWatchers();
+ _store->clear();
+ _tree_cache.clear();
+ _executeUpdate_sig = Glib::signal_timeout().connect( sigc::mem_fun(*this, &ObjectsPanel::_executeUpdate), 500, Glib::PRIORITY_DEFAULT_IDLE+50);
+ // In the spray tool, updating the tree competes in priority with the redrawing of the canvas,
+ // see SPCanvas::addIdle(), which is set to UPDATE_PRIORITY (=G_PRIORITY_DEFAULT_IDLE). We
+ // should take a lower priority (= higher value) to keep the spray tool updating longer, and to prevent
+ // the objects-panel from clogging the processor; however, once the spraying slows down, the tree might
+ // get updated anyway.
} else if ( !_pending ) {
_pending = new InternalUIBounce();
_pending->_actionCode = val;
@@ -1686,7 +1749,6 @@ ObjectsPanel::ObjectsPanel() :
_document(nullptr),
_model(nullptr),
_pending(nullptr),
- _pendingUpdateTree(false),
_toggleEvent(nullptr),
_defer_target(),
_visibleHeader(C_("Visibility", "V")),
@@ -2043,14 +2105,8 @@ ObjectsPanel::~ObjectsPanel()
void ObjectsPanel::setDocument(SPDesktop* /*desktop*/, SPDocument* document)
{
//Clear all object watchers
- while (!_objectWatchers.empty())
- {
- ObjectsPanel::ObjectWatcher *w = _objectWatchers.back();
- w->_repr->removeObserver(*w);
- _objectWatchers.pop_back();
- delete w;
- }
-
+ _removeWatchers();
+
//Delete the root watcher
if (_rootWatcher)
{
@@ -2058,7 +2114,7 @@ void ObjectsPanel::setDocument(SPDesktop* /*desktop*/, SPDocument* document)
delete _rootWatcher;
_rootWatcher = nullptr;
}
-
+
_document = document;
if (document && document->getRoot() && document->getRoot()->getRepr())
diff --git a/src/ui/dialog/objects.h b/src/ui/dialog/objects.h
index 21e6c6ce3..5c8de59dd 100644
--- a/src/ui/dialog/objects.h
+++ b/src/ui/dialog/objects.h
@@ -90,6 +90,9 @@ private:
sigc::connection _blendConnection;
sigc::connection _blurConnection;
+ sigc::connection _processQueue_sig;
+ sigc::connection _executeUpdate_sig;
+
//Desktop tracker for grabbing the desktop changed connection
DesktopTracker _deskTrack;
@@ -109,7 +112,6 @@ private:
//
InternalUIBounce* _pending;
- bool _pendingUpdateTree;
//Whether the drag & drop was dragged into an item
gboolean _dnd_into;
@@ -133,6 +135,13 @@ private:
Gtk::TreeModel::Path _defer_target;
Glib::RefPtr<Gtk::TreeStore> _store;
+ std::list<std::tuple<SPItem*, Gtk::TreeModel::iterator, bool> > _tree_update_queue;
+ //When the user selects an item in the document, we need to find that item in the tree view
+ //and highlight it. When looking up a specific item in the tree though, we don't want to have
+ //to iterate through the whole list, as this would take too long if the list is very long. So
+ //we will use a std::map for this instead, which is much faster (and call it _tree_cache). It
+ //would have been cleaner to create our own custom tree model, as described here
+ //https://en.wikibooks.org/wiki/GTK%2B_By_Example/Tree_View/Tree_Models
std::map<SPItem*, Gtk::TreeModel::iterator> _tree_cache;
std::vector<Gtk::Widget*> _watching;
@@ -217,7 +226,9 @@ private:
void _removeWatchers();
void _objectsChangedWrapper(SPObject *obj);
void _objectsChanged(SPObject *obj);
- void _addObject( SPObject* obj, Gtk::TreeModel::Row* parentRow );
+ bool _processQueue();
+ void _queueObject(SPObject* obj, Gtk::TreeModel::Row* parentRow);
+ void _addObject(SPItem* item, const Gtk::TreeModel::Row &parentRow, bool expanded);
void _isolationChangedIter(const Gtk::TreeIter &iter);
void _isolationValueChanged();