diff --git a/clipboard.cpp b/clipboard.cpp index 9f7f9e1..87df411 100644 --- a/clipboard.cpp +++ b/clipboard.cpp @@ -164,9 +164,9 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { } cr->newReq = hr; - ToggleSelectionStateOf(hr.entity(0)); + MakeSelected(hr.entity(0)); for(i = 0; i < pts; i++) { - ToggleSelectionStateOf(hr.entity(i+1)); + MakeSelected(hr.entity(i+1)); } } diff --git a/draw.cpp b/draw.cpp index 149179a..183f224 100644 --- a/draw.cpp +++ b/draw.cpp @@ -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; for(s = selection.First(); s; s = selection.NextAfter(s)) { - if(s->entity.v == he.v) { + if(s->Equals(st)) { return true; } } @@ -107,16 +113,18 @@ bool GraphicsWindow::EntityIsSelected(hEntity he) { } //----------------------------------------------------------------------------- -// Toggle the selection state of the indicated item: if it was selected then -// un-select it, and if it wasn't then select it. +// Unselect an item, if it is selected. We can either unselect just that item, +// 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; ZERO(&stog); 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; Selection *s; @@ -127,14 +135,12 @@ void GraphicsWindow::ToggleSelectionStateOf(Selection *stog, bool batch) { for(s = selection.First(); s; s = selection.NextAfter(s)) { if(s->Equals(stog)) { s->tag = 1; - wasSelected = true; - break; } } // If two points are coincident, then it's impossible to hover one of // them. But make sure to deselect both, to avoid mysterious seeming // 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); if(e->IsPoint()) { 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. - if(!batch) { - selection.RemoveTagged(); - } - if(wasSelected) return; - - // So it's not selected, so we should select it. +//----------------------------------------------------------------------------- +// Select an item, if it isn't selected already. +//----------------------------------------------------------------------------- +void GraphicsWindow::MakeSelected(hEntity he) { + Selection stog; + ZERO(&stog); + stog.entity = he; + 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()) { // In the interest of speed for the triangle drawing code, // only two faces may be selected at a time. int c = 0; + Selection *s; selection.ClearTags(); for(s = selection.First(); s; s = selection.NextAfter(s)) { hEntity he = s->entity; @@ -202,7 +215,7 @@ void GraphicsWindow::SelectByMarquee(void) { if(pp.x >= xmin && pp.x <= xmax && pp.y >= ymin && pp.y <= ymax) { - ToggleSelectionStateOf(e->h, true); + MakeSelected(e->h); } } else { // Use the 3d bounding box test routines, to avoid duplication; @@ -224,7 +237,7 @@ void GraphicsWindow::SelectByMarquee(void) { !ptA.OutsideAndNotOn(ptMax, ptMin) || !ptB.OutsideAndNotOn(ptMax, ptMin)) { - ToggleSelectionStateOf(e->h, true); + MakeSelected(e->h); break; } } diff --git a/graphicswin.cpp b/graphicswin.cpp index 08791c9..8254741 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -633,7 +633,7 @@ void GraphicsWindow::MenuEdit(int id) { if(e->IsFace() || e->IsDistance()) continue; if(!e->IsVisible()) continue; - SS.GW.ToggleSelectionStateOf(e->h, true); + SS.GW.MakeSelected(e->h); } InvalidateGraphics(); SS.later.showTW = true; @@ -675,15 +675,15 @@ void GraphicsWindow::MenuEdit(int id) { } } if(onChain && !alreadySelected) { - SS.GW.ToggleSelectionStateOf(e->h, true); + SS.GW.MakeSelected(e->h); newlySelected++; didSomething = true; } } } while(didSomething); if(newlySelected == 0) { - Error("No entities share endpoints with the selected " - "entities."); + Error("No additional entities share endpoints with the " + "selected entities."); } InvalidateGraphics(); SS.later.showTW = true; diff --git a/mouse.cpp b/mouse.cpp index 4f6ef68..8b8bbab 100644 --- a/mouse.cpp +++ b/mouse.cpp @@ -177,21 +177,21 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, pending.normal = hover.entity; pending.operation = DRAGGING_NORMAL; } else { - if(EntityIsSelected(e->h)) { - // The entity is selected, which means that it wasn't - // before the user clicked to start dragging, which + if(!hoverWasSelectedOnMousedown) { + // The user clicked an unselected entity, which // means they're dragging just the hovered thing, // not the full selection. So clear all the selection // except that entity. ClearSelection(); - ToggleSelectionStateOf(e->h); + MakeSelected(e->h); } StartDraggingBySelection(); - // If something's hovered, then the user selected it when - // they clicked to start dragging, but they probably - // didn't mean to select it. Or if it was selected, then - // they didn't mean to deselect it. So fix that. - ToggleSelectionStateOf(e->h); + if(!hoverWasSelectedOnMousedown) { + // And then clear the selection again, since they + // probably didn't want that selected if they just + // were dragging it. + ClearSelection(); + } hover.Clear(); pending.operation = DRAGGING_POINTS; } @@ -214,7 +214,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, if(hover.entity.v) { // Avoid accidentally selecting workplanes when // starting drags. - ToggleSelectionStateOf(hover.entity); + MakeUnselected(hover.entity, false); hover.Clear(); } pending.operation = DRAGGING_MARQUEE; @@ -500,121 +500,95 @@ void GraphicsWindow::MouseRightUp(double x, double y) { context.active = true; + if(!hover.IsEmpty()) { + MakeSelected(&hover); + SS.later.showTW = true; + } 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) - // 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; - } + bool itemsSelected = (gs.n > 0 || gs.constraints > 0); - if(selEmpty) { - if(hover.IsStylable()) { + if(itemsSelected) { + if(gs.stylables > 0) { ContextMenuListStyles(); - AddContextMenuItem("Hovered: Assign to Style", CONTEXT_SUBMENU); + AddContextMenuItem("Assign to Style", CONTEXT_SUBMENU); } - if(!hover.IsEmpty()) { - AddContextMenuItem("Hovered: Group Info", CMNU_GROUP_INFO); + if(gs.n + gs.constraints == 1) { + AddContextMenuItem("Group Info", CMNU_GROUP_INFO); } - if(hover.IsStylable()) { - AddContextMenuItem("Hovered: Style Info", CMNU_STYLE_INFO); + if(gs.n + gs.constraints == 1 && gs.stylables == 1) { + AddContextMenuItem("Style Info", CMNU_STYLE_INFO); } - if(hover.constraint.v) { - Constraint *c = SK.GetConstraint(hover.constraint); + if(gs.withEndpoints > 0) { + 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) { - AddContextMenuItem("Hovered: Toggle Reference Dimension", + AddContextMenuItem("Toggle Reference Dimension", CMNU_REFERENCE_DIM); } if(c->type == Constraint::ANGLE || c->type == Constraint::EQUAL_ANGLE) { - AddContextMenuItem("Hovered: Other Supplementary Angle", + AddContextMenuItem("Other Supplementary Angle", CMNU_OTHER_ANGLE); } } - if(hover.entity.v) { - Entity *p = SK.GetEntity(hover.entity); - if(p->IsPoint()) { - Constraint *c; - IdList *lc = &(SK.constraint); - 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) { - break; - } - } - if(c) { - AddContextMenuItem( - "Hovered: Delete Point-Coincident Constraint", - CMNU_DEL_COINCIDENT); + if(gs.comments > 0 || gs.points > 0) { + AddContextMenuItem("Snap to Grid", CMNU_SNAP_TO_GRID); + } + + if(gs.points == 1) { + Entity *p = SK.GetEntity(gs.point[0]); + Constraint *c; + IdList *lc = &(SK.constraint); + 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) { + break; } } - } - if(hover.HasEndpoints()) { - AddContextMenuItem("Hovered: Select Edge Chain", CMNU_SELECT_CHAIN); - } - 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); + if(c) { + AddContextMenuItem("Delete Point-Coincident Constraint", + CMNU_DEL_COINCIDENT); + } } AddContextMenuItem(NULL, CONTEXT_SEPARATOR); if(LockedInWorkplane()) { - AddContextMenuItem("Cut Selection", CMNU_CUT_SEL); - AddContextMenuItem("Copy Selection", CMNU_COPY_SEL); + AddContextMenuItem("Cut", CMNU_CUT_SEL); + AddContextMenuItem("Copy", CMNU_COPY_SEL); } } if(SS.clipboard.r.n > 0 && LockedInWorkplane()) { - AddContextMenuItem("Paste Selection", CMNU_PASTE_SEL); + AddContextMenuItem("Paste", CMNU_PASTE_SEL); } - if(!selEmpty) { - AddContextMenuItem("Delete Selection", CMNU_DELETE_SEL); + if(itemsSelected) { + AddContextMenuItem("Delete", CMNU_DELETE_SEL); + AddContextMenuItem(NULL, CONTEXT_SEPARATOR); 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(); - if(ret != 0 && ret != CMNU_DEL_COINCIDENT && selEmpty) { - ToggleSelectionStateOf(&hover); - } switch(ret) { case CMNU_UNSELECT_ALL: MenuEdit(MNU_UNSELECT_ALL); break; + case CMNU_UNSELECT_HOVERED: + if(!hover.IsEmpty()) { + MakeUnselected(&hover, true); + } + break; + case CMNU_SELECT_CHAIN: MenuEdit(MNU_SELECT_CHAIN); break; @@ -645,8 +619,8 @@ void GraphicsWindow::MouseRightUp(double x, double y) { case CMNU_DEL_COINCIDENT: { SS.UndoRemember(); - if(!hover.entity.v) break; - Entity *p = SK.GetEntity(hover.entity); + if(!gs.point[0].v) break; + Entity *p = SK.GetEntity(gs.point[0]); if(!p->IsPoint()) break; SK.constraint.ClearTags(); @@ -658,6 +632,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } } SK.constraint.RemoveTagged(); + ClearSelection(); break; } @@ -667,7 +642,6 @@ void GraphicsWindow::MouseRightUp(double x, double y) { case CMNU_GROUP_INFO: { hGroup hg; - GroupSelection(); if(gs.entities == 1) { hg = SK.GetEntity(gs.entity[0])->group; } else if(gs.points == 1) { @@ -687,7 +661,6 @@ void GraphicsWindow::MouseRightUp(double x, double y) { case CMNU_STYLE_INFO: { hStyle hs; - GroupSelection(); if(gs.entities == 1) { hs = Style::ForEntity(gs.entity[0]); } else if(gs.points == 1) { @@ -726,8 +699,8 @@ void GraphicsWindow::MouseRightUp(double x, double y) { break; } -done: context.active = false; + SS.later.showTW = true; } hRequest GraphicsWindow::AddRequest(int type) { @@ -1066,8 +1039,9 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { case 0: default: ClearPending(); - if(hover.entity.v || hover.constraint.v) { - ToggleSelectionStateOf(&hover); + if(!hover.IsEmpty()) { + hoverWasSelectedOnMousedown = IsSelected(&hover); + MakeSelected(&hover); } break; } @@ -1078,6 +1052,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { void GraphicsWindow::MouseLeftUp(double mx, double my) { orig.mouseDown = false; + hoverWasSelectedOnMousedown = false; switch(pending.operation) { case DRAGGING_POINTS: diff --git a/ui.h b/ui.h index 5f5a0ac..31b3821 100644 --- a/ui.h +++ b/ui.h @@ -456,6 +456,7 @@ public: bool HasEndpoints(void); }; Selection hover; + bool hoverWasSelectedOnMousedown; List selection; void HitTestMakeSelection(Point2d mp); void ClearSelection(void); @@ -486,18 +487,22 @@ public: int n; } gs; void GroupSelection(void); - bool EntityIsSelected(hEntity he); - void ToggleSelectionStateOf(hEntity he, bool batch=false); - void ToggleSelectionStateOf(Selection *s, bool batch=false); + bool IsSelected(Selection *s); + bool IsSelected(hEntity he); + void MakeSelected(hEntity he); + void MakeSelected(Selection *s); + void MakeUnselected(hEntity he, bool coincidentPointTrick); + void MakeUnselected(Selection *s, bool coincidentPointTrick); void SelectByMarquee(void); void ClearSuper(void); static const int CMNU_UNSELECT_ALL = 0x100; - static const int CMNU_CUT_SEL = 0x101; - static const int CMNU_COPY_SEL = 0x102; - static const int CMNU_PASTE_SEL = 0x103; - static const int CMNU_DELETE_SEL = 0x104; - static const int CMNU_SELECT_CHAIN = 0x105; + static const int CMNU_UNSELECT_HOVERED = 0x101; + static const int CMNU_CUT_SEL = 0x102; + static const int CMNU_COPY_SEL = 0x103; + static const int CMNU_PASTE_SEL = 0x104; + 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_NO_STYLE = 0x111; static const int CMNU_GROUP_INFO = 0x120; diff --git a/wishlist.txt b/wishlist.txt index 8202ebc..7fb3679 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -1,6 +1,5 @@ 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