Improvements on layer list search and filtering

This commit is contained in:
Matthias Koefferlein 2021-02-13 09:51:28 +01:00
parent 71f64f5f63
commit 0f8dc9ac13
4 changed files with 189 additions and 121 deletions

View File

@ -34,6 +34,7 @@
#include "layDialogs.h"
#include "layLayoutCanvas.h"
#include "layAbstractMenu.h"
#include "layQtTools.h"
#include "tlExceptions.h"
#include "tlInternational.h"
#include "tlAssert.h"
@ -203,12 +204,10 @@ LayerControlPanel::LayerControlPanel (lay::LayoutView *view, db::Manager *manage
mp_view (view),
m_needs_update (true),
m_tabs_need_update (true),
m_force_update_hidden_flags (true),
m_in_update (false),
m_phase (0),
m_do_update_content_dm (this, &LayerControlPanel::do_update_content),
m_hide_empty_layers (false),
m_test_shapes_in_view (false),
m_do_update_hidden_flags_dm (this, &LayerControlPanel::do_update_hidden_flags),
m_no_stipples (false)
{
setObjectName (QString::fromUtf8 (name));
@ -269,11 +268,18 @@ LayerControlPanel::LayerControlPanel (lay::LayoutView *view, db::Manager *manage
mp_case_sensitive->setChecked (true);
mp_case_sensitive->setText (tr ("Case sensitive search"));
mp_filter = new QAction (this);
mp_filter->setCheckable (true);
mp_filter->setChecked (false);
mp_filter->setText (tr ("Apply as filter"));
QMenu *m = new QMenu (mp_search_edit_box);
m->addAction (mp_use_regular_expressions);
m->addAction (mp_case_sensitive);
m->addAction (mp_filter);
connect (mp_use_regular_expressions, SIGNAL (triggered ()), this, SLOT (search_edited ()));
connect (mp_case_sensitive, SIGNAL (triggered ()), this, SLOT (search_edited ()));
connect (mp_filter, SIGNAL (triggered ()), this, SLOT (search_edited ()));
mp_search_edit_box->set_clear_button_enabled (true);
mp_search_edit_box->set_options_button_enabled (true);
@ -370,6 +376,8 @@ LayerControlPanel::LayerControlPanel (lay::LayoutView *view, db::Manager *manage
m_no_stipples_label->setPixmap (QPixmap (QString::fromUtf8 (":/important.png")));
m_no_stipples_label->setToolTip (tr ("Stipples are disabled - unselect \"View/Show Layers Without Fill\" to re-enable them"));
ltb->addWidget (m_no_stipples_label);
connect (mp_model, SIGNAL (hidden_flags_need_update ()), this, SLOT (update_hidden_flags ()));
}
LayerControlPanel::~LayerControlPanel ()
@ -1126,6 +1134,10 @@ LayerControlPanel::search_edited ()
return;
}
mp_model->set_filter_mode (mp_filter->isChecked ());
bool filter_invalid = false;
QString t = mp_search_edit_box->text ();
if (t.isEmpty ()) {
mp_model->clear_locate ();
@ -1135,8 +1147,12 @@ LayerControlPanel::search_edited ()
mp_layer_list->setCurrentIndex (found);
if (found.isValid ()) {
mp_layer_list->scrollTo (found);
} else {
filter_invalid = true;
}
}
lay::indicate_error (mp_search_edit_box, filter_invalid);
}
void
@ -1642,27 +1658,28 @@ LayerControlPanel::set_text_color (QColor c)
mp_model->set_text_color (c);
}
void
void
LayerControlPanel::update_hidden_flags ()
{
m_do_update_hidden_flags_dm ();
}
void
LayerControlPanel::set_hide_empty_layers (bool f)
{
if (f != m_hide_empty_layers) {
m_hide_empty_layers = f;
m_force_update_hidden_flags = true;
m_do_update_content_dm ();
}
mp_model->set_hide_empty_layers (f);
}
bool
LayerControlPanel::hide_empty_layers ()
{
return mp_model->get_hide_empty_layers ();
}
void
LayerControlPanel::set_test_shapes_in_view (bool f)
{
if (f != m_test_shapes_in_view) {
m_test_shapes_in_view = f;
mp_model->set_test_shapes_in_view (f);
if (m_hide_empty_layers) {
m_force_update_hidden_flags = true;
}
m_do_update_content_dm ();
}
mp_model->set_test_shapes_in_view (f);
}
void
@ -1707,7 +1724,6 @@ LayerControlPanel::cancel_updates ()
void
LayerControlPanel::end_updates ()
{
m_force_update_hidden_flags = true;
do_update_content ();
}
@ -1721,7 +1737,7 @@ LayerControlPanel::set_phase (int phase)
}
static void
set_hidden_flags_within_view_rec (LayerTreeModel *model, QTreeView *tree_view, const QModelIndex &parent, bool hide_empty)
set_hidden_flags_rec (LayerTreeModel *model, QTreeView *tree_view, const QModelIndex &parent)
{
int rows = model->rowCount (parent);
for (int r = 0; r < rows; ++r) {
@ -1730,7 +1746,7 @@ set_hidden_flags_within_view_rec (LayerTreeModel *model, QTreeView *tree_view, c
if (! model->hasChildren (index)) {
if (hide_empty && model->empty_within_view_predicate (index)) {
if (model->is_hidden (index)) {
tree_view->setRowHidden (r, parent, true);
} else {
tree_view->setRowHidden (r, parent, false);
@ -1738,43 +1754,33 @@ set_hidden_flags_within_view_rec (LayerTreeModel *model, QTreeView *tree_view, c
} else {
tree_view->setRowHidden (r, parent, false);
set_hidden_flags_within_view_rec (model, tree_view, index, hide_empty);
}
}
}
static void
set_hidden_flags_rec (LayerTreeModel *model, QTreeView *tree_view, const QModelIndex &parent, bool hide_empty)
{
int rows = model->rowCount (parent);
for (int r = 0; r < rows; ++r) {
QModelIndex index = model->index (r, 0, parent);
if (! model->hasChildren (index)) {
if (hide_empty && model->empty_predicate (index)) {
tree_view->setRowHidden (r, parent, true);
} else {
tree_view->setRowHidden (r, parent, false);
}
} else {
tree_view->setRowHidden (r, parent, false);
set_hidden_flags_rec (model, tree_view, index, hide_empty);
set_hidden_flags_rec (model, tree_view, index);
}
}
}
void
LayerControlPanel::do_update_hidden_flags ()
{
set_hidden_flags_rec (mp_model, mp_layer_list, QModelIndex ());
// scroll the current index into view if it was not visible before
QModelIndex current = mp_layer_list->currentIndex ();
if (current.isValid ()) {
QModelIndex parent = mp_layer_list->model ()->parent (current);
if (! mp_layer_list->isRowHidden (current.row (), parent)) {
QRect visual_rect = mp_layer_list->visualRect (current);
if (! visual_rect.intersects (mp_layer_list->viewport ()->rect ())) {
mp_layer_list->scrollTo (current, QAbstractItemView::PositionAtCenter);
}
}
}
}
void
LayerControlPanel::do_update_content ()
{
// clear search. TODO: update search instead of clearing
mp_search_edit_box->clear ();
mp_model->clear_locate();
mp_model->set_phase (m_phase);
if (m_tabs_need_update) {
@ -1853,48 +1859,21 @@ LayerControlPanel::do_update_content ()
m_needs_update = false;
} else if (m_needs_update) {
m_needs_update = false;
bool has_children = false;
for (lay::LayerPropertiesConstIterator l = mp_view->begin_layers (); l != mp_view->end_layers () && ! has_children; ++l) {
if (l->has_children ()) {
has_children = true;
}
}
mp_layer_list->setRootIsDecorated (has_children);
mp_layer_list->reset ();
} else {
if (m_needs_update) {
m_needs_update = false;
bool has_children = false;
for (lay::LayerPropertiesConstIterator l = mp_view->begin_layers (); l != mp_view->end_layers () && ! has_children; ++l) {
if (l->has_children ()) {
has_children = true;
}
}
mp_layer_list->setRootIsDecorated (has_children);
mp_layer_list->reset ();
} else {
mp_model->signal_data_changed (); // this makes the view redraw the data
}
}
if (m_hide_empty_layers || m_force_update_hidden_flags) {
m_force_update_hidden_flags = false;
if (m_test_shapes_in_view) {
set_hidden_flags_within_view_rec (mp_model, mp_layer_list, QModelIndex (), m_hide_empty_layers);
} else {
set_hidden_flags_rec (mp_model, mp_layer_list, QModelIndex (), m_hide_empty_layers);
}
// scroll the current index into view if it was not visible before
QModelIndex current = mp_layer_list->currentIndex ();
if (current.isValid ()) {
QModelIndex parent = mp_layer_list->model ()->parent (current);
if (! mp_layer_list->isRowHidden (current.row (), parent)) {
QRect visual_rect = mp_layer_list->visualRect (current);
if (! visual_rect.intersects (mp_layer_list->viewport ()->rect ())) {
mp_layer_list->scrollTo (current, QAbstractItemView::PositionAtCenter);
}
}
}
mp_model->signal_data_changed (); // this makes the view redraw the data
}
}
@ -1975,7 +1954,7 @@ LayerControlPanel::redo (db::Op *op)
void
LayerControlPanel::signal_vp_changed ()
{
if (m_test_shapes_in_view) {
if (mp_model->get_test_shapes_in_view ()) {
update_required (1);
}
}
@ -2028,7 +2007,7 @@ LayerControlPanel::update_required (int f)
}
if ((f & 3) != 0) {
m_force_update_hidden_flags = true;
m_do_update_hidden_flags_dm ();
}
m_do_update_content_dm ();

View File

@ -184,10 +184,7 @@ public:
/**
* @brief Get the "hide empty layers" flag
*/
bool hide_empty_layers ()
{
return m_hide_empty_layers;
}
bool hide_empty_layers ();
/**
* @brief Set the "test_shapes_in_view" flag
@ -201,7 +198,7 @@ public:
*/
bool test_shapes_in_view ()
{
return m_test_shapes_in_view;
return mp_model->get_test_shapes_in_view ();
}
/**
@ -333,6 +330,9 @@ public slots:
void search_next ();
void search_prev ();
private slots:
void update_hidden_flags ();
private:
QTabBar *mp_tab_bar;
LCPTreeWidget *mp_layer_list;
@ -341,19 +341,17 @@ private:
lay::LayoutView *mp_view;
bool m_needs_update;
bool m_tabs_need_update;
bool m_force_update_hidden_flags;
bool m_in_update;
std::vector<size_t> m_new_sel;
int m_phase;
tl::DeferredMethod<LayerControlPanel> m_do_update_content_dm;
bool m_hide_empty_layers;
bool m_test_shapes_in_view;
tl::DeferredMethod<LayerControlPanel> m_do_update_content_dm, m_do_update_hidden_flags_dm;
std::set<unsigned int> m_expanded;
bool m_no_stipples;
QLabel *m_no_stipples_label;
lay::DecoratedLineEdit *mp_search_edit_box;
QAction *mp_case_sensitive;
QAction *mp_use_regular_expressions;
QAction *mp_filter;
QFrame *mp_search_frame;
QCheckBox *mp_search_close_cb;
@ -369,6 +367,7 @@ private:
void signal_vp_changed ();
void do_update_content ();
void do_update_hidden_flags ();
void do_delete ();
void do_copy ();
void recover ();

View File

@ -180,7 +180,7 @@ EmptyWithinViewCache::determine_empty_layers (const db::Layout *layout, unsigned
LayerTreeModel::LayerTreeModel (QWidget *parent, lay::LayoutView *view)
: QAbstractItemModel (parent),
mp_view (view), m_id_start (0), m_id_end (0), m_phase ((unsigned int) -1), m_test_shapes_in_view (false)
mp_view (view), m_filter_mode (false), m_id_start (0), m_id_end (0), m_phase ((unsigned int) -1), m_test_shapes_in_view (false), m_hide_empty_layers (false)
{
// .. nothing yet ..
}
@ -210,6 +210,37 @@ LayerTreeModel::set_text_color (QColor color)
signal_data_changed ();
}
void
LayerTreeModel::set_test_shapes_in_view (bool f)
{
if (m_test_shapes_in_view != f) {
m_test_shapes_in_view = f;
if (m_hide_empty_layers) {
emit hidden_flags_need_update ();
}
signal_data_changed ();
}
}
void
LayerTreeModel::set_hide_empty_layers (bool f)
{
if (m_hide_empty_layers != f) {
m_hide_empty_layers = f;
// we actually can't do this ourselves.
emit hidden_flags_need_update ();
}
}
void
LayerTreeModel::set_filter_mode (bool f)
{
if (f != m_filter_mode) {
m_filter_mode = f;
emit hidden_flags_need_update ();
}
}
void
LayerTreeModel::set_background_color (QColor background)
{
@ -288,6 +319,10 @@ LayerTreeModel::clear_locate ()
m_selected_ids.clear ();
signal_data_changed ();
if (m_filter_mode) {
emit hidden_flags_need_update ();
}
}
QModelIndex
@ -358,6 +393,10 @@ LayerTreeModel::locate (const char *name, bool glob_pattern, bool case_sensitive
signal_data_changed ();
if (m_filter_mode) {
emit hidden_flags_need_update ();
}
m_current_index = m_selected_indexes.begin ();
if (m_current_index == m_selected_indexes.end ()) {
return QModelIndex ();
@ -481,6 +520,22 @@ single_bitmap_to_image (const lay::ViewOp &view_op, lay::Bitmap &bitmap,
lay::bitmaps_to_image (view_ops, pbitmaps, dither_pattern, line_styles, pimage, width, height, false, 0);
}
bool
LayerTreeModel::is_hidden (const QModelIndex &index) const
{
if (m_filter_mode && ! m_selected_ids.empty () && m_selected_ids.find (size_t (index.internalPointer ())) == m_selected_ids.end ()) {
return true;
}
if (! m_hide_empty_layers) {
return false;
} else if (m_test_shapes_in_view) {
return empty_within_view_predicate (index);
} else {
return empty_predicate (index);
}
}
bool
LayerTreeModel::empty_predicate (const QModelIndex &index) const
{

View File

@ -126,21 +126,9 @@ public:
lay::LayerPropertiesConstIterator iterator (const QModelIndex &index) const;
/**
* @brief Get a flag indicating that a layer is empty
* @brief Get a flag indicating that an entry is hidden
*/
bool empty_predicate (const QModelIndex &index) const;
/**
* @brief Get a flag indicating that a layer does not have shapes within the shown area
*/
bool empty_within_view_predicate (const QModelIndex &index) const;
/**
* @brief Set the non-empty layers (the "uint" for the layer iterators) for the "test shapes is view" mode
*
* @return True, if a change has been made.
*/
bool set_non_empty_layers (const std::set <size_t> &non_empty_layers);
bool is_hidden (const QModelIndex &index) const;
/**
* @brief Set the animation phase
@ -201,13 +189,42 @@ public:
void clear_locate ();
/**
* @brief Set the test_shapes_in_view flag
*
* This method does not issue a data changed signal. This has to be done somewhere else.
* @brief Sets a flag indicating whether to test shapes in view for highlighting non-empty layers
*/
void set_test_shapes_in_view (bool f)
void set_test_shapes_in_view (bool f);
/**
* @brief Gets a flag indicating whether to test shapes in view for highlighting non-empty layers
*/
bool get_test_shapes_in_view ()
{
m_test_shapes_in_view = f;
return m_test_shapes_in_view;
}
/**
* @brief Sets the flag indicating whether to hide empty layers
*/
void set_hide_empty_layers (bool f);
/**
* @brief Gets the flag indicating whether to hide empty layers
*/
bool get_hide_empty_layers () const
{
return m_hide_empty_layers;
}
/**
* @brief Sets a flag indicating whether selected indexes are filtered or highlighted
*/
void set_filter_mode (bool f);
/**
* @brief Gets a flag indicating whether selected indexes are filtered or highlighted
*/
bool get_filter_mode () const
{
return m_filter_mode;
}
/**
@ -220,11 +237,19 @@ public:
*/
void signal_layer_changed ();
signals:
/**
* @brief This signal is emitted to indicate
*/
void hidden_flags_need_update ();
private:
lay::LayoutView *mp_view;
bool m_filter_mode;
size_t m_id_start, m_id_end;
unsigned int m_phase;
bool m_test_shapes_in_view;
bool m_hide_empty_layers;
QFont m_font;
QColor m_text_color, m_background_color;
mutable EmptyWithinViewCache m_test_shapes_cache;
@ -232,6 +257,16 @@ private:
std::vector <QModelIndex> m_selected_indexes;
std::vector <QModelIndex>::const_iterator m_current_index;
/**
* @brief Get a flag indicating that a layer is empty
*/
bool empty_predicate (const QModelIndex &index) const;
/**
* @brief Get a flag indicating that a layer does not have shapes within the shown area
*/
bool empty_within_view_predicate (const QModelIndex &index) const;
void search_children (const tl::GlobPattern &pattern, const QModelIndex &parent, bool recurse);
};