Multi-selection in properties dialog: bugfixing and finalization

This commit is contained in:
Matthias Koefferlein 2022-10-13 23:45:21 +02:00
parent 5144f239eb
commit dc927717c2
11 changed files with 141 additions and 82 deletions

View File

@ -377,7 +377,7 @@ PropertiesPage::count () const
void
PropertiesPage::select_entries (const std::vector<size_t> &entries)
{
tl_assert (entries.size () == 1); // @@@
tl_assert (entries.size () == 1);
m_index = entries.front ();
}
@ -523,7 +523,7 @@ PropertiesPage::apply ()
{
ant::Object obj;
get_object (obj);
mp_rulers->change_ruler (m_selection [m_index], obj); // @@@ multi-apply
mp_rulers->change_ruler (m_selection [m_index], obj);
}
void PropertiesPage::get_object(ant::Object &obj)

View File

@ -61,7 +61,6 @@ InstPropertiesPage::InstPropertiesPage (edt::Service *service, db::Manager *mana
for (edt::Service::obj_iterator s = service->selection ().begin (); s != service->selection ().end (); ++s) {
m_selection_ptrs.push_back (s);
}
m_index = 0;
m_prop_id = 0;
mp_service->clear_highlights ();
@ -156,6 +155,10 @@ get_cell_or_pcell_ids_by_name (const db::Layout *layout, const std::string &name
void
InstPropertiesPage::browse_cell ()
{
if (m_indexes.empty ()) {
return;
}
BEGIN_PROTECTED
// find the layout the cell has to be looked up: that is either the layout of the current instance or
@ -166,7 +169,7 @@ BEGIN_PROTECTED
lib = lib_cbx->current_library ();
layout = &lib->layout ();
} else {
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
layout = &cv->layout ();
}
@ -207,8 +210,12 @@ END_PROTECTED
void
InstPropertiesPage::show_props ()
{
if (m_indexes.empty ()) {
return;
}
lay::UserPropertiesForm props_form (this);
if (props_form.show (mp_service->view (), m_selection_ptrs [m_index]->cv_index (), m_prop_id)) {
if (props_form.show (mp_service->view (), m_selection_ptrs [m_indexes.front ()]->cv_index (), m_prop_id)) {
emit edited ();
}
}
@ -235,8 +242,7 @@ InstPropertiesPage::count () const
void
InstPropertiesPage::select_entries (const std::vector<size_t> &entries)
{
tl_assert (entries.size () == 1); // @@@
m_index = entries.front ();
m_indexes = entries;
}
std::string
@ -296,10 +302,14 @@ InstPropertiesPage::leave ()
void
InstPropertiesPage::update ()
{
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
if (m_indexes.empty ()) {
return;
}
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
tl_assert (pos->is_cell_inst ());
mp_service->highlight (m_index);
mp_service->highlight (m_indexes);
m_enable_cb_callback = false;
dbu_cb->setChecked (mp_service->view ()->dbu_coordinates ());
@ -407,7 +417,11 @@ InstPropertiesPage::update ()
void
InstPropertiesPage::show_cell ()
{
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
if (m_indexes.empty ()) {
return;
}
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
lay::CellView::unspecific_cell_path_type path (mp_service->view ()->cellview (pos->cv_index ()).combined_unspecific_path ());
for (lay::ObjectInstPath::iterator p = pos->begin (); p != pos->end (); ++p) {
@ -420,8 +434,12 @@ InstPropertiesPage::show_cell ()
void
InstPropertiesPage::show_inst ()
{
if (m_indexes.empty ()) {
return;
}
InstantiationForm inst_form (this);
inst_form.show (mp_service->view (), *m_selection_ptrs [m_index]);
inst_form.show (mp_service->view (), *m_selection_ptrs [m_indexes.front ()]);
}
bool
@ -433,12 +451,14 @@ InstPropertiesPage::readonly ()
ChangeApplicator *
InstPropertiesPage::create_applicator (db::Cell & /*cell*/, const db::Instance & /*inst*/, double dbu)
{
tl_assert (! m_indexes.empty ());
bool has_error = false;
bool has_pcell_error = false;
std::unique_ptr<CombinedChangeApplicator> appl (new CombinedChangeApplicator ());
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
bool du = dbu_cb->isChecked ();
@ -732,13 +752,17 @@ InstPropertiesPage::recompute_selection_ptrs (const std::vector<lay::ObjectInstP
void
InstPropertiesPage::do_apply (bool current_only, bool relative)
{
if (m_indexes.empty ()) {
return;
}
lay::LayerState layer_state = mp_service->view ()->layer_snapshot ();
unsigned int cv_index = m_selection_ptrs [m_index]->cv_index ();
unsigned int cv_index = m_selection_ptrs [m_indexes.front ()]->cv_index ();
std::unique_ptr<ChangeApplicator> applicator;
{
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
tl_assert (pos->is_cell_inst ());
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
@ -766,7 +790,7 @@ InstPropertiesPage::do_apply (bool current_only, bool relative)
// But it avoids issues with duplicate selections of the same instance which may happen when
// an instance is selected multiple times through different hierarchy branches.
db::Instance current = m_selection_ptrs [m_index]->back ().inst_ptr;
db::Instance current = m_selection_ptrs [m_indexes.front ()]->back ().inst_ptr;
std::vector<lay::ObjectInstPath> new_sel;
new_sel.reserve (m_selection_ptrs.size ());
@ -780,9 +804,10 @@ InstPropertiesPage::do_apply (bool current_only, bool relative)
try {
for (std::vector<edt::Service::obj_iterator>::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) {
for (auto ii = m_indexes.begin (); ii != m_indexes.end (); ++ii) {
edt::Service::obj_iterator pos = *p;
size_t index = *ii;
edt::Service::obj_iterator pos = m_selection_ptrs [*ii];
// only update objects from the same layout - this is not practical limitation but saves a lot of effort for
// managing different property id's etc.
@ -815,8 +840,6 @@ InstPropertiesPage::do_apply (bool current_only, bool relative)
if (new_inst != pos->back ().inst_ptr) {
size_t index = p - m_selection_ptrs.begin ();
// change selection to new instance
new_sel[index].back ().inst_ptr = new_inst;
@ -869,6 +892,10 @@ InstPropertiesPage::apply_to_all (bool relative)
void
InstPropertiesPage::update_pcell_parameters ()
{
if (m_indexes.empty ()) {
return;
}
db::Layout *layout;
// find the layout the cell has to be looked up: that is either the layout of the current instance or
@ -879,7 +906,7 @@ InstPropertiesPage::update_pcell_parameters ()
} else {
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
layout = &cv->layout ();
@ -903,7 +930,7 @@ InstPropertiesPage::update_pcell_parameters ()
std::vector<tl::Variant> parameters;
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
db::Cell &cell = cv->layout ().cell (pos->cell_index ());
std::pair<bool, db::pcell_id_type> pci = cell.is_pcell_instance (pos->back ().inst_ptr);

View File

@ -59,7 +59,7 @@ private:
protected:
std::vector<edt::Service::obj_iterator> m_selection_ptrs;
unsigned int m_index;
std::vector<size_t> m_indexes;
edt::Service *mp_service;
bool m_enable_cb_callback;
db::properties_id_type m_prop_id;

View File

@ -51,7 +51,6 @@ ShapePropertiesPage::ShapePropertiesPage (const std::string &description, edt::S
for (edt::Service::obj_iterator s = service->selection ().begin (); s != service->selection ().end (); ++s) {
m_selection_ptrs.push_back (s);
}
m_index = 0;
m_prop_id = 0;
mp_service->clear_highlights ();
}
@ -82,8 +81,7 @@ ShapePropertiesPage::count () const
void
ShapePropertiesPage::select_entries (const std::vector<size_t> &entries)
{
tl_assert (entries.size () == 1); // @@@
m_index = entries.front ();
m_indexes = entries;
}
lay::LayoutViewBase *
@ -166,8 +164,8 @@ ShapePropertiesPage::abs_trans () const
db::ICplxTrans
ShapePropertiesPage::trans () const
{
if (abs_trans ()) {
return m_selection_ptrs[m_index]->trans ();
if (abs_trans () && ! m_indexes.empty ()) {
return m_selection_ptrs[m_indexes.front ()]->trans ();
} else {
return db::ICplxTrans ();
}
@ -190,7 +188,7 @@ END_PROTECTED
void
ShapePropertiesPage::update ()
{
mp_service->highlight (m_index);
mp_service->highlight (m_indexes);
update_shape ();
}
@ -215,12 +213,16 @@ ShapePropertiesPage::recompute_selection_ptrs (const std::vector<lay::ObjectInst
void
ShapePropertiesPage::do_apply (bool current_only, bool relative)
{
if (m_indexes.empty ()) {
return;
}
std::unique_ptr<ChangeApplicator> applicator;
unsigned int cv_index = m_selection_ptrs [m_index]->cv_index ();
unsigned int cv_index = m_selection_ptrs [m_indexes.front ()]->cv_index ();
{
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
tl_assert (! pos->is_cell_inst ());
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
@ -249,7 +251,7 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative)
// But it avoids issues with duplicate selections of the same shape which may happen when
// a shape is selected multiple times through different hierarchy branches.
db::Shape current = m_selection_ptrs [m_index]->shape ();
db::Shape current = m_selection_ptrs [m_indexes.front ()]->shape ();
std::vector<lay::ObjectInstPath> new_sel;
new_sel.reserve (m_selection_ptrs.size ());
@ -263,11 +265,10 @@ ShapePropertiesPage::do_apply (bool current_only, bool relative)
try {
for (std::vector<edt::Service::obj_iterator>::const_iterator p = m_selection_ptrs.begin (); p != m_selection_ptrs.end (); ++p) {
for (auto i = m_indexes.begin (); i != m_indexes.end (); ++i) {
size_t index = p - m_selection_ptrs.begin ();
edt::Service::obj_iterator pos = *p;
size_t index = *i;
edt::Service::obj_iterator pos = m_selection_ptrs [*i];
// only update objects from the same layout - this is not practical limitation but saves a lot of effort for
// managing different property id's etc.
@ -365,7 +366,11 @@ ShapePropertiesPage::apply_to_all (bool relative)
void
ShapePropertiesPage::update_shape ()
{
edt::Service::obj_iterator pos = m_selection_ptrs [m_index];
if (m_indexes.empty ()) {
return;
}
edt::Service::obj_iterator pos = m_selection_ptrs [m_indexes.front ()];
const lay::CellView &cv = mp_service->view ()->cellview (pos->cv_index ());
double dbu = cv->layout ().dbu ();
@ -397,15 +402,23 @@ ShapePropertiesPage::update_shape ()
void
ShapePropertiesPage::show_inst ()
{
if (m_indexes.empty ()) {
return;
}
InstantiationForm inst_form (this);
inst_form.show (mp_service->view (), *m_selection_ptrs [m_index]);
inst_form.show (mp_service->view (), *m_selection_ptrs [m_indexes.front ()]);
}
void
ShapePropertiesPage::show_props ()
{
if (m_indexes.empty ()) {
return;
}
lay::UserPropertiesForm props_form (this);
if (props_form.show (mp_service->view (), m_selection_ptrs [m_index]->cv_index (), m_prop_id)) {
if (props_form.show (mp_service->view (), m_selection_ptrs [m_indexes.front ()]->cv_index (), m_prop_id)) {
emit edited ();
}
}

View File

@ -70,7 +70,7 @@ private:
protected:
std::string m_description;
std::vector<edt::Service::obj_iterator> m_selection_ptrs;
unsigned int m_index;
std::vector<size_t> m_indexes;
edt::Service *mp_service;
bool m_enable_cb_callback;
db::properties_id_type m_prop_id;

View File

@ -77,6 +77,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view, db::ShapeIter
m_hier_copy_mode (-1),
m_indicate_secondary_selection (false),
m_seq (0),
m_highlights_selected (false),
dm_selection_to_view (this, &edt::Service::do_selection_to_view)
{
mp_view->geom_changed_event.add (this, &edt::Service::selection_to_view);
@ -97,6 +98,7 @@ Service::Service (db::Manager *manager, lay::LayoutViewBase *view)
m_hier_copy_mode (-1),
m_indicate_secondary_selection (false),
m_seq (0),
m_highlights_selected (false),
dm_selection_to_view (this, &edt::Service::do_selection_to_view)
{
mp_view->geom_changed_event.add (this, &edt::Service::selection_to_view);
@ -271,28 +273,36 @@ Service::configure (const std::string &name, const std::string &value)
void
Service::clear_highlights ()
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->visible (false);
}
m_highlights_selected = true;
m_selected_highlights.clear ();
apply_highlights ();
}
void
Service::restore_highlights ()
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->visible (true);
}
m_highlights_selected = false;
m_selected_highlights.clear ();
apply_highlights ();
}
void
Service::highlight (unsigned int n)
void
Service::highlight (const std::vector<size_t> &n)
{
m_highlights_selected = true;
m_selected_highlights = std::set<size_t> (n.begin (), n.end ());
apply_highlights ();
}
void
Service::apply_highlights ()
{
for (std::vector<lay::ViewObject *>::iterator r = m_markers.begin (); r != m_markers.end (); ++r) {
(*r)->visible (n-- == 0);
(*r)->visible (! m_highlights_selected || m_selected_highlights.find (r - m_markers.begin ()) != m_selected_highlights.end ());
}
}
void
void
Service::cut ()
{
if (has_selection () && view ()->is_editable ()) {
@ -1597,6 +1607,8 @@ Service::do_selection_to_view ()
}
}
apply_highlights ();
}
void

View File

@ -105,10 +105,10 @@ public:
*/
void restore_highlights ();
/**
* @brief Highlight a certain object
/**
* @brief Highlights a group of objects
*/
void highlight (unsigned int n);
void highlight (const std::vector<size_t> &n);
/**
* @brief "delete" operation
@ -616,6 +616,10 @@ private:
bool m_indicate_secondary_selection;
unsigned long m_seq;
// selective highlights
bool m_highlights_selected;
std::set<size_t> m_selected_highlights;
// Deferred method to update the selection
tl::DeferredMethod<edt::Service> dm_selection_to_view;
@ -646,6 +650,11 @@ private:
*/
void display_status (bool transient);
/**
* @brief Apply highlight selection
*/
void apply_highlights ();
private:
void copy_selected (unsigned int inst_mode);
};

View File

@ -163,7 +163,7 @@ PropertiesPage::count () const
void
PropertiesPage::select_entries (const std::vector<size_t> &entries)
{
tl_assert (entries.size () == 1); // @@@
tl_assert (entries.size () == 1);
m_index = entries.front ();
invalidate ();
}

View File

@ -162,7 +162,6 @@ public:
/**
* @brief Returns true, if the properties page supports "apply all"
* @@@ TODO: remove
*/
virtual bool can_apply_to_all () const
{

View File

@ -58,14 +58,14 @@ public:
if (role == Qt::DisplayRole) {
if (index.internalId () < mp_dialog->properties_pages ().size ()) {
return tl::to_qstring (mp_dialog->properties_pages () [index.internalId ()]->description (index.row ()));
} else {
} else if (index.row () < int (mp_dialog->properties_pages ().size ())) {
return tl::to_qstring (mp_dialog->properties_pages () [index.row ()]->description ());
}
} else if (role == Qt::DecorationRole) {
QIcon icon;
if (index.internalId () < mp_dialog->properties_pages ().size ()) {
icon = mp_dialog->properties_pages () [index.internalId ()]->icon (index.row (), m_icon_width, m_icon_height);
} else {
} else if (index.row () < int (mp_dialog->properties_pages ().size ())) {
icon = mp_dialog->properties_pages () [index.row ()]->icon (m_icon_width, m_icon_height);
}
if (! icon.isNull ()) {
@ -217,13 +217,16 @@ PropertiesDialog::PropertiesDialog (QWidget * /*parent*/, db::Manager *manager,
mp_ui->tree->header()->setSectionResizeMode (QHeaderView::ResizeToContents);
mp_ui->tree->expandAll ();
if (mp_properties_pages.empty ()) {
mp_ui->tree->hide ();
}
m_signals_enabled = false;
mp_ui->tree->setCurrentIndex (mp_tree_model->index_for (m_index, 0));
m_signals_enabled = true;
update_controls ();
// @@@ save this status!
mp_ui->apply_to_all_cbx->setChecked (false);
mp_ui->relative_cbx->setChecked (true);
@ -262,7 +265,7 @@ PropertiesDialog::apply_to_all_pressed ()
if (mp_ui->apply_to_all_cbx->isChecked ()) {
mp_ui->tree->setCurrentIndex (mp_tree_model->index_for (m_index));
} else if (! m_object_indexes.empty ()) {
mp_ui->tree->setCurrentIndex (mp_tree_model->index_for (m_index, m_object_indexes.front ()));
mp_ui->tree->setCurrentIndex (mp_tree_model->index_for (m_index, int (m_object_indexes.front ())));
}
m_signals_enabled = true;
}
@ -294,21 +297,21 @@ PropertiesDialog::current_index_changed (const QModelIndex &index, const QModelI
if (mp_properties_pages [m_index]->can_apply_to_all ()) {
m_object_indexes.push_back (mp_tree_model->object_index (index));
m_object_indexes.push_back (size_t (mp_tree_model->object_index (index)));
auto selection = mp_ui->tree->selectionModel ()->selectedIndexes ();
for (auto i = selection.begin (); i != selection.end (); ++i) {
if (mp_tree_model->parent (*i).isValid () && mp_tree_model->page_index (*i) == m_index) {
int oi = mp_tree_model->object_index (*i);
if (oi != m_object_indexes.front ()) {
m_object_indexes.push_back (oi);
if (oi != int (m_object_indexes.front ())) {
m_object_indexes.push_back (size_t (oi));
}
}
}
} else {
m_object_indexes.push_back (mp_tree_model->object_index (index));
m_object_indexes.push_back (size_t (mp_tree_model->object_index (index)));
}
@ -317,8 +320,16 @@ PropertiesDialog::current_index_changed (const QModelIndex &index, const QModelI
m_index = index.row ();
mp_ui->apply_to_all_cbx->setChecked (mp_properties_pages [m_index]->can_apply_to_all ());
for (int oi = 0; oi < mp_properties_pages [m_index]->count (); ++oi) {
m_object_indexes.push_back (oi);
if (mp_properties_pages [m_index]->can_apply_to_all ()) {
for (size_t oi = 0; oi < mp_properties_pages [m_index]->count (); ++oi) {
m_object_indexes.push_back (oi);
}
} else {
m_object_indexes.push_back (size_t (mp_tree_model->object_index (index)));
}
}
@ -330,7 +341,7 @@ PropertiesDialog::current_index_changed (const QModelIndex &index, const QModelI
for (int i = 0; i < m_index; ++i) {
m_current_object += mp_properties_pages [i]->count ();
}
m_current_object += m_object_indexes.front ();
m_current_object += int (m_object_indexes.front ());
} else {
m_current_object = -1;
}
@ -373,7 +384,7 @@ PropertiesDialog::update_controls ()
mp_ui->ok_button->setEnabled (! mp_properties_pages [m_index]->readonly ());
mp_ui->tree->setEnabled (true);
mp_properties_pages [m_index]->select_entry (m_object_indexes);
mp_properties_pages [m_index]->select_entries (m_object_indexes);
mp_properties_pages [m_index]->update ();
}
@ -397,7 +408,7 @@ BEGIN_PROTECTED
}
// advance the current entry
int object_index = m_object_indexes.front ();
int object_index = int (m_object_indexes.front ());
++object_index;
// look for next usable editable if at end
@ -445,7 +456,7 @@ BEGIN_PROTECTED
}
// advance the current entry
int object_index = m_object_indexes.front ();
int object_index = int (m_object_indexes.front ());
if (object_index == 0) {
// look for last usable editable if at end
@ -494,15 +505,9 @@ PropertiesDialog::any_next () const
return false;
}
// look for the next applicable page
// @@@ Pages should not be empty
int index = m_index;
if (m_object_indexes.front () + 1 >= int (mp_properties_pages [index]->count ())) {
if (m_object_indexes.front () + 1 >= mp_properties_pages [index]->count ()) {
++index;
while (index < int (mp_properties_pages.size ()) &&
(mp_properties_pages [index] == 0 || mp_properties_pages [index]->count () == 0)) {
++index;
}
}
// return true, if not at end
@ -516,15 +521,9 @@ PropertiesDialog::any_prev () const
return false;
}
// look for the next applicable page
// @@@ Pages should not be empty
int index = m_index;
if (m_object_indexes.front () == 0) {
--index;
while (index >= 0 &&
(mp_properties_pages [index] == 0 || mp_properties_pages [index]->count () == 0)) {
--index;
}
}
// return true, if not at the beginning

View File

@ -86,7 +86,7 @@ private:
db::Manager *mp_manager;
lay::Editables *mp_editables;
int m_index, m_prev_index;
std::vector<int> m_object_indexes;
std::vector<size_t> m_object_indexes;
QStackedLayout *mp_stack;
QLabel *mp_none;
lay::MainWindow *mp_mw;