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]
This commit is contained in:
Jonathan Westhues 2009-09-23 02:59:59 -08:00
parent 274005c02c
commit 9416faca88
10 changed files with 346 additions and 100 deletions

274
draw.cpp
View File

@ -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;

View File

@ -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();
}

View File

@ -777,21 +777,3 @@ void Group::CopyEntity(IdList<Entity,hEntity> *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;
}
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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);
}

20
ui.h
View File

@ -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);

View File

@ -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),

View File

@ -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