Remove the toggle semantics from all the selection stuff. Now

left-clicking always selects, and there's a special context menu
item to deselect. Also streamline the right-click behavior by
making that select the hovered item, and making all the context
menu items work on the selection.

[git-p4: depot-paths = "//depot/solvespace/": change = 2091]
This commit is contained in:
Jonathan Westhues 2010-01-03 02:26:15 -08:00
parent ee052b556a
commit a4a1d3b619
6 changed files with 124 additions and 132 deletions

View File

@ -164,9 +164,9 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) {
} }
cr->newReq = hr; cr->newReq = hr;
ToggleSelectionStateOf(hr.entity(0)); MakeSelected(hr.entity(0));
for(i = 0; i < pts; i++) { for(i = 0; i < pts; i++) {
ToggleSelectionStateOf(hr.entity(i+1)); MakeSelected(hr.entity(i+1));
} }
} }

View File

@ -94,12 +94,18 @@ void GraphicsWindow::ClearNonexistentSelectionItems(void) {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Is this entity selected? // Is this entity/constraint selected?
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
bool GraphicsWindow::EntityIsSelected(hEntity he) { bool GraphicsWindow::IsSelected(hEntity he) {
Selection s;
ZERO(&s);
s.entity = he;
return IsSelected(&s);
}
bool GraphicsWindow::IsSelected(Selection *st) {
Selection *s; Selection *s;
for(s = selection.First(); s; s = selection.NextAfter(s)) { for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->entity.v == he.v) { if(s->Equals(st)) {
return true; return true;
} }
} }
@ -107,16 +113,18 @@ bool GraphicsWindow::EntityIsSelected(hEntity he) {
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Toggle the selection state of the indicated item: if it was selected then // Unselect an item, if it is selected. We can either unselect just that item,
// un-select it, and if it wasn't then select it. // or also unselect any coincident points. The latter is useful if the user
// somehow selects two coincident points (like with select all), because it
// would otherwise be impossible to de-select the lower of the two.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void GraphicsWindow::ToggleSelectionStateOf(hEntity he, bool batch) { void GraphicsWindow::MakeUnselected(hEntity he, bool coincidentPointTrick) {
Selection stog; Selection stog;
ZERO(&stog); ZERO(&stog);
stog.entity = he; stog.entity = he;
ToggleSelectionStateOf(&stog, batch); MakeUnselected(&stog, coincidentPointTrick);
} }
void GraphicsWindow::ToggleSelectionStateOf(Selection *stog, bool batch) { void GraphicsWindow::MakeUnselected(Selection *stog, bool coincidentPointTrick){
if(stog->IsEmpty()) return; if(stog->IsEmpty()) return;
Selection *s; Selection *s;
@ -127,14 +135,12 @@ void GraphicsWindow::ToggleSelectionStateOf(Selection *stog, bool batch) {
for(s = selection.First(); s; s = selection.NextAfter(s)) { for(s = selection.First(); s; s = selection.NextAfter(s)) {
if(s->Equals(stog)) { if(s->Equals(stog)) {
s->tag = 1; s->tag = 1;
wasSelected = true;
break;
} }
} }
// If two points are coincident, then it's impossible to hover one of // If two points are coincident, then it's impossible to hover one of
// them. But make sure to deselect both, to avoid mysterious seeming // them. But make sure to deselect both, to avoid mysterious seeming
// inability to deselect if the bottom one did somehow get selected. // inability to deselect if the bottom one did somehow get selected.
if(wasSelected && stog->entity.v && !batch) { if(stog->entity.v && coincidentPointTrick) {
Entity *e = SK.GetEntity(stog->entity); Entity *e = SK.GetEntity(stog->entity);
if(e->IsPoint()) { if(e->IsPoint()) {
Vector ep = e->PointGetNum(); Vector ep = e->PointGetNum();
@ -149,20 +155,27 @@ void GraphicsWindow::ToggleSelectionStateOf(Selection *stog, bool batch) {
} }
} }
} }
selection.RemoveTagged();
}
// It's too confusing to make operations that select multiple entities //-----------------------------------------------------------------------------
// (like marquee selection) toggle. So make those select-only. // Select an item, if it isn't selected already.
if(!batch) { //-----------------------------------------------------------------------------
selection.RemoveTagged(); void GraphicsWindow::MakeSelected(hEntity he) {
} Selection stog;
if(wasSelected) return; ZERO(&stog);
stog.entity = he;
// So it's not selected, so we should select it. MakeSelected(&stog);
}
void GraphicsWindow::MakeSelected(Selection *stog) {
if(stog->IsEmpty()) return;
if(IsSelected(stog)) return;
if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) { if(stog->entity.v != 0 && SK.GetEntity(stog->entity)->IsFace()) {
// In the interest of speed for the triangle drawing code, // In the interest of speed for the triangle drawing code,
// only two faces may be selected at a time. // only two faces may be selected at a time.
int c = 0; int c = 0;
Selection *s;
selection.ClearTags(); selection.ClearTags();
for(s = selection.First(); s; s = selection.NextAfter(s)) { for(s = selection.First(); s; s = selection.NextAfter(s)) {
hEntity he = s->entity; hEntity he = s->entity;
@ -202,7 +215,7 @@ void GraphicsWindow::SelectByMarquee(void) {
if(pp.x >= xmin && pp.x <= xmax && if(pp.x >= xmin && pp.x <= xmax &&
pp.y >= ymin && pp.y <= ymax) pp.y >= ymin && pp.y <= ymax)
{ {
ToggleSelectionStateOf(e->h, true); MakeSelected(e->h);
} }
} else { } else {
// Use the 3d bounding box test routines, to avoid duplication; // Use the 3d bounding box test routines, to avoid duplication;
@ -224,7 +237,7 @@ void GraphicsWindow::SelectByMarquee(void) {
!ptA.OutsideAndNotOn(ptMax, ptMin) || !ptA.OutsideAndNotOn(ptMax, ptMin) ||
!ptB.OutsideAndNotOn(ptMax, ptMin)) !ptB.OutsideAndNotOn(ptMax, ptMin))
{ {
ToggleSelectionStateOf(e->h, true); MakeSelected(e->h);
break; break;
} }
} }

View File

@ -633,7 +633,7 @@ void GraphicsWindow::MenuEdit(int id) {
if(e->IsFace() || e->IsDistance()) continue; if(e->IsFace() || e->IsDistance()) continue;
if(!e->IsVisible()) continue; if(!e->IsVisible()) continue;
SS.GW.ToggleSelectionStateOf(e->h, true); SS.GW.MakeSelected(e->h);
} }
InvalidateGraphics(); InvalidateGraphics();
SS.later.showTW = true; SS.later.showTW = true;
@ -675,15 +675,15 @@ void GraphicsWindow::MenuEdit(int id) {
} }
} }
if(onChain && !alreadySelected) { if(onChain && !alreadySelected) {
SS.GW.ToggleSelectionStateOf(e->h, true); SS.GW.MakeSelected(e->h);
newlySelected++; newlySelected++;
didSomething = true; didSomething = true;
} }
} }
} while(didSomething); } while(didSomething);
if(newlySelected == 0) { if(newlySelected == 0) {
Error("No entities share endpoints with the selected " Error("No additional entities share endpoints with the "
"entities."); "selected entities.");
} }
InvalidateGraphics(); InvalidateGraphics();
SS.later.showTW = true; SS.later.showTW = true;

167
mouse.cpp
View File

@ -177,21 +177,21 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
pending.normal = hover.entity; pending.normal = hover.entity;
pending.operation = DRAGGING_NORMAL; pending.operation = DRAGGING_NORMAL;
} else { } else {
if(EntityIsSelected(e->h)) { if(!hoverWasSelectedOnMousedown) {
// The entity is selected, which means that it wasn't // The user clicked an unselected entity, which
// before the user clicked to start dragging, which
// means they're dragging just the hovered thing, // means they're dragging just the hovered thing,
// not the full selection. So clear all the selection // not the full selection. So clear all the selection
// except that entity. // except that entity.
ClearSelection(); ClearSelection();
ToggleSelectionStateOf(e->h); MakeSelected(e->h);
} }
StartDraggingBySelection(); StartDraggingBySelection();
// If something's hovered, then the user selected it when if(!hoverWasSelectedOnMousedown) {
// they clicked to start dragging, but they probably // And then clear the selection again, since they
// didn't mean to select it. Or if it was selected, then // probably didn't want that selected if they just
// they didn't mean to deselect it. So fix that. // were dragging it.
ToggleSelectionStateOf(e->h); ClearSelection();
}
hover.Clear(); hover.Clear();
pending.operation = DRAGGING_POINTS; pending.operation = DRAGGING_POINTS;
} }
@ -214,7 +214,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown,
if(hover.entity.v) { if(hover.entity.v) {
// Avoid accidentally selecting workplanes when // Avoid accidentally selecting workplanes when
// starting drags. // starting drags.
ToggleSelectionStateOf(hover.entity); MakeUnselected(hover.entity, false);
hover.Clear(); hover.Clear();
} }
pending.operation = DRAGGING_MARQUEE; pending.operation = DRAGGING_MARQUEE;
@ -500,121 +500,95 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
context.active = true; context.active = true;
if(!hover.IsEmpty()) {
MakeSelected(&hover);
SS.later.showTW = true;
}
GroupSelection(); GroupSelection();
if(hover.IsEmpty() &&
gs.n == 0 &&
gs.constraints == 0 &&
(SS.clipboard.r.n == 0 || !LockedInWorkplane()))
{
// No reason to display a context menu.
goto done;
}
// We can either work on the selection (like the functions are designed to) bool itemsSelected = (gs.n > 0 || gs.constraints > 0);
// or on the hovered item. In the latter case we can fudge things by just
// selecting the hovered item, and then applying our operation to the
// selection.
bool selEmpty = false;
if(gs.n == 0 && gs.constraints == 0) {
selEmpty = true;
}
if(selEmpty) { if(itemsSelected) {
if(hover.IsStylable()) { if(gs.stylables > 0) {
ContextMenuListStyles(); ContextMenuListStyles();
AddContextMenuItem("Hovered: Assign to Style", CONTEXT_SUBMENU); AddContextMenuItem("Assign to Style", CONTEXT_SUBMENU);
} }
if(!hover.IsEmpty()) { if(gs.n + gs.constraints == 1) {
AddContextMenuItem("Hovered: Group Info", CMNU_GROUP_INFO); AddContextMenuItem("Group Info", CMNU_GROUP_INFO);
} }
if(hover.IsStylable()) { if(gs.n + gs.constraints == 1 && gs.stylables == 1) {
AddContextMenuItem("Hovered: Style Info", CMNU_STYLE_INFO); AddContextMenuItem("Style Info", CMNU_STYLE_INFO);
} }
if(hover.constraint.v) { if(gs.withEndpoints > 0) {
Constraint *c = SK.GetConstraint(hover.constraint); AddContextMenuItem("Select Edge Chain", CMNU_SELECT_CHAIN);
}
if(gs.constraints == 1 && gs.n == 0) {
Constraint *c = SK.GetConstraint(gs.constraint[0]);
if(c->HasLabel() && c->type != Constraint::COMMENT) { if(c->HasLabel() && c->type != Constraint::COMMENT) {
AddContextMenuItem("Hovered: Toggle Reference Dimension", AddContextMenuItem("Toggle Reference Dimension",
CMNU_REFERENCE_DIM); CMNU_REFERENCE_DIM);
} }
if(c->type == Constraint::ANGLE || if(c->type == Constraint::ANGLE ||
c->type == Constraint::EQUAL_ANGLE) c->type == Constraint::EQUAL_ANGLE)
{ {
AddContextMenuItem("Hovered: Other Supplementary Angle", AddContextMenuItem("Other Supplementary Angle",
CMNU_OTHER_ANGLE); CMNU_OTHER_ANGLE);
} }
} }
if(hover.entity.v) { if(gs.comments > 0 || gs.points > 0) {
Entity *p = SK.GetEntity(hover.entity); AddContextMenuItem("Snap to Grid", CMNU_SNAP_TO_GRID);
if(p->IsPoint()) { }
Constraint *c;
IdList<Constraint,hConstraint> *lc = &(SK.constraint); if(gs.points == 1) {
for(c = lc->First(); c; c = lc->NextAfter(c)) { Entity *p = SK.GetEntity(gs.point[0]);
if(c->type != Constraint::POINTS_COINCIDENT) continue; Constraint *c;
if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) { IdList<Constraint,hConstraint> *lc = &(SK.constraint);
break; for(c = lc->First(); c; c = lc->NextAfter(c)) {
} if(c->type != Constraint::POINTS_COINCIDENT) continue;
} if(c->ptA.v == p->h.v || c->ptB.v == p->h.v) {
if(c) { break;
AddContextMenuItem(
"Hovered: Delete Point-Coincident Constraint",
CMNU_DEL_COINCIDENT);
} }
} }
} if(c) {
if(hover.HasEndpoints()) { AddContextMenuItem("Delete Point-Coincident Constraint",
AddContextMenuItem("Hovered: Select Edge Chain", CMNU_SELECT_CHAIN); CMNU_DEL_COINCIDENT);
} }
if((hover.constraint.v &&
SK.GetConstraint(hover.constraint)->type == Constraint::COMMENT) ||
(hover.entity.v &&
SK.GetEntity(hover.entity)->IsPoint()))
{
AddContextMenuItem("Hovered: Snap to Grid", CMNU_SNAP_TO_GRID);
}
if(!hover.IsEmpty()) {
AddContextMenuItem(NULL, CONTEXT_SEPARATOR);
AddContextMenuItem("Delete Hovered Item", CMNU_DELETE_SEL);
}
} else {
if(gs.stylables > 0) {
ContextMenuListStyles();
AddContextMenuItem("Selected: Assign to Style", CONTEXT_SUBMENU);
}
if(gs.n + gs.constraints == 1) {
AddContextMenuItem("Selected: Group Info", CMNU_GROUP_INFO);
}
if(gs.n + gs.constraints == 1 && gs.stylables == 1) {
AddContextMenuItem("Selected: Style Info", CMNU_STYLE_INFO);
}
if(gs.withEndpoints > 0) {
AddContextMenuItem("Selected: Select Edge Chain",
CMNU_SELECT_CHAIN);
} }
AddContextMenuItem(NULL, CONTEXT_SEPARATOR); AddContextMenuItem(NULL, CONTEXT_SEPARATOR);
if(LockedInWorkplane()) { if(LockedInWorkplane()) {
AddContextMenuItem("Cut Selection", CMNU_CUT_SEL); AddContextMenuItem("Cut", CMNU_CUT_SEL);
AddContextMenuItem("Copy Selection", CMNU_COPY_SEL); AddContextMenuItem("Copy", CMNU_COPY_SEL);
} }
} }
if(SS.clipboard.r.n > 0 && LockedInWorkplane()) { if(SS.clipboard.r.n > 0 && LockedInWorkplane()) {
AddContextMenuItem("Paste Selection", CMNU_PASTE_SEL); AddContextMenuItem("Paste", CMNU_PASTE_SEL);
} }
if(!selEmpty) { if(itemsSelected) {
AddContextMenuItem("Delete Selection", CMNU_DELETE_SEL); AddContextMenuItem("Delete", CMNU_DELETE_SEL);
AddContextMenuItem(NULL, CONTEXT_SEPARATOR);
AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL);
} }
// If only one item is selected, then it must be the one that we just
// selected from the hovered item; in which case unselect all and hovered
// are equivalent.
if(!hover.IsEmpty() && selection.n > 1) {
AddContextMenuItem("Unselect Hovered", CMNU_UNSELECT_HOVERED);
}
int ret = ShowContextMenu(); int ret = ShowContextMenu();
if(ret != 0 && ret != CMNU_DEL_COINCIDENT && selEmpty) {
ToggleSelectionStateOf(&hover);
}
switch(ret) { switch(ret) {
case CMNU_UNSELECT_ALL: case CMNU_UNSELECT_ALL:
MenuEdit(MNU_UNSELECT_ALL); MenuEdit(MNU_UNSELECT_ALL);
break; break;
case CMNU_UNSELECT_HOVERED:
if(!hover.IsEmpty()) {
MakeUnselected(&hover, true);
}
break;
case CMNU_SELECT_CHAIN: case CMNU_SELECT_CHAIN:
MenuEdit(MNU_SELECT_CHAIN); MenuEdit(MNU_SELECT_CHAIN);
break; break;
@ -645,8 +619,8 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
case CMNU_DEL_COINCIDENT: { case CMNU_DEL_COINCIDENT: {
SS.UndoRemember(); SS.UndoRemember();
if(!hover.entity.v) break; if(!gs.point[0].v) break;
Entity *p = SK.GetEntity(hover.entity); Entity *p = SK.GetEntity(gs.point[0]);
if(!p->IsPoint()) break; if(!p->IsPoint()) break;
SK.constraint.ClearTags(); SK.constraint.ClearTags();
@ -658,6 +632,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
} }
} }
SK.constraint.RemoveTagged(); SK.constraint.RemoveTagged();
ClearSelection();
break; break;
} }
@ -667,7 +642,6 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
case CMNU_GROUP_INFO: { case CMNU_GROUP_INFO: {
hGroup hg; hGroup hg;
GroupSelection();
if(gs.entities == 1) { if(gs.entities == 1) {
hg = SK.GetEntity(gs.entity[0])->group; hg = SK.GetEntity(gs.entity[0])->group;
} else if(gs.points == 1) { } else if(gs.points == 1) {
@ -687,7 +661,6 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
case CMNU_STYLE_INFO: { case CMNU_STYLE_INFO: {
hStyle hs; hStyle hs;
GroupSelection();
if(gs.entities == 1) { if(gs.entities == 1) {
hs = Style::ForEntity(gs.entity[0]); hs = Style::ForEntity(gs.entity[0]);
} else if(gs.points == 1) { } else if(gs.points == 1) {
@ -726,8 +699,8 @@ void GraphicsWindow::MouseRightUp(double x, double y) {
break; break;
} }
done:
context.active = false; context.active = false;
SS.later.showTW = true;
} }
hRequest GraphicsWindow::AddRequest(int type) { hRequest GraphicsWindow::AddRequest(int type) {
@ -1066,8 +1039,9 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
case 0: case 0:
default: default:
ClearPending(); ClearPending();
if(hover.entity.v || hover.constraint.v) { if(!hover.IsEmpty()) {
ToggleSelectionStateOf(&hover); hoverWasSelectedOnMousedown = IsSelected(&hover);
MakeSelected(&hover);
} }
break; break;
} }
@ -1078,6 +1052,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) {
void GraphicsWindow::MouseLeftUp(double mx, double my) { void GraphicsWindow::MouseLeftUp(double mx, double my) {
orig.mouseDown = false; orig.mouseDown = false;
hoverWasSelectedOnMousedown = false;
switch(pending.operation) { switch(pending.operation) {
case DRAGGING_POINTS: case DRAGGING_POINTS:

21
ui.h
View File

@ -456,6 +456,7 @@ public:
bool HasEndpoints(void); bool HasEndpoints(void);
}; };
Selection hover; Selection hover;
bool hoverWasSelectedOnMousedown;
List<Selection> selection; List<Selection> selection;
void HitTestMakeSelection(Point2d mp); void HitTestMakeSelection(Point2d mp);
void ClearSelection(void); void ClearSelection(void);
@ -486,18 +487,22 @@ public:
int n; int n;
} gs; } gs;
void GroupSelection(void); void GroupSelection(void);
bool EntityIsSelected(hEntity he); bool IsSelected(Selection *s);
void ToggleSelectionStateOf(hEntity he, bool batch=false); bool IsSelected(hEntity he);
void ToggleSelectionStateOf(Selection *s, bool batch=false); void MakeSelected(hEntity he);
void MakeSelected(Selection *s);
void MakeUnselected(hEntity he, bool coincidentPointTrick);
void MakeUnselected(Selection *s, bool coincidentPointTrick);
void SelectByMarquee(void); void SelectByMarquee(void);
void ClearSuper(void); void ClearSuper(void);
static const int CMNU_UNSELECT_ALL = 0x100; static const int CMNU_UNSELECT_ALL = 0x100;
static const int CMNU_CUT_SEL = 0x101; static const int CMNU_UNSELECT_HOVERED = 0x101;
static const int CMNU_COPY_SEL = 0x102; static const int CMNU_CUT_SEL = 0x102;
static const int CMNU_PASTE_SEL = 0x103; static const int CMNU_COPY_SEL = 0x103;
static const int CMNU_DELETE_SEL = 0x104; static const int CMNU_PASTE_SEL = 0x104;
static const int CMNU_SELECT_CHAIN = 0x105; static const int CMNU_DELETE_SEL = 0x105;
static const int CMNU_SELECT_CHAIN = 0x106;
static const int CMNU_NEW_CUSTOM_STYLE = 0x110; static const int CMNU_NEW_CUSTOM_STYLE = 0x110;
static const int CMNU_NO_STYLE = 0x111; static const int CMNU_NO_STYLE = 0x111;
static const int CMNU_GROUP_INFO = 0x120; static const int CMNU_GROUP_INFO = 0x120;

View File

@ -1,6 +1,5 @@
show and modify view parameters (translate, rotate, scale) show and modify view parameters (translate, rotate, scale)
context menu to hide / suppress groups by entity?
----- -----
associative entities from solid model, as a special group associative entities from solid model, as a special group