From bf15e46c10560d9d83abd809bd3c476eaa59583b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 28 Feb 2017 22:24:14 +0100 Subject: [PATCH] "Find cell" feature: just type into the cell list This feature includes: * Enhancements to tl::GlobPattern (exact mode, case insensitive) TODO: UTF8 support * Enhancements to DecoratedLineEdit (ESC key handling, Tab key handling) --- src/klayout.pro | 3 + src/laybasic/SimpleCellSelectionForm.ui | 379 +++++++++++++---------- src/laybasic/layCellSelectionForm.cc | 24 +- src/laybasic/layCellTreeModel.cc | 117 +++++-- src/laybasic/layCellTreeModel.h | 16 +- src/laybasic/layHierarchyControlPanel.cc | 207 ++++++++++++- src/laybasic/layHierarchyControlPanel.h | 18 ++ src/laybasic/layWidgets.cc | 53 +++- src/laybasic/layWidgets.h | 34 ++ src/tl/tlGlobPattern.cc | 93 ++++-- src/tl/tlGlobPattern.h | 33 ++ src/unit_tests/tlGlobPattern.cc | 61 +++- 12 files changed, 801 insertions(+), 237 deletions(-) diff --git a/src/klayout.pro b/src/klayout.pro index f1fcc52d2..765639077 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -74,3 +74,6 @@ plugins.depends += lay ext lib ut klayout_main.depends += lay ext lib plugins unit_tests.depends += ut plugins +RESOURCES += \ + laybasic/layResources.qrc + diff --git a/src/laybasic/SimpleCellSelectionForm.ui b/src/laybasic/SimpleCellSelectionForm.ui index 5d9e620c8..0ac66895d 100644 --- a/src/laybasic/SimpleCellSelectionForm.ui +++ b/src/laybasic/SimpleCellSelectionForm.ui @@ -1,7 +1,8 @@ - + + SimpleCellSelectionForm - - + + 0 0 @@ -9,40 +10,58 @@ 525 - + Select Cell - - - 9 - - + + 6 + + 9 + + + 9 + + + 9 + + + 9 + - - + + QFrame::NoFrame - + QFrame::Raised - - + + 0 - + + 0 + + + 0 + + + 0 + + 6 - + - + Qt::Vertical - + QSizePolicy::Fixed - + 451 16 @@ -50,62 +69,70 @@ - - - + + + QFrame::NoFrame - + QFrame::Raised - - + + 0 - + + 0 + + + 0 + + + 0 + + 6 - - - - - 7 - 0 + + + + 1 1 - - - + + + (* and ? can be used to match any text) - - - + + + Selected cell - - - - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Find next</p></body></html> +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Find next</p></body></html> - + ... - - :/find.png + + + :/find.png:/find.png - + true @@ -113,83 +140,95 @@ p, li { white-space: pre-wrap; } - - - + + + Cell list - - - - - 5 - 7 + + + + 1 1 - + QAbstractItemView::ExtendedSelection - + true - - - - - 5 - 5 + + + + 1 0 - + QFrame::NoFrame - + QFrame::Raised - - - 0 - - + + 6 + + 0 + + + 0 + + + 0 + + + 0 + - - - - 5 - 5 + + + 1 1 - + QFrame::NoFrame - + QFrame::Raised - - + + 0 - + + 0 + + + 0 + + + 0 + + 6 - + - + Qt::Horizontal - + 40 20 @@ -197,50 +236,46 @@ p, li { white-space: pre-wrap; } - - - - - 7 - 7 + + + + 1 1 - + true - - - + + + Parents - - - - - 5 - 5 + + + + 0 0 - + Select - - - + + + - - :/right.png + + :/right.png @@ -248,34 +283,41 @@ p, li { white-space: pre-wrap; } - - - - 5 - 5 + + + 1 1 - + QFrame::NoFrame - + QFrame::Raised - - + + 0 - + + 0 + + + 0 + + + 0 + + 6 - + - + Qt::Horizontal - + 40 20 @@ -283,50 +325,46 @@ p, li { white-space: pre-wrap; } - - - + + + Children - - - - - 7 - 7 + + + + 1 1 - + true - - - - - 5 - 5 + + + + 0 0 - + Select - - - + + + - - :/right.png + + :/right.png @@ -341,13 +379,13 @@ p, li { white-space: pre-wrap; } - + Qt::Vertical - + QSizePolicy::Fixed - + 611 16 @@ -356,36 +394,45 @@ p, li { white-space: pre-wrap; } - - + + Qt::Horizontal - - + + QFrame::NoFrame - + QFrame::Raised - - + + + 6 + + 0 - - 6 + + 0 + + + 0 + + + 0 - + Qt::Horizontal - + QSizePolicy::Expanding - + 91 31 @@ -394,19 +441,19 @@ p, li { white-space: pre-wrap; } - - - _ok + + + Ok - + true - - - _cancel + + + Cancel @@ -415,9 +462,9 @@ p, li { white-space: pre-wrap; } - + - + diff --git a/src/laybasic/layCellSelectionForm.cc b/src/laybasic/layCellSelectionForm.cc index 9fd49dd58..4c7f22409 100644 --- a/src/laybasic/layCellSelectionForm.cc +++ b/src/laybasic/layCellSelectionForm.cc @@ -394,18 +394,22 @@ CellSelectionForm::name_changed (const QString &s) return; } - QModelIndex mi = model->locate (tl::to_string (s).c_str (), true); - if (mi.isValid ()) { - - m_cells_cb_enabled = false; - lv_cells->selectionModel ()->setCurrentIndex (mi, QItemSelectionModel::SelectCurrent); - lv_cells->scrollTo (mi); - update_children_list (); - update_parents_list (); - m_cells_cb_enabled = true; - + QModelIndex mi; + if (!s.isEmpty ()) { + mi = model->locate (tl::to_string (s).c_str (), true); + } else { + model->clear_locate (); } + m_cells_cb_enabled = false; + lv_cells->selectionModel ()->setCurrentIndex (mi, QItemSelectionModel::SelectCurrent); + if (mi.isValid ()) { + lv_cells->scrollTo (mi); + } + update_children_list (); + update_parents_list (); + m_cells_cb_enabled = true; + } } diff --git a/src/laybasic/layCellTreeModel.cc b/src/laybasic/layCellTreeModel.cc index 980fda7a7..6cb7e928a 100644 --- a/src/laybasic/layCellTreeModel.cc +++ b/src/laybasic/layCellTreeModel.cc @@ -27,6 +27,7 @@ #include "dbPCellHeader.h" #include +#include #include #include @@ -314,22 +315,24 @@ CellTreeModel::build_top_level () m_flat = true; // no "hierarchical children" yet. - m_toplevel.reserve (mp_base->child_cells ()); - - for (db::Cell::child_cell_iterator child = mp_base->begin_child_cells (); ! child.at_end (); ++child) { - CellTreeItem *item = new CellTreeItem (mp_layout, 0, false, *child, true, m_sorting); - m_toplevel.push_back (item); + if (mp_base) { + m_toplevel.reserve (mp_base->child_cells ()); + for (db::Cell::child_cell_iterator child = mp_base->begin_child_cells (); ! child.at_end (); ++child) { + CellTreeItem *item = new CellTreeItem (mp_layout, 0, false, *child, true, m_sorting); + m_toplevel.push_back (item); + } } } else if ((m_flags & Parents) != 0) { m_flat = true; // no "hierarchical parents" yet. - m_toplevel.reserve (mp_base->parent_cells ()); - - for (db::Cell::parent_cell_iterator parent = mp_base->begin_parent_cells (); parent != mp_base->end_parent_cells (); ++parent) { - CellTreeItem *item = new CellTreeItem (mp_layout, 0, false, *parent, true, m_sorting); - m_toplevel.push_back (item); + if (mp_base) { + m_toplevel.reserve (mp_base->parent_cells ()); + for (db::Cell::parent_cell_iterator parent = mp_base->begin_parent_cells (); parent != mp_base->end_parent_cells (); ++parent) { + CellTreeItem *item = new CellTreeItem (mp_layout, 0, false, *parent, true, m_sorting); + m_toplevel.push_back (item); + } } } else { @@ -498,8 +501,12 @@ CellTreeModel::data (const QModelIndex &index, int role) const } else if (role == Qt::BackgroundRole) { - if (m_selected_indexes.find (index) != m_selected_indexes.end ()) { - return QVariant (QColor (Qt::blue).lighter (180)); + if (m_selected_indexes_set.find (index) != m_selected_indexes_set.end ()) { + // for selected items pick a color between Highlight and Base + QPalette pl (mp_parent->palette ()); + QColor c1 = pl.color (QPalette::Highlight); + QColor cb = pl.color (QPalette::Base); + return QVariant (QColor ((c1.red () + cb.red ()) / 2, (c1.green () + cb.green ()) / 2, (c1.blue () + cb.blue ()) / 2)); } else { return QVariant (); } @@ -512,11 +519,11 @@ CellTreeModel::data (const QModelIndex &index, int role) const } else { QPalette pl (mp_parent->palette ()); if (mp_view->is_cell_hidden (item->cell_index (), m_cv_index)) { - QColor c1 = pl.color (QColorGroup::Text); - QColor cb = pl.color (QColorGroup::Base); + QColor c1 = pl.color (QPalette::Text); + QColor cb = pl.color (QPalette::Base); return QVariant (QColor ((c1.red () + cb.red ()) / 2, (c1.green () + cb.green ()) / 2, (c1.blue () + cb.blue ()) / 2)); } else { - return QVariant (pl.color (QColorGroup::Text)); + return QVariant (pl.color (QPalette::Text)); } } #else @@ -693,6 +700,7 @@ CellTreeModel::clear_locate () { m_selected_indexes.clear (); m_current_index = m_selected_indexes.begin (); + m_selected_indexes_set.clear (); signal_data_changed (); } @@ -715,8 +723,56 @@ CellTreeModel::locate_next () } } +QModelIndex +CellTreeModel::locate_prev () +{ + if (mp_layout->under_construction () || (mp_layout->manager () && mp_layout->manager ()->transacting ())) { + return QModelIndex (); + } + + if (m_current_index == m_selected_indexes.end ()) { + return QModelIndex (); + } else { + if (m_current_index == m_selected_indexes.begin ()) { + m_current_index = m_selected_indexes.end (); + } + --m_current_index; + return *m_current_index; + } +} + +void +CellTreeModel::search_children (const tl::GlobPattern &pattern, CellTreeItem *item) +{ + int children = item->children (); + for (int i = 0; i < children; ++i) { + CellTreeItem *c = item->child (i); + if (c) { + if (c->name_matches (pattern)) { + m_selected_indexes.push_back (model_index (c)); + } + search_children (pattern, c); + } + } +} + +void +CellTreeModel::search_children (const char *name, CellTreeItem *item) +{ + int children = item->children (); + for (int i = 0; i < children; ++i) { + CellTreeItem *c = item->child (i); + if (c) { + if (c->name_equals (name)) { + m_selected_indexes.push_back (model_index (c)); + } + search_children (name, c); + } + } +} + QModelIndex -CellTreeModel::locate (const char *name, bool glob_pattern) +CellTreeModel::locate (const char *name, bool glob_pattern, bool case_sensitive, bool top_only) { if (mp_layout->under_construction () || (mp_layout->manager () && mp_layout->manager ()->transacting ())) { return QModelIndex (); @@ -724,25 +780,22 @@ CellTreeModel::locate (const char *name, bool glob_pattern) m_selected_indexes.clear (); - std::vector ::const_iterator lc; - if (m_sorting == ByName && ! glob_pattern) { - // employ sorting to look for the cell with that name - lc = std::lower_bound (m_toplevel.begin (), m_toplevel.end (), name, cmp_cell_tree_item_vs_name_f ()); - } else if (glob_pattern) { - tl::GlobPattern p (std::string (name) + "*"); - for (lc = m_toplevel.begin (); lc != m_toplevel.end (); ++lc) { - if ((*lc)->name_matches (p)) { - m_selected_indexes.insert (model_index (*lc)); - } + tl::GlobPattern p = tl::GlobPattern (std::string (name)); + p.set_case_sensitive (case_sensitive); + p.set_exact (!glob_pattern); + p.set_header_match (true); + + for (std::vector ::const_iterator lc = m_toplevel.begin (); lc != m_toplevel.end (); ++lc) { + if ((*lc)->name_matches (p)) { + m_selected_indexes.push_back (model_index (*lc)); } - } else { - // sorting does not help: linear search - for (lc = m_toplevel.begin (); lc != m_toplevel.end (); ++lc) { - if ((*lc)->name_equals (name)) { - m_selected_indexes.insert (model_index (*lc)); - } + if (! top_only) { + search_children (p, *lc); } } + + m_selected_indexes_set.clear (); + m_selected_indexes_set.insert (m_selected_indexes.begin (), m_selected_indexes.end ()); signal_data_changed (); diff --git a/src/laybasic/layCellTreeModel.h b/src/laybasic/layCellTreeModel.h index 8b4706ec5..653d49f04 100644 --- a/src/laybasic/layCellTreeModel.h +++ b/src/laybasic/layCellTreeModel.h @@ -168,16 +168,21 @@ public: /** * @brief Locate an index by name (at least closest) * - * Only top-level items are searched. An invalid model index is returned if + * If top_only is set, only top-level items are searched. An invalid model index is returned if * no corresponding item could be found. */ - QModelIndex locate (const char *name, bool glob_pattern = false); + QModelIndex locate (const char *name, bool glob_pattern = false, bool case_sensitive = true, bool top_only = true); /** * @brief Locate the next index (after the first locate) */ QModelIndex locate_next (); + /** + * @brief Locate the previous index (after the first locate) + */ + QModelIndex locate_prev (); + /** * @brief Clears the locate flags */ @@ -226,11 +231,14 @@ private: int m_cv_index; const db::Cell *mp_base; std::vector m_toplevel; - std::set m_selected_indexes; - std::set ::const_iterator m_current_index; + std::set m_selected_indexes_set; + std::vector m_selected_indexes; + std::vector ::const_iterator m_current_index; void build_top_level (); void clear_top_level (); + void search_children (const tl::GlobPattern &pattern, CellTreeItem *item); + void search_children (const char *name, CellTreeItem *item); }; /** diff --git a/src/laybasic/layHierarchyControlPanel.cc b/src/laybasic/layHierarchyControlPanel.cc index 72ba11eef..4958f1d91 100644 --- a/src/laybasic/layHierarchyControlPanel.cc +++ b/src/laybasic/layHierarchyControlPanel.cc @@ -34,6 +34,7 @@ #include #include #include +#include #include "dbClipboard.h" #include "dbClipboardData.h" @@ -69,13 +70,39 @@ HCPCellTreeWidget::HCPCellTreeWidget (QWidget *parent, const char *name) : QTreeView (parent) { // Don't request focus: this leaves focus on the canvas and the arrow keys functional there - setFocusPolicy (Qt::NoFocus); + // @@@ setFocusPolicy (Qt::NoFocus); -> solve differently!!! + setFocusPolicy (Qt::ClickFocus); + // Allow dragging from here to setDragDropMode (QAbstractItemView::DragOnly); setObjectName (QString::fromUtf8 (name)); } + +bool +HCPCellTreeWidget::event (QEvent *event) +{ + // Handling this event makes the widget receive all keystrokes + if (event->type () == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast (event); + QString t = ke->text (); + if (!t.isEmpty () && t[0].isPrint ()) { + ke->accept (); + } + } + return QTreeView::event (event); +} + +void +HCPCellTreeWidget::keyPressEvent (QKeyEvent *event) +{ + QString t = event->text (); + if (!t.isEmpty ()) { + emit search_triggered (t); + } +} + void HCPCellTreeWidget::startDrag (Qt::DropActions supportedActions) { @@ -216,6 +243,71 @@ HierarchyControlPanel::HierarchyControlPanel (lay::LayoutView *view, QWidget *pa mp_selector->setObjectName (QString::fromUtf8 ("cellview_selection")); ly->addWidget (mp_selector); + mp_search_frame = new QFrame (this); + ly->addWidget (mp_search_frame); + mp_search_frame->hide (); + mp_search_frame->setAutoFillBackground (true); + mp_search_frame->setObjectName (QString::fromUtf8 ("panel")); + mp_search_frame->setFrameStyle (QFrame::Panel | QFrame::Raised); + mp_search_frame->setLineWidth (1); + mp_search_frame->setBackgroundRole (QPalette::Highlight); + + QHBoxLayout *sf_ly = new QHBoxLayout (mp_search_frame); + sf_ly->setMargin (0); + sf_ly->setContentsMargins (0, 0, 0, 0); + sf_ly->setSpacing (0); + + mp_search_close_cb = new QCheckBox (mp_search_frame); + sf_ly->addWidget (mp_search_close_cb); + + mp_search_close_cb->setFocusPolicy (Qt::NoFocus); + mp_search_close_cb->setBackgroundRole (QPalette::Highlight); + mp_search_close_cb->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Preferred)); + QPalette pl (mp_search_close_cb->palette ()); + pl.setColor (QPalette::Foreground, pl.color (QPalette::Active, QPalette::HighlightedText)); + mp_search_close_cb->setPalette (pl); + mp_search_close_cb->setMaximumSize (QSize (mp_search_close_cb->maximumSize ().width (), mp_search_close_cb->sizeHint ().height () - 4)); + connect (mp_search_close_cb, SIGNAL (clicked ()), this, SLOT (search_editing_finished ())); + + mp_search_model = 0; + mp_search_edit_box = new lay::DecoratedLineEdit (mp_search_frame); + mp_search_edit_box->setObjectName (QString::fromUtf8 ("cellview_search_edit_box")); + mp_search_edit_box->set_escape_signal_enabled (true); + mp_search_edit_box->set_tab_signal_enabled (true); + connect (mp_search_edit_box, SIGNAL (returnPressed ()), this, SLOT (search_editing_finished ())); + connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited ())); + connect (mp_search_edit_box, SIGNAL (esc_pressed ()), this, SLOT (search_editing_finished ())); + connect (mp_search_edit_box, SIGNAL (tab_pressed ()), this, SLOT (search_next ())); + connect (mp_search_edit_box, SIGNAL (backtab_pressed ()), this, SLOT (search_prev ())); + sf_ly->addWidget (mp_search_edit_box); + + mp_use_regular_expressions = new QAction (this); + mp_use_regular_expressions->setCheckable (true); + mp_use_regular_expressions->setChecked (true); + mp_use_regular_expressions->setText (tr ("Use expressions (use * and ? for any character)")); + + mp_case_sensitive = new QAction (this); + mp_case_sensitive->setCheckable (true); + mp_case_sensitive->setChecked (true); + mp_case_sensitive->setText (tr ("Case sensitive search")); + + QMenu *m = new QMenu (mp_search_edit_box); + m->addAction (mp_use_regular_expressions); + m->addAction (mp_case_sensitive); + connect (mp_use_regular_expressions, SIGNAL (triggered ()), this, SLOT (search_edited ())); + connect (mp_case_sensitive, SIGNAL (triggered ()), this, SLOT (search_edited ())); + + mp_search_edit_box->set_clear_button_enabled (true); + mp_search_edit_box->set_options_button_enabled (true); + mp_search_edit_box->set_options_menu (m); + + QToolButton *sf_next = new QToolButton (mp_search_frame); + sf_next->setAutoRaise (true); + sf_next->setToolTip (tr ("Find next")); + sf_next->setIcon (QIcon (QString::fromUtf8 (":/find.png"))); + connect (sf_next, SIGNAL (clicked ()), this, SLOT (search_next ())); + sf_ly->addWidget (sf_next); + mp_splitter = new QSplitter (Qt::Vertical, this); ly->addWidget (mp_splitter); @@ -335,6 +427,105 @@ HierarchyControlPanel::cm_cell_select () emit cell_selected (path, active ()); } +void +HierarchyControlPanel::search_triggered (const QString &t) +{ + mp_search_model = 0; + lay::HCPCellTreeWidget *w = dynamic_cast (sender ()); + if (w) { + for (size_t i = 0; i < mp_cell_lists.size (); ++i) { + if (mp_cell_lists [i] == w) { + // Switch the active list for split mode -> CAUTION: this may trigger a search_editing_finished call + select_active (int (i)); + mp_search_model = dynamic_cast (w->model ()); + break; + } + } + } + + if (mp_search_model) { + mp_search_close_cb->setChecked (true); + mp_search_frame->show (); + mp_search_edit_box->setText (t); + mp_search_edit_box->setFocus (); + search_edited (); + } +} + +void +HierarchyControlPanel::search_edited () +{ + QString t = mp_search_edit_box->text (); + + for (std::vector ::const_iterator v = mp_cell_lists.begin (); v != mp_cell_lists.end (); ++v) { + if ((*v)->model () == mp_search_model) { + if (t.isEmpty ()) { + mp_search_model->clear_locate (); + (*v)->setCurrentIndex (QModelIndex ()); + } else { + QModelIndex found = mp_search_model->locate (t.toUtf8 ().constData (), mp_use_regular_expressions->isChecked (), mp_case_sensitive->isChecked (), false); + (*v)->setCurrentIndex (found); + if (found.isValid ()) { + (*v)->scrollTo (found); + } + } + break; + } + } +} + +void +HierarchyControlPanel::search_next () +{ + for (std::vector ::const_iterator v = mp_cell_lists.begin (); v != mp_cell_lists.end (); ++v) { + if ((*v)->model () == mp_search_model) { + QModelIndex found = mp_search_model->locate_next (); + if (found.isValid ()) { + (*v)->setCurrentIndex (found); + (*v)->scrollTo (found); + } + break; + } + } +} + +void +HierarchyControlPanel::search_prev () +{ + for (std::vector ::const_iterator v = mp_cell_lists.begin (); v != mp_cell_lists.end (); ++v) { + if ((*v)->model () == mp_search_model) { + QModelIndex found = mp_search_model->locate_prev (); + if (found.isValid ()) { + (*v)->setCurrentIndex (found); + (*v)->scrollTo (found); + } + break; + } + } +} + +void +HierarchyControlPanel::search_editing_finished () +{ + for (std::vector ::const_iterator v = mp_cell_lists.begin (); v != mp_cell_lists.end (); ++v) { + CellTreeModel *m = dynamic_cast ((*v)->model ()); + if (m) { + m->clear_locate (); + } + } + + // give back the focus to the cell list + for (size_t i = 0; i < mp_cell_lists.size (); ++i) { + if (mp_cell_lists [i]->model () == mp_search_model) { + mp_cell_lists [i]->setFocus (); + break; + } + } + + mp_search_frame->hide (); + mp_search_model = 0; +} + void HierarchyControlPanel::middle_clicked (const QModelIndex &index) { @@ -469,6 +660,13 @@ void HierarchyControlPanel::set_background_color (QColor c) { m_background_color = c; + QColor hl; + if (c.green () > 128) { + hl = QColor (192, 192, 255); + } else { + hl = QColor (0, 0, 80); + } + for (std::vector ::const_iterator f = mp_cell_lists.begin (); f != mp_cell_lists.end (); ++f) { QPalette pl ((*f)->palette ()); pl.setColor (QPalette::Base, c); @@ -490,7 +688,7 @@ HierarchyControlPanel::set_text_color (QColor c) void HierarchyControlPanel::do_full_update_content () { - size_t i = 0; + size_t i = 0; for (std::vector ::const_iterator cv = m_cellviews.begin (); cv != m_cellviews.end (); ++cv, ++i) { if (m_needs_update.size () > i) { m_needs_update [i] = true; @@ -614,6 +812,10 @@ HierarchyControlPanel::display_string (int n) const void HierarchyControlPanel::do_update_content (int cv_index) { + // close the search box since we will modify the model + mp_search_frame->hide (); + mp_search_model = 0; + unsigned int imin = (cv_index < 0 ? 0 : (unsigned int) cv_index); unsigned int imax = (cv_index < 0 ? std::numeric_limits ::max () : (unsigned int) cv_index); @@ -709,6 +911,7 @@ HierarchyControlPanel::do_update_content (int cv_index) connect (cell_list, SIGNAL (cell_clicked (const QModelIndex &)), this, SLOT (clicked (const QModelIndex &))); connect (cell_list, SIGNAL (cell_double_clicked (const QModelIndex &)), this, SLOT (double_clicked (const QModelIndex &))); connect (cell_list, SIGNAL (cell_middle_clicked (const QModelIndex &)), this, SLOT (middle_clicked (const QModelIndex &))); + connect (cell_list, SIGNAL (search_triggered (const QString &)), this, SLOT (search_triggered (const QString &))); mp_cell_lists.push_back (cell_list); mp_cell_list_frames.push_back (cl_frame); diff --git a/src/laybasic/layHierarchyControlPanel.h b/src/laybasic/layHierarchyControlPanel.h index 67dc4f33e..6669b2bc1 100644 --- a/src/laybasic/layHierarchyControlPanel.h +++ b/src/laybasic/layHierarchyControlPanel.h @@ -36,6 +36,7 @@ #include "layViewOp.h" #include "layLayoutView.h" #include "layCellTreeModel.h" +#include "layWidgets.h" #include "tlDeferredExecution.h" class QModelIndex; @@ -44,6 +45,9 @@ class QMenu; class QSplitter; class QFrame; class QToolButton; +class QLineEdit; +class QAction; +class QCheckBox; namespace lay { @@ -67,12 +71,15 @@ signals: void cell_clicked (const QModelIndex &); void cell_double_clicked (const QModelIndex &); void cell_middle_clicked (const QModelIndex &); + void search_triggered (const QString &t); protected: virtual void mouseDoubleClickEvent (QMouseEvent *event); virtual void mousePressEvent (QMouseEvent *event); virtual void mouseReleaseEvent (QMouseEvent *event); virtual void startDrag (Qt::DropActions supportedActions); + virtual void keyPressEvent (QKeyEvent *event); + virtual bool event (QEvent *event); }; /** @@ -259,6 +266,11 @@ public slots: void middle_clicked (const QModelIndex &index); void selection_changed (int index); void context_menu (const QPoint &pt); + void search_triggered (const QString &t); + void search_edited (); + void search_editing_finished (); + void search_next (); + void search_prev (); void cm_cell_select (); private: @@ -277,6 +289,12 @@ private: bool m_split_mode; CellTreeModel::Sorting m_sorting; QComboBox *mp_selector; + lay::DecoratedLineEdit *mp_search_edit_box; + QAction *mp_case_sensitive; + QAction *mp_use_regular_expressions; + CellTreeModel *mp_search_model; + QFrame *mp_search_frame; + QCheckBox *mp_search_close_cb; QSplitter *mp_splitter; QColor m_background_color; QColor m_text_color; diff --git a/src/laybasic/layWidgets.cc b/src/laybasic/layWidgets.cc index 371e3c89a..2b891a757 100644 --- a/src/laybasic/layWidgets.cc +++ b/src/laybasic/layWidgets.cc @@ -934,7 +934,8 @@ const int le_decoration_space = 2; // additional distance between decoration ic DecoratedLineEdit::DecoratedLineEdit (QWidget *parent) : QLineEdit (parent), - m_clear_button_enabled (false), m_options_button_enabled (false), mp_options_menu (0) + m_clear_button_enabled (false), m_options_button_enabled (false), mp_options_menu (0), + m_escape_signal_enabled (false), m_tab_signal_enabled (false) { mp_options_label = new QLabel (this); mp_options_label->hide (); @@ -957,6 +958,56 @@ DecoratedLineEdit::~DecoratedLineEdit () // .. nothing yet .. } +void DecoratedLineEdit::set_escape_signal_enabled (bool en) +{ + m_escape_signal_enabled = en; +} + +void DecoratedLineEdit::set_tab_signal_enabled (bool en) +{ + m_tab_signal_enabled = en; +} + +bool DecoratedLineEdit::event (QEvent *event) +{ + // Handling this event makes the widget receive all keystrokes + if (event->type () == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast (event); + if (ke->key () == Qt::Key_Escape && m_escape_signal_enabled) { + ke->accept (); + } + } + return QLineEdit::event (event); +} + +void DecoratedLineEdit::keyPressEvent (QKeyEvent *event) +{ + if (m_escape_signal_enabled && event->key () == Qt::Key_Escape) { + emit esc_pressed (); + event->accept (); + } else if (m_tab_signal_enabled && event->key () == Qt::Key_Tab) { + emit tab_pressed (); + event->accept (); + } else if (m_tab_signal_enabled && event->key () == Qt::Key_Backtab) { + emit backtab_pressed (); + event->accept (); + } else { + QLineEdit::keyPressEvent (event); + } +} + +bool DecoratedLineEdit::focusNextPrevChild (bool next) +{ + if (m_tab_signal_enabled && isEnabled ()) { + QKeyEvent event (QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); + keyPressEvent (&event); + if (event.isAccepted ()) { + return true; + } + } + return QLineEdit::focusNextPrevChild (next); +} + void DecoratedLineEdit::set_clear_button_enabled (bool en) { if (en != m_clear_button_enabled) { diff --git a/src/laybasic/layWidgets.h b/src/laybasic/layWidgets.h index 138d99eff..738fbaebb 100644 --- a/src/laybasic/layWidgets.h +++ b/src/laybasic/layWidgets.h @@ -392,17 +392,51 @@ public: */ QMenu *options_menu () const; + /** + * @brief Sets a value indicating whether the widgets accepts ESC keys and sends an esc_pressed signal for this + */ + void set_escape_signal_enabled (bool f); + + /** + * @brief gets a value indicating whether the widgets accepts ESC keys and sends an esc_pressed signal for this + */ + bool escape_signal_enabled () const + { + return m_escape_signal_enabled; + } + + /** + * @brief Sets a value indicating whether the widgets accepts Tab keys and sends an tab_pressed or backtab_pressed signal for this + */ + void set_tab_signal_enabled (bool f); + + /** + * @brief gets a value indicating whether the widgets accepts Tab keys and sends an tab_pressed or backtab_pressed signal for this + */ + bool tab_signal_enabled () const + { + return m_tab_signal_enabled; + } + signals: void options_button_clicked (); + void esc_pressed (); + void tab_pressed (); + void backtab_pressed (); protected: void mousePressEvent (QMouseEvent *event); void mouseReleaseEvent (QMouseEvent *event); void resizeEvent (QResizeEvent *event); + void keyPressEvent (QKeyEvent *event); + bool focusNextPrevChild (bool next); + bool event (QEvent *event); private: bool m_clear_button_enabled; bool m_options_button_enabled; + bool m_escape_signal_enabled; + bool m_tab_signal_enabled; QLabel *mp_options_label; QLabel *mp_clear_label; QMenu *mp_options_menu; diff --git a/src/tl/tlGlobPattern.cc b/src/tl/tlGlobPattern.cc index f0f12e516..e36dc8b8d 100644 --- a/src/tl/tlGlobPattern.cc +++ b/src/tl/tlGlobPattern.cc @@ -27,11 +27,11 @@ namespace tl { static bool -do_match (const char *p, const char *s, std::vector *o, std::vector > &bstart) +do_match (const char *p, const char *s, bool cs, bool exact, bool hm, std::vector *o, std::vector > &bstart) { while (*p) { - if (*p == '\\') { + if (!exact && *p == '\\') { ++p; if (!*s || *s != *p) { @@ -42,7 +42,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector } ++s; - } else if (*p == '?') { + } else if (!exact && *p == '?') { ++p; if (! *s) { @@ -50,7 +50,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector } ++s; - } else if (*p == '*') { + } else if (!exact && *p == '*') { ++p; @@ -63,7 +63,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector size_t no = o ? o->size () : 0; while (*s) { - if (do_match (p, s, o, bstart)) { + if (do_match (p, s, cs, exact, hm, o, bstart)) { return true; } bstart = bs; @@ -73,7 +73,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector ++s; } - } else if (*p == '[') { + } else if (!exact && *p == '[') { if (! *s) { return false; @@ -111,7 +111,10 @@ do_match (const char *p, const char *s, std::vector *o, std::vector } if (! hit) { - if (*s >= c1 && *s <= c2) { + if (cs && *s >= c1 && *s <= c2) { + hit = true; + // TODO: implement UTF-8 support + } else if (!cs && tolower (*s) >= tolower (c1) && tolower (*s) <= tolower (c2)) { hit = true; } } @@ -127,7 +130,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector ++p; } - } else if (*p == '{') { + } else if (!exact && *p == '{') { ++p; @@ -158,7 +161,10 @@ do_match (const char *p, const char *s, std::vector *o, std::vector if (hit) { if (! *s) { hit = false; - } else if (*p != *s) { + } else if (cs && *p != *s) { + hit = false; + // TODO: implement UTF-8 support + } else if (!cs && tolower (*p) != tolower (*s)) { hit = false; } else { ++s; @@ -184,7 +190,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector return false; } - } else if (*p == ')') { + } else if (!exact && *p == ')') { ++p; @@ -195,7 +201,7 @@ do_match (const char *p, const char *s, std::vector *o, std::vector bstart.pop_back (); } - } else if (*p == '(') { + } else if (!exact && *p == '(') { ++p; if (o) { @@ -203,33 +209,78 @@ do_match (const char *p, const char *s, std::vector *o, std::vector o->push_back (std::string ()); } - } else if (*s != *p) { - return false; } else { - ++s; - ++p; + + if (cs) { + if (*s != *p) { + return false; + } else { + ++s; + ++p; + } + } else { + // TODO: implement UTF-8 support + if (tolower (*s) != tolower (*p)) { + return false; + } else { + ++s; + ++p; + } + } + } } - return (*s == 0); + return (hm || *s == 0); } GlobPattern::GlobPattern () + : m_case_sensitive (true), m_exact (false), m_header_match (false) { // .. nothing yet .. } GlobPattern::GlobPattern (const std::string &p) - : m_p (p) + : m_p (p), m_case_sensitive (true), m_exact (false), m_header_match (false) { // .. nothing yet .. } +void GlobPattern::set_case_sensitive (bool f) +{ + m_case_sensitive = f; +} + +bool GlobPattern::case_sensitive () const +{ + return m_case_sensitive; +} + +void GlobPattern::set_exact (bool f) +{ + m_exact = f; +} + +bool GlobPattern::exact () const +{ + return m_exact; +} + +void GlobPattern::set_header_match (bool f) +{ + m_header_match = f; +} + +bool GlobPattern::header_match () const +{ + return m_header_match; +} + bool GlobPattern::match (const char *s) const { std::vector > bstart; - return do_match (m_p.c_str (), s, 0, bstart); + return do_match (m_p.c_str (), s, m_case_sensitive, m_exact, m_header_match, 0, bstart); } bool GlobPattern::match (const char *s, std::vector &e) const @@ -238,13 +289,13 @@ bool GlobPattern::match (const char *s, std::vector &e) const e.clear (); } std::vector > bstart; - return do_match (m_p.c_str (), s, &e, bstart); + return do_match (m_p.c_str (), s, m_case_sensitive, m_exact, m_header_match, &e, bstart); } bool GlobPattern::match (const std::string &s) const { std::vector > bstart; - return do_match (m_p.c_str (), s.c_str (), 0, bstart); + return do_match (m_p.c_str (), s.c_str (), m_case_sensitive, m_exact, m_header_match, 0, bstart); } bool GlobPattern::match (const std::string &s, std::vector &e) const @@ -253,7 +304,7 @@ bool GlobPattern::match (const std::string &s, std::vector &e) cons e.clear (); } std::vector > bstart; - return do_match (m_p.c_str (), s.c_str (), &e, bstart); + return do_match (m_p.c_str (), s.c_str (), m_case_sensitive, m_exact, m_header_match, &e, bstart); } } diff --git a/src/tl/tlGlobPattern.h b/src/tl/tlGlobPattern.h index 0f8d3ad16..a9252e737 100644 --- a/src/tl/tlGlobPattern.h +++ b/src/tl/tlGlobPattern.h @@ -60,6 +60,36 @@ public: return *this; } + /** + * @brief Sets a value indicating whether to treat the match case sensitive + */ + void set_case_sensitive (bool f); + + /** + * @brief Gets a value indicating whether to treat the match case sensitive + */ + bool case_sensitive () const; + + /** + * @brief Sets a value indicating whether to match exact (without brackets and wildcards) + */ + void set_exact (bool f); + + /** + * @brief Gets a value indicating whether to match exact (without brackets and wildcards) + */ + bool exact () const; + + /** + * @brief Sets a value indicating whether to allow trailing characters in the subject + */ + void set_header_match (bool f); + + /** + * @brief Gets a value indicating whether to allow trailing characters in the subject + */ + bool header_match () const; + /** * @brief Get the pattern string */ @@ -94,6 +124,9 @@ public: private: std::string m_p; + bool m_case_sensitive; + bool m_exact; + bool m_header_match; }; } // namespace tl diff --git a/src/unit_tests/tlGlobPattern.cc b/src/unit_tests/tlGlobPattern.cc index cacd805aa..54bb78cdb 100644 --- a/src/unit_tests/tlGlobPattern.cc +++ b/src/unit_tests/tlGlobPattern.cc @@ -116,7 +116,7 @@ TEST(6) EXPECT_EQ (a.match ("ah"), false); } -TEST(7) +TEST(7) { tl::GlobPattern a ("(*({bc,d}))(*)"); @@ -134,5 +134,64 @@ TEST(7) EXPECT_EQ (v[2], ""); } +TEST(8) +{ + // case insensitive + tl::GlobPattern a ("(*({bc,d}))(*)"); + std::vector v; + EXPECT_EQ (a.case_sensitive (), true); + EXPECT_EQ (a.match ("aBcG", v), false); + + a.set_case_sensitive (false); + EXPECT_EQ (a.case_sensitive (), false); + EXPECT_EQ (a.match ("aBcG", v), true); + EXPECT_EQ (v.size (), 3); + EXPECT_EQ (v[0], "aBc"); + EXPECT_EQ (v[1], "Bc"); + EXPECT_EQ (v[2], "G"); + + tl::GlobPattern b ("*a[bcd]"); + + EXPECT_EQ (b.match ("ab"), true); + EXPECT_EQ (b.match ("Ab"), false); + EXPECT_EQ (b.match ("aB"), false); + + b.set_case_sensitive (false); + EXPECT_EQ (b.match ("ab"), true); + EXPECT_EQ (b.match ("Ab"), true); + EXPECT_EQ (b.match ("aB"), true); +} + +TEST(9) +{ + // exact match + + tl::GlobPattern a ("(*({bc,d}))(*)"); + a.set_exact (true); + + EXPECT_EQ (a.exact (), true); + EXPECT_EQ (a.match ("abcg"), false); + EXPECT_EQ (a.match ("(*({bc,d}))(*)"), true); + EXPECT_EQ (a.match ("(*({bc,D}))(*)"), false); + + a.set_case_sensitive (false); + EXPECT_EQ (a.match ("abcg"), false); + EXPECT_EQ (a.match ("(*({bc,d}))(*)"), true); + EXPECT_EQ (a.match ("(*({bc,D}))(*)"), true); +} + +TEST(10) +{ + // header match + + tl::GlobPattern a ("abc"); + EXPECT_EQ (a.match ("abcg"), false); + EXPECT_EQ (a.match ("abc"), true); + + a.set_header_match (true); + EXPECT_EQ (a.header_match (), true); + EXPECT_EQ (a.match ("abcg"), true); + EXPECT_EQ (a.match ("abc"), true); +}