Merge pull request #696 from KLayout/more-elaborate-layout_view-selections

More elaborate handling of selection changed events in LayoutView (av…
This commit is contained in:
Matthias Köfferlein 2020-12-27 21:22:00 +01:00 committed by GitHub
commit 78672b1175
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 272 additions and 68 deletions

View File

@ -1642,7 +1642,7 @@ Service::reduce_rulers (int num)
void
Service::cut ()
{
if (selection_size () > 0) {
if (has_selection ()) {
// copy & delete the selected rulers
copy_selected ();
@ -1701,7 +1701,7 @@ Service::paste ()
void
Service::del ()
{
if (selection_size () > 0) {
if (has_selection ()) {
// delete the selected rulers
del_selected ();
@ -1727,12 +1727,24 @@ Service::del_selected ()
mp_view->annotation_shapes ().erase_positions (positions.begin (), positions.end ());
}
size_t
bool
Service::has_selection ()
{
return ! m_selected.empty ();
}
size_t
Service::selection_size ()
{
return m_selected.size ();
}
bool
Service::has_transient_selection ()
{
return mp_transient_ruler != 0;
}
bool
Service::select (obj_iterator obj, lay::Editable::SelectionMode mode)
{
@ -1934,7 +1946,7 @@ Service::transient_select (const db::DPoint &pos)
mp_transient_ruler = new ant::View (this, robj, true /*selected*/);
}
if (any_selected && editables ()->selection_size () == 0) {
if (any_selected && ! editables ()->has_selection ()) {
display_status (true);
}

View File

@ -256,11 +256,21 @@ public:
virtual void paste ();
/**
* @brief Tell the number of selected objects
* @brief Indicates whether there are selection objects
*/
virtual bool has_selection ();
/**
* @brief Indicates how many objects are selected
*/
virtual size_t selection_size ();
/**
/**
* @brief Indicates whether there are selection objects in transient mode
*/
virtual bool has_transient_selection ();
/**
* @brief point selection proximity predicate
*/
virtual double click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mode);

View File

@ -1117,7 +1117,7 @@ static bool has_annotation_selection (const lay::LayoutView *view)
{
std::vector<ant::Service *> ant_services = view->get_plugins <ant::Service> ();
for (std::vector<ant::Service *>::const_iterator s = ant_services.begin (); s != ant_services.end (); ++s) {
if ((*s)->selection_size () > 0) {
if ((*s)->has_selection ()) {
return true;
}
}

View File

@ -202,7 +202,7 @@ gsi::EnumIn<db::LoadLayoutOptions, db::CellConflictResolution> decl_dbCommonRead
gsi::enum_const ("RenameCell", db::RenameCell,
"@brief The new cell will be renamed to become unique\n"
),
"@brief This enum specifies how cell conflicts are handled if a layout read into another layout and a cell name conflict arises. "
"@brief This enum specifies how cell conflicts are handled if a layout read into another layout and a cell name conflict arises.\n"
"Until version 0.26.8 and before, the mode was always 'AddToCell'. On reading, a cell was 'reopened' when encountering a cell name "
"which already existed. This mode is still the default. The other modes are made available to support other ways of merging layouts.\n"
"\n"

View File

@ -2032,13 +2032,26 @@ PartialService::mouse_release_event (const db::DPoint &p, unsigned int buttons,
return false;
}
size_t
bool
PartialService::has_selection ()
{
return ! m_selection.empty ();
}
size_t
PartialService::selection_size ()
{
return m_selection.size ();
}
void
bool
PartialService::has_transient_selection ()
{
// there is no specific transient selection for the partial editor
return false;
}
void
PartialService::del ()
{
// stop dragging

View File

@ -240,10 +240,20 @@ public:
virtual double catch_distance ();
/**
* @brief Returns the number of selected objects
* @brief Indicates whether objects are selected
*/
virtual bool has_selection ();
/**
* @brief Indicates how many objects are selected
*/
virtual size_t selection_size ();
/**
* @brief Indicates whether objects are selected in transient mode
*/
virtual bool has_transient_selection ();
/**
* @brief Implement the "select" method at least to clear the selection
*/

View File

@ -293,7 +293,7 @@ Service::highlight (unsigned int n)
void
Service::cut ()
{
if (selection_size () > 0 && view ()->is_editable ()) {
if (has_selection () && view ()->is_editable ()) {
// copy & delete the selected objects
copy_selected ();
del_selected ();
@ -834,7 +834,7 @@ Service::edit_cancel ()
void
Service::del ()
{
if (selection_size () > 0 && view ()->is_editable ()) {
if (has_selection () && view ()->is_editable ()) {
// delete the selected objects
del_selected ();
}
@ -871,14 +871,19 @@ Service::del_selected ()
}
}
size_t
bool
Service::has_selection ()
{
return ! m_selection.empty ();
}
size_t
Service::selection_size ()
{
return m_selection.size ();
}
bool
bool
Service::has_transient_selection ()
{
return ! m_transient_selection.empty ();
@ -1016,7 +1021,7 @@ Service::transient_select (const db::DPoint &pos)
}
if (editables ()->selection_size () == 0) {
if (! editables ()->has_selection ()) {
display_status (true);
}
@ -1055,7 +1060,7 @@ Service::transient_select (const db::DPoint &pos)
mp_transient_marker = marker;
if (editables ()->selection_size () == 0) {
if (! editables ()->has_selection ()) {
display_status (true);
}
@ -1410,7 +1415,7 @@ Service::move_markers (const db::DTrans &t)
if (m_move_trans != t) {
// display current move vector
if (selection_size () > 0) {
if (has_selection ()) {
std::string pos = std::string ("dx: ") + tl::micron_to_string (t.disp ().x ()) + " dy: " + tl::micron_to_string (t.disp ().y ());
if (t.rot () != 0) {
pos += std::string (" ") + ((const db::DFTrans &) t).to_string ();

View File

@ -159,12 +159,17 @@ public:
virtual void end_move (const db::DPoint &p, lay::angle_constraint_type ac);
/**
* @brief Tell the number of selected objects
* @brief Indicates whether objects are selected
*/
virtual bool has_selection ();
/**
* @brief Indicates how many objects are selected
*/
virtual size_t selection_size ();
/**
* @brief Tell if anything is selected in the transient selection
* @brief Indicates whether objects are selected in transient mode
*/
virtual bool has_transient_selection ();

View File

@ -525,7 +525,7 @@ static bool has_object_selection (const lay::LayoutView *view)
{
std::vector<edt::Service *> edt_services = view->get_plugins <edt::Service> ();
for (std::vector<edt::Service *>::const_iterator s = edt_services.begin (); s != edt_services.end (); ++s) {
if ((*s)->selection_size () > 0) {
if ((*s)->has_selection ()) {
return true;
}
}

View File

@ -1336,7 +1336,7 @@ static bool has_image_selection (const lay::LayoutView *view)
{
std::vector<img::Service *> img = view->get_plugins <img::Service> ();
for (std::vector<img::Service *>::const_iterator s = img.begin (); s != img.end (); ++s) {
if ((*s)->selection_size () > 0) {
if ((*s)->has_selection ()) {
return true;
}
}

View File

@ -1006,7 +1006,7 @@ Service::edit_cancel ()
void
Service::cut ()
{
if (selection_size () > 0) {
if (has_selection ()) {
// copy & delete the selected images
copy_selected ();
@ -1052,7 +1052,7 @@ Service::paste ()
void
Service::del ()
{
if (selection_size () > 0) {
if (has_selection ()) {
// delete the selected images
del_selected ();
@ -1078,12 +1078,24 @@ Service::del_selected ()
mp_view->annotation_shapes ().erase_positions (positions.begin (), positions.end ());
}
size_t
bool
Service::has_selection ()
{
return ! m_selected.empty ();
}
size_t
Service::selection_size ()
{
return m_selected.size ();
}
bool
Service::has_transient_selection ()
{
return mp_transient_view != 0;
}
void
Service::clear_previous_selection ()
{
@ -1193,7 +1205,7 @@ Service::transient_select (const db::DPoint &pos)
// if in move mode (which also receives transient_select requests) the move will take the selection,
// hence only highlight the transient selection if it's part of the current selection.
if (view ()->selection_size () > 0 && view ()->is_move_mode () && m_selected.find (imin) == m_selected.end ()) {
if (view ()->has_selection () && view ()->is_move_mode () && m_selected.find (imin) == m_selected.end ()) {
return false;
}
@ -1208,7 +1220,7 @@ Service::transient_select (const db::DPoint &pos)
}
if (any_selected && editables ()->selection_size () == 0) {
if (any_selected && ! editables ()->has_selection ()) {
display_status (true);
}

View File

@ -275,11 +275,21 @@ public:
virtual void paste ();
/**
* @brief Tell the number of selected objects
* @brief Indicates if any objects are selected
*/
virtual bool has_selection ();
/**
* @brief Indicates how many objects are selected
*/
virtual size_t selection_size ();
/**
/**
* @brief Indicates if any objects are selected in transient mode
*/
virtual bool has_transient_selection ();
/**
* @brief point selection proximity predicate
*/
virtual double click_proximity (const db::DPoint &pos, lay::Editable::SelectionMode mode);

View File

@ -23,6 +23,7 @@
#include "gsiDecl.h"
#include "gsiSignals.h"
#include "gsiEnums.h"
#include "rdb.h"
#include "layLayoutView.h"
#include "layDitherPattern.h"
@ -978,11 +979,32 @@ Class<lay::LayoutView> decl_LayoutView (QT_EXTERNAL_BASE (QWidget) "lay", "Layou
"selection. Calling this method is useful to ensure there are no potential interactions with the script's "
"functionality.\n"
) +
gsi::method ("clear_selection", &lay::LayoutView::clear_selection,
gsi::method ("clear_selection", (void (lay::LayoutView::*) ()) &lay::LayoutView::clear_selection,
"@brief Clears the selection of all objects (shapes, annotations, images ...)\n"
"\n"
"This method has been introduced in version 0.26.2\n"
) +
gsi::method ("select_all", (void (lay::LayoutView::*) ()) &lay::LayoutView::select,
"@brief Selects all objects from the view\n"
"\n"
"This method has been introduced in version 0.27\n"
) +
gsi::method ("select_from", (void (lay::LayoutView::*) (const db::DPoint &, lay::Editable::SelectionMode)) &lay::LayoutView::select, gsi::arg ("point"), gsi::arg ("mode", lay::Editable::SelectionMode::Replace, "Replace"),
"@brief Selects the objects from a given point\n"
"\n"
"The mode indicates whether to add to the selection, replace the selection, remove from selection or invert the selected status of the objects "
"found around the given point.\n"
"\n"
"This method has been introduced in version 0.27\n"
) +
gsi::method ("select_from", (void (lay::LayoutView::*) (const db::DBox &, lay::Editable::SelectionMode)) &lay::LayoutView::select, gsi::arg ("box"), gsi::arg ("mode", lay::Editable::SelectionMode::Replace, "Replace"),
"@brief Selects the objects from a given box\n"
"\n"
"The mode indicates whether to add to the selection, replace the selection, remove from selection or invert the selected status of the objects "
"found inside the given box.\n"
"\n"
"This method has been introduced in version 0.27\n"
) +
gsi::method ("clear_transient_selection", &lay::LayoutView::clear_transient_selection,
"@brief Clears the transient selection (mouse-over hightlights) of all objects (shapes, annotations, images ...)\n"
"\n"
@ -1001,6 +1023,16 @@ Class<lay::LayoutView> decl_LayoutView (QT_EXTERNAL_BASE (QWidget) "lay", "Layou
"\n"
"This method has been introduced in version 0.26.2\n"
) +
gsi::method ("selection_size", (size_t (lay::LayoutView::*) ()) &lay::LayoutView::selection_size,
"@brief Returns the number of selected objects\n"
"\n"
"This method has been introduced in version 0.27\n"
) +
gsi::method ("has_selection?", (bool (lay::LayoutView::*) ()) &lay::LayoutView::has_selection,
"@brief Indicates whether any objects are selected\n"
"\n"
"This method has been introduced in version 0.27\n"
) +
gsi::method ("stop", &lay::LayoutView::stop,
"@brief Stops redraw thread and close any browsers\n"
"This method usually does not need to be called explicitly. The redraw thread is stopped automatically."
@ -1867,6 +1899,27 @@ Class<lay::LayoutView> decl_LayoutView (QT_EXTERNAL_BASE (QWidget) "lay", "Layou
"This object controls these aspects of the view and controls the appearance of the data. "
);
gsi::EnumIn<lay::LayoutView, lay::Editable::SelectionMode> decl_layLayoutView_SelectionMode ("lay", "SelectionMode",
gsi::enum_const ("Add", lay::Editable::SelectionMode::Add,
"@brief Adds to any existing selection\n"
) +
gsi::enum_const ("Reset", lay::Editable::SelectionMode::Reset,
"@brief Removes from any existing selection\n"
) +
gsi::enum_const ("Replace", lay::Editable::SelectionMode::Replace,
"@brief Replaces the existing selection\n"
) +
gsi::enum_const ("Invert", lay::Editable::SelectionMode::Invert,
"@brief Adds to any existing selection, if it's not there yet or removes it from the selection if it's already selected\n"
),
"@brief Specifies how selected objects interact with already selected ones.\n"
"\n"
"This enum was introduced in version 0.27.\n"
);
// Inject the NetlistCrossReference::Status declarations into NetlistCrossReference:
gsi::ClassExt<lay::LayoutView> inject_SelectionMode_in_parent (decl_layLayoutView_SelectionMode.defs ());
static db::Layout *get_layout (const lay::CellViewRef *cv)
{
if ((*cv).operator-> ()) {

View File

@ -92,7 +92,7 @@ Editables::del (db::Transaction *transaction)
{
std::auto_ptr<db::Transaction> trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Delete"))));
if (selection_size () > 0) {
if (has_selection ()) {
try {
@ -118,7 +118,7 @@ Editables::del (db::Transaction *transaction)
void
Editables::cut ()
{
if (selection_size () > 0) {
if (has_selection ()) {
cancel_edits ();
@ -136,7 +136,7 @@ Editables::cut ()
void
Editables::copy ()
{
if (selection_size () > 0) {
if (has_selection ()) {
db::Clipboard::instance ().clear ();
for (iterator e = begin (); e != end (); ++e) {
e->copy ();
@ -170,7 +170,7 @@ Editables::transform (const db::DCplxTrans &tr, db::Transaction *transaction)
{
std::auto_ptr<db::Transaction> trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (QObject::tr ("Transform"))));
if (selection_size () > 0) {
if (has_selection ()) {
try {
@ -298,19 +298,33 @@ Editables::clear_previous_selection ()
void
Editables::clear_transient_selection ()
{
bool had_transient_selection = false;
for (iterator e = begin (); e != end (); ++e) {
if (e->has_transient_selection ()) {
had_transient_selection = true;
}
e->clear_transient_selection ();
}
// send a signal to the observers
signal_transient_selection_changed ();
if (had_transient_selection) {
signal_transient_selection_changed ();
}
}
void
Editables::transient_to_selection ()
{
bool had_transient_selection = false;
bool had_selection = false;
cancel_edits ();
for (iterator e = begin (); e != end (); ++e) {
if (e->has_selection ()) {
had_selection = true;
}
if (e->has_transient_selection ()) {
had_transient_selection = true;
}
e->select (db::DBox (), lay::Editable::Reset); // clear selection
e->clear_previous_selection ();
e->transient_to_selection ();
@ -318,22 +332,41 @@ Editables::transient_to_selection ()
}
// send a signal to the observers
signal_transient_selection_changed ();
signal_selection_changed ();
if (had_transient_selection) {
signal_transient_selection_changed ();
}
if (had_selection || had_transient_selection) {
signal_selection_changed ();
}
}
void
Editables::clear_selection ()
{
cancel_edits ();
bool had_transient_selection = false;
bool had_selection = false;
for (iterator e = begin (); e != end (); ++e) {
if (e->has_selection ()) {
had_selection = true;
}
if (e->has_transient_selection ()) {
had_transient_selection = true;
}
e->select (db::DBox (), lay::Editable::Reset); // clear selection
e->clear_transient_selection ();
e->clear_previous_selection ();
}
// send a signal to the observers
signal_selection_changed ();
if (had_transient_selection) {
signal_transient_selection_changed ();
}
if (had_selection) {
signal_selection_changed ();
}
}
void
@ -479,7 +512,7 @@ Editables::begin_move (const db::DPoint &p, lay::angle_constraint_type ac)
// sort the plugins found by the proximity
std::sort (plugins.begin (), plugins.end (), first_of_pair_cmp_f<double, iterator> ());
if (selection_size () > 0 && selection_catch_bbox ().contains (p)) {
if (has_selection () && selection_catch_bbox ().contains (p)) {
// if anything is selected and we are within the selection bbox,
// issue a move operation on all editables: first try a Partial mode begin_move
@ -527,7 +560,7 @@ Editables::begin_move (const db::DPoint &p, lay::angle_constraint_type ac)
select (p, Editable::Replace);
// now we assume we have a selection - try to begin_move on this.
if (selection_size () > 0) {
if (has_selection ()) {
m_move_selection = true;
for (iterator e = begin (); e != end (); ++e) {
e->begin_move (Editable::Selected, p, ac);
@ -601,7 +634,18 @@ Editables::selection_size ()
return c;
}
void
bool
Editables::has_selection ()
{
for (iterator e = begin (); e != end (); ++e) {
if (e->has_selection ()) {
return true;
}
}
return false;
}
void
Editables::edit_cancel ()
{
clear_previous_selection ();
@ -627,7 +671,7 @@ Editables::cancel_edits ()
void
Editables::show_properties (QWidget *parent)
{
if (selection_size () == 0) {
if (! has_selection ()) {
// try to use the transient selection for the real one
transient_to_selection ();
}

View File

@ -319,16 +319,29 @@ public:
}
/**
* @brief Tell how many objects are selected
*
* This method is used to determine if anything is selected - i.e.
* anything can be copied.
* @brief Indicates if any objects are selected
*/
virtual bool has_selection ()
{
return false;
}
/**
* @brief Indicates how many objects are selected
*/
virtual size_t selection_size ()
{
return 0;
}
/**
* @brief Indicates if any objects are selected in the transient selection
*/
virtual bool has_transient_selection ()
{
return false;
}
/**
* @brief Create a "properties page" object
*
@ -528,12 +541,17 @@ public:
void end_move (const db::DPoint &p, lay::angle_constraint_type ac, db::Transaction *transaction = 0);
/**
* @brief Tell how many objects are selected.
* @brief Indicates how many objects are selected.
*
* This method will return the number of selected objects.
*/
size_t selection_size ();
/**
* @brief Indicates whether any object is selected.
*/
bool has_selection ();
/**
* @brief Cancel any pending operations
*/

View File

@ -5246,7 +5246,7 @@ LayoutView::has_selection ()
} else if (mp_hierarchy_panel && mp_hierarchy_panel->has_focus ()) {
return mp_hierarchy_panel->has_selection ();
} else {
return lay::Editables::selection_size () > 0;
return lay::Editables::has_selection ();
}
}
@ -5319,7 +5319,7 @@ LayoutView::copy ()
mp_control_panel->copy ();
} else {
if (lay::Editables::selection_size () == 0) {
if (! lay::Editables::has_selection ()) {
// try to use the transient selection for the real one
lay::Editables::transient_to_selection ();
}
@ -5341,7 +5341,7 @@ LayoutView::cut ()
mp_control_panel->cut ();
} else {
if (lay::Editables::selection_size () == 0) {
if (! lay::Editables::has_selection ()) {
// try to use the transient selection for the real one
lay::Editables::transient_to_selection ();
}

View File

@ -100,7 +100,7 @@ MoveService::key_event (unsigned int key, unsigned int /*buttons*/)
dx = 1.0;
}
if (! m_dragging && fabs (dx + dy) > 0.0 && mp_editables->selection_size () > 0) {
if (! m_dragging && fabs (dx + dy) > 0.0 && mp_editables->has_selection ()) {
// determine a shift distance which is 2, 5 or 10 times the grid and is more than 5 pixels
double dmin = double (5 /*pixels min shift*/) / widget ()->mouse_event_trans ().mag ();
@ -250,13 +250,13 @@ MoveService::begin_move (db::Transaction *transaction, bool selected_after_move)
std::auto_ptr<db::Transaction> trans_holder (transaction);
bool drag_transient = ! selected_after_move;
if (mp_editables->selection_size () == 0) {
if (! mp_editables->has_selection ()) {
// try to use the transient selection for the real one
mp_editables->transient_to_selection ();
drag_transient = true;
}
if (mp_editables->selection_size () == 0) {
if (! mp_editables->has_selection ()) {
// still nothing selected
return false;
}

View File

@ -180,6 +180,25 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(cv1.index, 0)
assert_equal(view.has_selection?, false)
assert_equal(view.selection_size, 0)
view.select_from(RBA::DBox::new(-1.0, -1.0, 1.0, 1.0))
assert_equal(selection_changed, 1)
assert_equal(view.selection_size, 4)
assert_equal(view.has_selection?, true)
view.select_from(RBA::DPoint::new(0, 0), RBA::LayoutView::Invert)
assert_equal(selection_changed, 2)
assert_equal(view.selection_size, 3)
assert_equal(view.has_selection?, true)
view.clear_selection
assert_equal(selection_changed, 3)
assert_equal(view.has_selection?, false)
assert_equal(view.selection_size, 0)
selection_changed = 0
cv2 = mw.load_layout(ENV["TESTSRC"] + "/testdata/gds/t10.gds", 2)
assert_equal(RBA::CellView::active.index, 1)
assert_equal(cv2.index, 1)
@ -196,8 +215,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 1)
assert_equal(selection_changed, 0)
view.pan_up
assert_equal(viewport_changed, 2)
@ -234,8 +252,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 1)
assert_equal(selection_changed, 0)
cv2.path = [ cv2.layout.cell("RINGO").cell_index, cv2.layout.cell("INV2").cell_index, cv2.layout.cell("TRANS").cell_index ]
assert_equal(cv2.cell_name, "TRANS")
@ -252,8 +269,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 2)
assert_equal(selection_changed, 0)
cv2.path = [ cv2.layout.cell("RINGO").cell_index, cv2.layout.cell("INV2").cell_index ]
assert_equal(cv2.cell_name, "INV2")
@ -270,8 +286,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 3)
assert_equal(selection_changed, 0)
sp = []
cv2.cell.each_inst { |i| sp << RBA::InstElement::new(i); break }
@ -291,8 +306,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 4)
assert_equal(selection_changed, 0)
cv2.ascend
@ -310,8 +324,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 5)
assert_equal(selection_changed, 0)
assert_equal(view.cellviews, 2)
@ -332,8 +345,7 @@ class LAYLayoutView_TestClass < TestBase
assert_equal(layer_list_deleted, 0)
assert_equal(current_layer_list_changed, 0)
assert_equal(cell_visibility_changed, 0)
# TODO: spontaneous event: does it hurt?
assert_equal(selection_changed, 6)
assert_equal(selection_changed, 0)
active_cellview_changed = 0
cellviews_changed = 0