From 9416faca887cd99f1251aac424c24c9a2cb0b3c0 Mon Sep 17 00:00:00 2001 From: Jonathan Westhues Date: Wed, 23 Sep 2009 02:59:59 -0800 Subject: [PATCH] Add a context menu, with a grab bag of options. That will need some refinement later, but it does not affect file formats so it's not very critical. [git-p4: depot-paths = "//depot/solvespace/": change = 2032] --- draw.cpp | 274 ++++++++++++++++++++++++++++++++++++++-------- graphicswin.cpp | 2 + group.cpp | 18 --- sketch.h | 4 +- solvespace.cpp | 2 +- solvespace.h | 6 + style.cpp | 74 ++++++++----- ui.h | 20 +++- win32/w32main.cpp | 41 +++++++ wishlist.txt | 5 +- 10 files changed, 346 insertions(+), 100 deletions(-) diff --git a/draw.cpp b/draw.cpp index 08e2d7e..e7b5eaf 100644 --- a/draw.cpp +++ b/draw.cpp @@ -20,6 +20,8 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, bool middleDown, bool rightDown, bool shiftDown, bool ctrlDown) { if(GraphicsEditControlIsVisible()) return; + if(context.active) return; + if(rightDown) { middleDown = true; shiftDown = !shiftDown; @@ -34,6 +36,13 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, Point2d mp = { x, y }; + if(rightDown && orig.mouse.DistanceTo(mp) < 5 && !orig.startedMoving) { + // Avoid accidentally panning (or rotating if shift is down) if the + // user wants a context menu. + return; + } + orig.startedMoving = true; + // If the middle button is down, then mouse movement is used to pan and // rotate our view. This wins over everything else. if(middleDown) { @@ -347,18 +356,174 @@ void GraphicsWindow::ClearPending(void) { void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { if(GraphicsEditControlIsVisible()) return; - if(pending.operation == DRAGGING_NEW_LINE_POINT) { - // Special case; use a middle or right click to stop drawing lines, - // since a left click would draw another one. This is quicker and - // more intuitive than hitting escape. - ClearPending(); - } - orig.offset = offset; orig.projUp = projUp; orig.projRight = projRight; orig.mouse.x = x; orig.mouse.y = y; + orig.startedMoving = false; +} + +void GraphicsWindow::ContextMenuListStyles(void) { + CreateContextSubmenu(); + Style *s; + bool empty = true; + for(s = SK.style.First(); s; s = SK.style.NextAfter(s)) { + if(s->h.v < Style::FIRST_CUSTOM) continue; + + AddContextMenuItem(s->DescriptionString(), CMNU_FIRST_STYLE + s->h.v); + empty = false; + } + + if(!empty) AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + + AddContextMenuItem("No Style", CMNU_NO_STYLE); + AddContextMenuItem("Newly Created Custom Style...", CMNU_NEW_CUSTOM_STYLE); +} + +void GraphicsWindow::MouseRightUp(double x, double y) { + // Don't show a context menu if the user is right-clicking the toolbar, + // or if they are finishing a pan. + if(ToolbarMouseMoved((int)x, (int)y)) return; + if(orig.startedMoving) return; + + if(context.active) return; + context.active = true; + + if(pending.operation == DRAGGING_NEW_LINE_POINT) { + // Special case; use a right click to stop drawing lines, since + // a left click would draw another one. This is quicker and more + // intuitive than hitting escape. + ClearPending(); + } + + GroupSelection(); + if(hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { + // 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 toggleForStyles = false, + toggleForGroupInfo = false, + toggleForDelete = false; + + if(!hover.IsEmpty()) { + AddContextMenuItem("Toggle Hovered Item Selection", + CMNU_TOGGLE_SELECTION); + } + + if(gs.entities > 0) { + ContextMenuListStyles(); + AddContextMenuItem("Assign Selection to Style", CONTEXT_SUBMENU); + } else if(hover.entity.v && gs.n == 0 && gs.constraints == 0) { + ContextMenuListStyles(); + AddContextMenuItem("Assign Hovered Item to Style", CONTEXT_SUBMENU); + toggleForStyles = true; + } + + if(gs.n + gs.constraints == 1) { + AddContextMenuItem("Group Info for Selected Item", CMNU_GROUP_INFO); + } else if(!hover.IsEmpty() && gs.n == 0 && gs.constraints == 0) { + AddContextMenuItem("Group Info for Hovered Item", CMNU_GROUP_INFO); + toggleForGroupInfo = true; + } + + if(hover.constraint.v && gs.n == 0 && gs.constraints == 0) { + Constraint *c = SK.GetConstraint(hover.constraint); + if(c->HasLabel()) { + AddContextMenuItem("Toggle Reference Dimension", + CMNU_REFERENCE_DIM); + } + if(c->type == Constraint::ANGLE || + c->type == Constraint::EQUAL_ANGLE) + { + AddContextMenuItem("Other Supplementary Angle", + CMNU_OTHER_ANGLE); + } + } + + if(gs.n > 0 || gs.constraints > 0) { + AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + AddContextMenuItem("Delete Selection", CMNU_DELETE_SEL); + AddContextMenuItem("Unselect All", CMNU_UNSELECT_ALL); + } else if(!hover.IsEmpty()) { + AddContextMenuItem(NULL, CONTEXT_SEPARATOR); + AddContextMenuItem("Delete Hovered Item", CMNU_DELETE_SEL); + toggleForDelete = true; + } + + int ret = ShowContextMenu(); + switch(ret) { + case CMNU_TOGGLE_SELECTION: + ToggleSelectionStateOfHovered(); + break; + + case CMNU_UNSELECT_ALL: + MenuEdit(MNU_UNSELECT_ALL); + break; + + case CMNU_DELETE_SEL: + if(toggleForDelete) ToggleSelectionStateOfHovered(); + MenuEdit(MNU_DELETE); + break; + + case CMNU_REFERENCE_DIM: + ToggleSelectionStateOfHovered(); + Constraint::MenuConstrain(MNU_REFERENCE); + break; + + case CMNU_OTHER_ANGLE: + ToggleSelectionStateOfHovered(); + Constraint::MenuConstrain(MNU_OTHER_ANGLE); + break; + + case CMNU_GROUP_INFO: { + if(toggleForGroupInfo) ToggleSelectionStateOfHovered(); + GroupSelection(); + hGroup hg; + if(gs.entities == 1) { + hg = SK.GetEntity(gs.entity[0])->group; + } else if(gs.points == 1) { + hg = SK.GetEntity(gs.point[0])->group; + } else if(gs.constraints == 1) { + hg = SK.GetConstraint(gs.constraint[0])->group; + } else { + break; + } + ClearSelection(); + SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); + SS.TW.shown.group = hg; + SS.later.showTW = true; + break; + } + + case CMNU_NEW_CUSTOM_STYLE: { + if(toggleForStyles) ToggleSelectionStateOfHovered(); + DWORD v = Style::CreateCustomStyle(); + Style::AssignSelectionToStyle(v); + break; + } + + case CMNU_NO_STYLE: + if(toggleForStyles) ToggleSelectionStateOfHovered(); + Style::AssignSelectionToStyle(0); + break; + + default: + if(ret >= CMNU_FIRST_STYLE) { + if(toggleForStyles) ToggleSelectionStateOfHovered(); + Style::AssignSelectionToStyle(ret - CMNU_FIRST_STYLE); + } + // otherwise it was probably cancelled, so do nothing + break; + } + +done: + context.active = false; } hRequest GraphicsWindow::AddRequest(int type) { @@ -607,43 +772,10 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { } case 0: - default: { + default: ClearPending(); - - if(hover.IsEmpty()) break; - - // If an item is hovered, then by clicking on it, we toggle its - // selection state. - int i; - for(i = 0; i < MAX_SELECTED; i++) { - if(selection[i].Equals(&hover)) { - selection[i].Clear(); - break; - } - } - if(i != MAX_SELECTED) break; - - if(hover.entity.v != 0 && SK.GetEntity(hover.entity)->IsFace()) { - // In the interest of speed for the triangle drawing code, - // only two faces may be selected at a time. - int c = 0; - for(i = 0; i < MAX_SELECTED; i++) { - hEntity he = selection[i].entity; - if(he.v != 0 && SK.GetEntity(he)->IsFace()) { - c++; - if(c >= 2) selection[i].Clear(); - } - } - } - - for(i = 0; i < MAX_SELECTED; i++) { - if(selection[i].IsEmpty()) { - selection[i] = hover; - break; - } - } + ToggleSelectionStateOfHovered(); break; - } } SS.later.showTW = true; @@ -771,11 +903,14 @@ void GraphicsWindow::MouseScroll(double x, double y, int delta) { } void GraphicsWindow::MouseLeave(void) { - // Un-hover everything when the mouse leaves our window. - hover.Clear(); - toolbarTooltipped = 0; - toolbarHovered = 0; - PaintGraphics(); + // Un-hover everything when the mouse leaves our window, unless there's + // currently a context menu shown. + if(!context.active) { + hover.Clear(); + toolbarTooltipped = 0; + toolbarHovered = 0; + PaintGraphics(); + } } bool GraphicsWindow::Selection::Equals(Selection *b) { @@ -851,6 +986,51 @@ void GraphicsWindow::ClearNonexistentSelectionItems(void) { if(change) InvalidateGraphics(); } +//----------------------------------------------------------------------------- +// Toggle the selection state of the hovered item: if it was selected then +// un-select it, and if it wasn't then select it. +//----------------------------------------------------------------------------- +void GraphicsWindow::ToggleSelectionStateOfHovered(void) { + if(hover.IsEmpty()) return; + + // If an item was selected, then we just un-select it. + int i; + for(i = 0; i < MAX_SELECTED; i++) { + if(selection[i].Equals(&hover)) { + selection[i].Clear(); + break; + } + } + if(i != MAX_SELECTED) return; + + // So it's not selected, so we should select it. + + if(hover.entity.v != 0 && SK.GetEntity(hover.entity)->IsFace()) { + // In the interest of speed for the triangle drawing code, + // only two faces may be selected at a time. + int c = 0; + for(i = 0; i < MAX_SELECTED; i++) { + hEntity he = selection[i].entity; + if(he.v != 0 && SK.GetEntity(he)->IsFace()) { + c++; + if(c >= 2) selection[i].Clear(); + } + } + } + + for(i = 0; i < MAX_SELECTED; i++) { + if(selection[i].IsEmpty()) { + selection[i] = hover; + break; + } + } +} + +//----------------------------------------------------------------------------- +// Sort the selection according to various critieria: the entities and +// constraints separately, counts of certain types of entities (circles, +// lines, etc.), and so on. +//----------------------------------------------------------------------------- void GraphicsWindow::GroupSelection(void) { memset(&gs, 0, sizeof(gs)); int i; diff --git a/graphicswin.cpp b/graphicswin.cpp index 510af86..1b0376c 100644 --- a/graphicswin.cpp +++ b/graphicswin.cpp @@ -154,6 +154,8 @@ void GraphicsWindow::Init(void) { showTextWindow = true; ShowTextWindow(showTextWindow); + context.active = false; + // Do this last, so that all the menus get updated correctly. EnsureValidActives(); } diff --git a/group.cpp b/group.cpp index 85aef48..1f3dc21 100644 --- a/group.cpp +++ b/group.cpp @@ -777,21 +777,3 @@ void Group::CopyEntity(IdList *el, el->Add(&en); } -void Group::TagEdgesFromLineSegments(SEdgeList *el) { - int i, j; - for(i = 0; i < SK.entity.n; i++) { - Entity *e = &(SK.entity.elem[i]); - if(e->group.v != opA.v) continue; - if(e->type != Entity::LINE_SEGMENT) continue; - - Vector p0 = SK.GetEntity(e->point[0])->PointGetNum(); - Vector p1 = SK.GetEntity(e->point[1])->PointGetNum(); - - for(j = 0; j < el->l.n; j++) { - SEdge *se = &(el->l.elem[j]); - if((p0.Equals(se->a) && p1.Equals(se->b))) se->tag = e->h.v; - if((p0.Equals(se->b) && p1.Equals(se->a))) se->tag = e->h.v; - } - } -} - diff --git a/sketch.h b/sketch.h index 6f68992..07fc6f1 100644 --- a/sketch.h +++ b/sketch.h @@ -201,7 +201,6 @@ public: hEntity Remap(hEntity in, int copyNumber); void MakeExtrusionLines(EntityList *el, hEntity in); void MakeExtrusionTopBottomFaces(EntityList *el, hEntity pt); - void TagEdgesFromLineSegments(SEdgeList *sle); void CopyEntity(EntityList *el, Entity *ep, int timesApplied, int remap, hParam dx, hParam dy, hParam dz, @@ -654,6 +653,9 @@ public: static void FreezeDefaultStyles(void); static void LoadFactoryDefaults(void); + static void AssignSelectionToStyle(DWORD v); + static DWORD CreateCustomStyle(void); + static Style *Get(hStyle hs); static DWORD Color(hStyle hs, bool forExport=false); static float Width(hStyle hs); diff --git a/solvespace.cpp b/solvespace.cpp index 9c24c62..210c409 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -27,7 +27,7 @@ void SolveSpace::CheckLicenseFromRegistry(void) { } const int SECONDS_IN_DAY = 60*60*24; - license.trialDaysRemaining = 90 - + license.trialDaysRemaining = 30 - (int)(((now - license.firstUse))/SECONDS_IN_DAY); } diff --git a/solvespace.h b/solvespace.h index f4c0c5d..56973e1 100644 --- a/solvespace.h +++ b/solvespace.h @@ -114,6 +114,12 @@ void ShowTextEditControl(int hr, int c, char *s); void HideTextEditControl(void); BOOL TextEditControlIsVisible(void); +#define CONTEXT_SUBMENU (-1) +#define CONTEXT_SEPARATOR (-2) +void AddContextMenuItem(char *legend, int id); +void CreateContextSubmenu(void); +int ShowContextMenu(void); + void ShowTextWindow(BOOL visible); void InvalidateText(void); void InvalidateGraphics(void); diff --git a/style.cpp b/style.cpp index 2f4f3ef..21dd55b 100644 --- a/style.cpp +++ b/style.cpp @@ -33,8 +33,8 @@ char *Style::CnfWidth(char *prefix) { char *Style::CnfPrefixToName(char *prefix) { static char name[100]; int i = 0, j; - strcpy(name, "def-"); - j = 4; + strcpy(name, "#def-"); + j = 5; while(prefix[i] && j < 90) { if(isupper(prefix[i]) && i != 0) { name[j++] = '-'; @@ -106,6 +106,46 @@ void Style::FreezeDefaultStyles(void) { } } +DWORD Style::CreateCustomStyle(void) { + SS.UndoRemember(); + DWORD vs = max(Style::FIRST_CUSTOM, SK.style.MaximumId() + 1); + hStyle hs = { vs }; + (void)Style::Get(hs); + return hs.v; +} + +void Style::AssignSelectionToStyle(DWORD v) { + bool showError = false; + SS.GW.GroupSelection(); + + SS.UndoRemember(); + for(int i = 0; i < SS.GW.gs.entities; i++) { + hEntity he = SS.GW.gs.entity[i]; + if(!he.isFromRequest()) { + showError = true; + continue; + } + + hRequest hr = he.request(); + Request *r = SK.GetRequest(hr); + r->style.v = v; + SS.later.generateAll = true; + } + + if(showError) { + Error("Can't assign style to an entity that's derived from another " + "entity; try assigning a style to this entity's parent."); + } + + SS.GW.ClearSelection(); + InvalidateGraphics(); + + // And show that style's info screen in the text window. + SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); + SS.TW.shown.style.v = v; + SS.later.showTW = true; +} + //----------------------------------------------------------------------------- // Look up a style by its handle. If that style does not exist, then create // the style, according to our table of default styles. @@ -241,10 +281,7 @@ void TextWindow::ScreenLoadFactoryDefaultStyles(int link, DWORD v) { } void TextWindow::ScreenCreateCustomStyle(int link, DWORD v) { - SS.UndoRemember(); - DWORD vs = max(Style::FIRST_CUSTOM, SK.style.MaximumId() + 1); - hStyle hs = { vs }; - (void)Style::Get(hs); + Style::CreateCustomStyle(); } void TextWindow::ScreenChangeBackgroundColor(int link, DWORD v) { @@ -475,29 +512,6 @@ void TextWindow::ShowStyleInfo(void) { } void TextWindow::ScreenAssignSelectionToStyle(int link, DWORD v) { - bool showError = false; - SS.GW.GroupSelection(); - - SS.UndoRemember(); - for(int i = 0; i < SS.GW.gs.entities; i++) { - hEntity he = SS.GW.gs.entity[i]; - if(!he.isFromRequest()) { - showError = true; - continue; - } - - hRequest hr = he.request(); - Request *r = SK.GetRequest(hr); - r->style.v = v; - SS.later.generateAll = true; - } - - if(showError) { - Error("Can't assign style to an entity that's derived from another " - "entity; try assigning a style to this entity's parent."); - } - - SS.GW.ClearSelection(); - InvalidateGraphics(); + Style::AssignSelectionToStyle(v); } diff --git a/ui.h b/ui.h index 48beb91..1586348 100644 --- a/ui.h +++ b/ui.h @@ -313,6 +313,7 @@ public: Vector projRight; Vector projUp; Point2d mouse; + bool startedMoving; } orig; // When the user is dragging a point, don't solve multiple times without @@ -320,6 +321,11 @@ public: // not displayed. bool havePainted; + // Some state for the context menu. + struct { + bool active; + } context; + void NormalizeProjectionVectors(void); Point2d ProjectPoint(Vector p); Vector ProjectPoint3(Vector p); @@ -422,9 +428,20 @@ public: int n; } gs; void GroupSelection(void); - + void ToggleSelectionStateOfHovered(void); void ClearSuper(void); + static const int CMNU_TOGGLE_SELECTION = 0x100; + static const int CMNU_UNSELECT_ALL = 0x101; + static const int CMNU_DELETE_SEL = 0x102; + static const int CMNU_NEW_CUSTOM_STYLE = 0x103; + static const int CMNU_NO_STYLE = 0x104; + static const int CMNU_GROUP_INFO = 0x105; + static const int CMNU_REFERENCE_DIM = 0x106; + static const int CMNU_OTHER_ANGLE = 0x107; + static const int CMNU_FIRST_STYLE = 0x40000000; + void ContextMenuListStyles(void); + // The toolbar, in toolbar.cpp bool ToolbarDrawOrHitTest(int x, int y, bool paint, int *menu); void ToolbarDraw(void); @@ -459,6 +476,7 @@ public: void MouseLeftUp(double x, double y); void MouseLeftDoubleClick(double x, double y); void MouseMiddleOrRightDown(double x, double y); + void MouseRightUp(double x, double y); void MouseScroll(double x, double y, int delta); void MouseLeave(void); void EditControlDone(char *s); diff --git a/win32/w32main.cpp b/win32/w32main.cpp index 30de427..1c4108b 100644 --- a/win32/w32main.cpp +++ b/win32/w32main.cpp @@ -49,6 +49,8 @@ char RecentFile[MAX_RECENT][MAX_PATH]; HMENU SubMenus[100]; HMENU RecentOpenMenu, RecentImportMenu; +HMENU ContextMenu, ContextSubmenu; + int ClientIsSmallerBy; HFONT FixedFont, LinkFont; @@ -94,6 +96,42 @@ void Message(char *str, ...) va_end(f); } +void AddContextMenuItem(char *label, int id) +{ + if(!ContextMenu) ContextMenu = CreatePopupMenu(); + + if(id == CONTEXT_SUBMENU) { + AppendMenu(ContextMenu, MF_STRING | MF_POPUP, + (UINT_PTR)ContextSubmenu, label); + ContextSubmenu = NULL; + } else { + HMENU m = ContextSubmenu ? ContextSubmenu : ContextMenu; + if(id == CONTEXT_SEPARATOR) { + AppendMenu(m, MF_SEPARATOR, 0, ""); + } else { + AppendMenu(m, MF_STRING, id, label); + } + } +} + +void CreateContextSubmenu(void) +{ + ContextSubmenu = CreatePopupMenu(); +} + +int ShowContextMenu(void) +{ + POINT p; + GetCursorPos(&p); + int r = TrackPopupMenu(ContextMenu, + TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_TOPALIGN, + p.x, p.y, 0, GraphicsWnd, NULL); + + DestroyMenu(ContextMenu); + ContextMenu = NULL; + return r; +} + void CALLBACK TimerCallback(HWND hwnd, UINT msg, UINT_PTR id, DWORD time) { // The timer is periodic, so needs to be killed explicitly. @@ -709,6 +747,7 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: + case WM_RBUTTONUP: case WM_MBUTTONDOWN: { int x = LOWORD(lParam); int y = HIWORD(lParam); @@ -738,6 +777,8 @@ LRESULT CALLBACK GraphicsWndProc(HWND hwnd, UINT msg, WPARAM wParam, SS.GW.MouseLeftDoubleClick(x, y); } else if(msg == WM_MBUTTONDOWN || msg == WM_RBUTTONDOWN) { SS.GW.MouseMiddleOrRightDown(x, y); + } else if(msg == WM_RBUTTONUP) { + SS.GW.MouseRightUp(x, y); } else if(msg == WM_MOUSEMOVE) { SS.GW.MouseMoved(x, y, !!(wParam & MK_LBUTTON), diff --git a/wishlist.txt b/wishlist.txt index c362bb6..32a8b47 100644 --- a/wishlist.txt +++ b/wishlist.txt @@ -2,10 +2,11 @@ grid better text right-click menu -faster triangulation -interpolating splines +wireframe export ----- +faster triangulation +interpolating splines copy and paste loop detection IGES export