From 2c39f259db0c6b6f1eeef7fa95125311daadc991 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 18 Mar 2015 20:02:11 +0300 Subject: [PATCH] Add a GTK2/3 port. In principle, GTK3 is the way forward, and GTK2 is officially deprecated, though still maintained. In practice however, GTK3 is often unbearably buggy; e.g. on my system, combo boxes don't ever roll up in GTK3 windows. So I have added support for both. This required a few minor changes to the core, namely: * GTK wants to know beforehand whether a menu item is a check menu item or a regular one. * GTK doesn't give us an easy way to execute something after any event is processed, so an explicit idle timer is added. This is a no-op on Win32. * A few function signatures were const'ed, since GTK expects immutable strings when converting to Glib::ustring. --- .gitignore | 1 + CMakeLists.txt | 28 +- src/CMakeLists.txt | 43 +- src/clipboard.cpp | 6 +- src/config.h.in | 19 +- src/constraint.cpp | 6 +- src/describescreen.cpp | 4 +- src/draw.cpp | 2 +- src/export.cpp | 10 +- src/file.cpp | 8 +- src/fltk/fltkmain.cpp | 11 +- src/generate.cpp | 2 +- src/graphicswin.cpp | 288 ++++---- src/group.cpp | 6 +- src/groupmesh.cpp | 2 +- src/gtk/gtkmain.cpp | 1511 ++++++++++++++++++++++++++++++++++++++++ src/modify.cpp | 4 +- src/mouse.cpp | 24 +- src/solvespace.cpp | 29 +- src/solvespace.h | 38 +- src/style.cpp | 6 +- src/textscreens.cpp | 12 +- src/textwin.cpp | 2 +- src/ui.h | 16 +- src/undoredo.cpp | 2 +- src/unix/unixutil.cpp | 8 + src/win32/w32main.cpp | 4 + 27 files changed, 1860 insertions(+), 232 deletions(-) create mode 100644 src/gtk/gtkmain.cpp diff --git a/.gitignore b/.gitignore index 2a50a889..a00d07b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /CMakeCache.txt /cbuild/ +*.trace # OpenGL apitrace files diff --git a/CMakeLists.txt b/CMakeLists.txt index 468282d6..45974ac6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,10 @@ project(solvespace) set(solvespace_VERSION_MAJOR 2) set(solvespace_VERSION_MINOR 1) +if(NOT WIN32) + set(GUI gtk2 CACHE STRING "GUI toolkit to use (one of: fltk gtk2 gtk3)") +endif() + # compiler if(WIN32) @@ -68,8 +72,28 @@ else() find_package(PNG REQUIRED) find_package(SpaceWare) - find_package(FLTK REQUIRED) - CHECK_INCLUDE_FILE("fontconfig/fontconfig.h" HAVE_FONTCONFIG) + if(GUI STREQUAL "fltk") + # Find the packages the old-fashioned way. Doesn't require + # the system to follow freedesktop standards. + find_package(FLTK REQUIRED) + CHECK_INCLUDE_FILE("fontconfig/fontconfig.h" HAVE_FONTCONFIG) + elseif(GUI STREQUAL "gtk3" OR GUI STREQUAL "gtk2") + # Use freedesktop's pkg-config to locate everything. + find_package(PkgConfig REQUIRED) + pkg_check_modules(FONTCONFIG REQUIRED fontconfig) + pkg_check_modules(JSONC REQUIRED json-c) + + set(HAVE_GTK TRUE) + if(GUI STREQUAL "gtk3") + set(HAVE_GTK3 TRUE) + pkg_check_modules(GTKMM REQUIRED gtkmm-3.0 pangomm-1.4 x11) + else() + set(HAVE_GTK2 TRUE) + pkg_check_modules(GTKMM REQUIRED gtkmm-2.4 pangomm-1.4 x11) + endif() + else() + message(FATAL_ERROR "GUI unrecognized: ${GUI}") + endif() endif() # components diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e8c0391..4631d925 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,14 @@ link_directories( add_definitions( ${PNG_CFLAGS_OTHER}) +link_directories( + ${PNG_LIBRARY_DIRS} + ${ZLIB_LIBRARY_DIRS}) + +add_definitions( + ${PNG_CFLAGS_OTHER} + ${ZLIB_CFLAGS_OTHER}) + include_directories( "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/built" @@ -20,8 +28,9 @@ if(SPACEWARE_FOUND) "${SPACEWARE_INCLUDE_DIR}") endif() -set(HAVE_FLTK ${FLTK_FOUND}) set(HAVE_SPACEWARE ${SPACEWARE_FOUND}) +set(HAVE_FLTK ${FLTK_FOUND}) +set(HAVE_GTK ${GTKMM_FOUND}) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h") @@ -48,7 +57,7 @@ set(libslvs_SOURCES set(libslvs_HEADERS solvespace.h) - add_library(slvs SHARED +add_library(slvs SHARED ${libslvs_SOURCES} ${libslvs_HEADERS} ${util_SOURCES} @@ -120,16 +129,38 @@ if(WIN32) win32/freeze.cpp win32/w32main.cpp win32/resource.rc) -else() - include_directories( - "${FLTK_INCLUDE_DIR}") +elseif(HAVE_FLTK) + include_directories(${FLTK_INCLUDE_DIR}) set(platform_SOURCES fltk/fltkmain.cpp) set(platform_LIBRARIES ${CMAKE_DL_LIBS} - "${FLTK_LIBRARIES}") + ${FLTK_LIBRARIES}) +elseif(HAVE_GTK) + include_directories( + ${GTKMM_INCLUDE_DIRS} + ${JSONC_INCLUDE_DIRS} + ${FONTCONFIG_INCLUDE_DIRS}) + + link_directories( + ${GTKMM_LIBRARY_DIRS} + ${JSONC_LIBRARY_DIRS} + ${FONTCONFIG_LIBRARY_DIRS}) + + add_definitions( + ${GTKMM_CFLAGS_OTHER} + ${JSONC_CFLAGS_OTHER} + ${FONTCONFIG_CFLAGS_OTHER}) + + set(platform_SOURCES + gtk/gtkmain.cpp) + + set(platform_LIBRARIES + ${GTKMM_LIBRARIES} + ${JSONC_LIBRARIES} + ${FONTCONFIG_LIBRARIES}) endif() # solvespace executable diff --git a/src/clipboard.cpp b/src/clipboard.cpp index 8cdfc3dd..549d4a7e 100644 --- a/src/clipboard.cpp +++ b/src/clipboard.cpp @@ -181,7 +181,7 @@ void GraphicsWindow::PasteClipboard(Vector trans, double theta, double scale) { } } - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } void GraphicsWindow::MenuClipboard(int id) { @@ -216,7 +216,7 @@ void GraphicsWindow::MenuClipboard(int id) { SS.TW.shown.paste.scale = 1; SS.TW.GoToScreen(TextWindow::SCREEN_PASTE_TRANSFORMED); SS.GW.ForceTextWindowShown(); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } @@ -360,7 +360,7 @@ void TextWindow::ScreenPasteTransformed(int link, uint32_t v) { SS.GW.PasteClipboard(t, theta, SS.TW.shown.paste.scale); } SS.TW.GoToScreen(SCREEN_LIST_OF_GROUPS); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } } diff --git a/src/config.h.in b/src/config.h.in index 516e638e..852e887e 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -3,12 +3,23 @@ #define PACKAGE_VERSION "@solvespace_VERSION_MAJOR@.@solvespace_VERSION_MINOR@" +/* MSVC includes a proper stdint.h, but only since VS2008. */ #cmakedefine HAVE_STDINT_H -#cmakedefine HAVE_FONTCONFIG_FONTCONFIG_H - -#cmakedefine HAVE_FLTK -#define HAVE_FLTK_FULLSCREEN +/* Do we have the si library on win32, or libspnav on *nix? */ #cmakedefine HAVE_SPACEWARE +#cmakedefine HAVE_GTK +#cmakedefine HAVE_GTK2 +#cmakedefine HAVE_GTK3 + +#cmakedefine HAVE_FLTK + +/* Only relevant for FLTK port. */ +#ifdef HAVE_FLTK +#define HAVE_FLTK_FULLSCREEN +#endif + +#cmakedefine HAVE_FONTCONFIG + #endif diff --git a/src/constraint.cpp b/src/constraint.cpp index b1290c9b..37a28b1c 100644 --- a/src/constraint.cpp +++ b/src/constraint.cpp @@ -86,7 +86,7 @@ void Constraint::AddConstraint(Constraint *c, bool rememberForUndo) { SK.constraint.AddAndAssignId(c); SS.MarkGroupDirty(c->group); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } void Constraint::Constrain(int type, hEntity ptA, hEntity ptB, @@ -515,7 +515,7 @@ void Constraint::MenuConstrain(int id) { SS.UndoRemember(); c->other = !(c->other); SS.MarkGroupDirty(c->group); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); break; } } @@ -699,7 +699,7 @@ void Constraint::MenuConstrain(int id) { case GraphicsWindow::MNU_COMMENT: SS.GW.pending.operation = GraphicsWindow::MNU_COMMENT; SS.GW.pending.description = "click center of comment text"; - SS.later.showTW = true; + SS.ScheduleShowTW(); break; default: oops(); diff --git a/src/describescreen.cpp b/src/describescreen.cpp index a4d21ea3..1ace25af 100644 --- a/src/describescreen.cpp +++ b/src/describescreen.cpp @@ -37,8 +37,8 @@ void TextWindow::ScreenSetTtfFont(int link, uint32_t v) { SS.UndoRemember(); r->font.strcpy(SS.fonts.l.elem[i].FontFileBaseName()); SS.MarkGroupDirty(r->group); - SS.later.generateAll = true; - SS.later.showTW = true; + SS.ScheduleGenerateAll(); + SS.ScheduleShowTW(); } void TextWindow::DescribeSelection(void) { diff --git a/src/draw.cpp b/src/draw.cpp index 2d563f3f..75620181 100644 --- a/src/draw.cpp +++ b/src/draw.cpp @@ -73,7 +73,7 @@ void GraphicsWindow::Selection::Draw(void) { void GraphicsWindow::ClearSelection(void) { selection.Clear(); - SS.later.showTW = true; + SS.ScheduleShowTW(); InvalidateGraphics(); } diff --git a/src/export.cpp b/src/export.cpp index 77c7db7f..74a9b5b9 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -9,7 +9,7 @@ #include "solvespace.h" #include -void SolveSpace::ExportSectionTo(char *filename) { +void SolveSpace::ExportSectionTo(const char *filename) { Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); gn = gn.WithMagnitude(1); @@ -109,7 +109,7 @@ void SolveSpace::ExportSectionTo(char *filename) { bl.Clear(); } -void SolveSpace::ExportViewOrWireframeTo(char *filename, bool wireframe) { +void SolveSpace::ExportViewOrWireframeTo(const char *filename, bool wireframe) { int i; SEdgeList edges; ZERO(&edges); @@ -403,7 +403,7 @@ double VectorFileWriter::MmToPts(double mm) { return (mm/25.4)*72; } -VectorFileWriter *VectorFileWriter::ForFile(char *filename) { +VectorFileWriter *VectorFileWriter::ForFile(const char *filename) { VectorFileWriter *ret; if(StringEndsIn(filename, ".dxf")) { static DxfFileWriter DxfWriter; @@ -573,7 +573,7 @@ void VectorFileWriter::BezierAsNonrationalCubic(SBezier *sb, int depth) { //----------------------------------------------------------------------------- // Export a triangle mesh, in the requested format. //----------------------------------------------------------------------------- -void SolveSpace::ExportMeshTo(char *filename) { +void SolveSpace::ExportMeshTo(const char *filename) { SMesh *m = &(SK.GetGroup(SS.GW.activeGroup)->displayMesh); if(m->IsEmpty()) { Error("Active group mesh is empty; nothing to export."); @@ -673,7 +673,7 @@ void SolveSpace::ExportMeshAsObjTo(FILE *f, SMesh *sm) { // Export a view of the model as an image; we just take a screenshot, by // rendering the view in the usual way and then copying the pixels. //----------------------------------------------------------------------------- -void SolveSpace::ExportAsPngTo(char *filename) { +void SolveSpace::ExportAsPngTo(const char *filename) { int w = (int)SS.GW.width, h = (int)SS.GW.height; // No guarantee that the back buffer contains anything valid right now, // so repaint the scene. And hide the toolbar too. diff --git a/src/file.cpp b/src/file.cpp index 315c3268..e58f86e9 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -253,11 +253,11 @@ void SolveSpace::SaveUsingTable(int type) { } } -bool SolveSpace::SaveToFile(char *filename) { +bool SolveSpace::SaveToFile(const char *filename) { // Make sure all the entities are regenerated up to date, since they // will be exported. We reload the imported files because that rewrites // the impFileRel for our possibly-new filename. - SS.later.showTW = true; + SS.ScheduleShowTW(); SS.ReloadAllImported(); SS.GenerateAll(0, INT_MAX); @@ -423,7 +423,7 @@ void SolveSpace::LoadUsingTable(char *key, char *val) { } } -bool SolveSpace::LoadFromFile(char *filename) { +bool SolveSpace::LoadFromFile(const char *filename) { allConsistent = false; fileLoadError = false; @@ -506,7 +506,7 @@ bool SolveSpace::LoadFromFile(char *filename) { return true; } -bool SolveSpace::LoadEntitiesFromFile(char *file, EntityList *le, +bool SolveSpace::LoadEntitiesFromFile(const char *file, EntityList *le, SMesh *m, SShell *sh) { SSurface srf; diff --git a/src/fltk/fltkmain.cpp b/src/fltk/fltkmain.cpp index c7665740..55168d01 100644 --- a/src/fltk/fltkmain.cpp +++ b/src/fltk/fltkmain.cpp @@ -169,6 +169,10 @@ void SetTimerFor(int milliseconds) Fl::add_timeout((double)milliseconds / 1000.0, TimerCallback); } +void ScheduleLater() +{ +} + void OpenWebsite(const char *url) { fl_open_uri(url, NULL, 0); @@ -356,13 +360,6 @@ int64_t GetMilliseconds(void) return 1000 * (int64_t)sec + (int64_t)usec / 1000; } -int64_t GetUnixTime(void) -{ - time_t ret; - time(&ret); - return (int64_t)ret; -} - class Graphics_Gl_Window : public Fl_Gl_Window { public: diff --git a/src/generate.cpp b/src/generate.cpp index f919b2ef..37bb3b5a 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -311,7 +311,7 @@ void SolveSpace::GenerateAll(int first, int last, bool andFindFree) { if(deleted.groups > 0) { SS.TW.ClearSuper(); } - later.showTW = true; + ScheduleShowTW(); GW.ClearSuper(); // People get annoyed if I complain whenever they delete any request, diff --git a/src/graphicswin.cpp b/src/graphicswin.cpp index ee6e8db4..113f5c5c 100644 --- a/src/graphicswin.cpp +++ b/src/graphicswin.cpp @@ -20,143 +20,146 @@ #define S SHIFT_MASK #define C CTRL_MASK #define F(k) (FUNCTION_KEY_BASE+(k)) +#define IN MENU_ITEM_NORMAL +#define IC MENU_ITEM_CHECK +#define IR MENU_ITEM_RADIO const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { //level -// label id accel fn -{ 0, "&File", 0, 0, NULL }, -{ 1, "&New", MNU_NEW, C|'N', mFile }, -{ 1, "&Open...", MNU_OPEN, C|'O', mFile }, -{ 1, "Open &Recent", MNU_OPEN_RECENT, 0, mFile }, -{ 1, "&Save", MNU_SAVE, C|'S', mFile }, -{ 1, "Save &As...", MNU_SAVE_AS, 0, mFile }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Export &Image...", MNU_EXPORT_PNG, 0, mFile }, -{ 1, "Export 2d &View...", MNU_EXPORT_VIEW, 0, mFile }, -{ 1, "Export 2d &Section...", MNU_EXPORT_SECTION, 0, mFile }, -{ 1, "Export 3d &Wireframe...", MNU_EXPORT_WIREFRAME, 0, mFile }, -{ 1, "Export Triangle &Mesh...", MNU_EXPORT_MESH, 0, mFile }, -{ 1, "Export &Surfaces...", MNU_EXPORT_SURFACES,0, mFile }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "E&xit", MNU_EXIT, C|'Q', mFile }, +// label id accel ty fn +{ 0, "&File", 0, 0, IN, NULL }, +{ 1, "&New", MNU_NEW, C|'N', IN, mFile }, +{ 1, "&Open...", MNU_OPEN, C|'O', IN, mFile }, +{ 1, "Open &Recent", MNU_OPEN_RECENT, 0, IN, mFile }, +{ 1, "&Save", MNU_SAVE, C|'S', IN, mFile }, +{ 1, "Save &As...", MNU_SAVE_AS, 0, IN, mFile }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Export &Image...", MNU_EXPORT_PNG, 0, IN, mFile }, +{ 1, "Export 2d &View...", MNU_EXPORT_VIEW, 0, IN, mFile }, +{ 1, "Export 2d &Section...", MNU_EXPORT_SECTION, 0, IN, mFile }, +{ 1, "Export 3d &Wireframe...", MNU_EXPORT_WIREFRAME, 0, IN, mFile }, +{ 1, "Export Triangle &Mesh...", MNU_EXPORT_MESH, 0, IN, mFile }, +{ 1, "Export &Surfaces...", MNU_EXPORT_SURFACES,0, IN, mFile }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "E&xit", MNU_EXIT, C|'Q', IN, mFile }, -{ 0, "&Edit", 0, 0, NULL }, -{ 1, "&Undo", MNU_UNDO, C|'Z', mEdit }, -{ 1, "&Redo", MNU_REDO, C|'Y', mEdit }, -{ 1, "Re&generate All", MNU_REGEN_ALL, ' ', mEdit }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Snap Selection to &Grid", MNU_SNAP_TO_GRID, '.', mEdit }, +{ 0, "&Edit", 0, 0, IN, NULL }, +{ 1, "&Undo", MNU_UNDO, C|'Z', IN, mEdit }, +{ 1, "&Redo", MNU_REDO, C|'Y', IN, mEdit }, +{ 1, "Re&generate All", MNU_REGEN_ALL, ' ', IN, mEdit }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Snap Selection to &Grid", MNU_SNAP_TO_GRID, '.', IN, mEdit }, #ifdef WIN32 -{ 1, "Rotate Imported &90\260", MNU_ROTATE_90, '9', mEdit }, +{ 1, "Rotate Imported &90\260", MNU_ROTATE_90, '9', IN, mEdit }, #else -{ 1, "Rotate Imported &90°", MNU_ROTATE_90, '9', mEdit }, +{ 1, "Rotate Imported &90°", MNU_ROTATE_90, '9', IN, mEdit }, #endif -{ 1, NULL, 0, 0, NULL }, -{ 1, "Cu&t", MNU_CUT, C|'X', mClip }, -{ 1, "&Copy", MNU_COPY, C|'C', mClip }, -{ 1, "&Paste", MNU_PASTE, C|'V', mClip }, -{ 1, "Paste &Transformed...", MNU_PASTE_TRANSFORM,C|'T', mClip }, -{ 1, "&Delete", MNU_DELETE, DEL, mClip }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Select &Edge Chain", MNU_SELECT_CHAIN, C|'E', mEdit }, -{ 1, "Select &All", MNU_SELECT_ALL, C|'A', mEdit }, -{ 1, "&Unselect All", MNU_UNSELECT_ALL, ESC, mEdit }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Cu&t", MNU_CUT, C|'X', IN, mClip }, +{ 1, "&Copy", MNU_COPY, C|'C', IN, mClip }, +{ 1, "&Paste", MNU_PASTE, C|'V', IN, mClip }, +{ 1, "Paste &Transformed...", MNU_PASTE_TRANSFORM,C|'T', IN, mClip }, +{ 1, "&Delete", MNU_DELETE, DEL, IN, mClip }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Select &Edge Chain", MNU_SELECT_CHAIN, C|'E', IN, mEdit }, +{ 1, "Select &All", MNU_SELECT_ALL, C|'A', IN, mEdit }, +{ 1, "&Unselect All", MNU_UNSELECT_ALL, ESC, IN, mEdit }, -{ 0, "&View", 0, 0, NULL }, -{ 1, "Zoom &In", MNU_ZOOM_IN, '+', mView }, -{ 1, "Zoom &Out", MNU_ZOOM_OUT, '-', mView }, -{ 1, "Zoom To &Fit", MNU_ZOOM_TO_FIT, 'F', mView }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Align View to &Workplane", MNU_ONTO_WORKPLANE, 'W', mView }, -{ 1, "Nearest &Ortho View", MNU_NEAREST_ORTHO, F(2), mView }, -{ 1, "Nearest &Isometric View", MNU_NEAREST_ISO, F(3), mView }, -{ 1, "&Center View At Point", MNU_CENTER_VIEW, F(4), mView }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Show Snap &Grid", MNU_SHOW_GRID, '>', mView }, -{ 1, "Use &Perspective Projection", MNU_PERSPECTIVE_PROJ,'`', mView }, -{ 1, NULL, 0, 0, NULL }, -#ifdef HAVE_FLTK -{ 1, "Show Menu &Bar", MNU_SHOW_MENU_BAR, F(12), mView }, +{ 0, "&View", 0, 0, IN, NULL }, +{ 1, "Zoom &In", MNU_ZOOM_IN, '+', IN, mView }, +{ 1, "Zoom &Out", MNU_ZOOM_OUT, '-', IN, mView }, +{ 1, "Zoom To &Fit", MNU_ZOOM_TO_FIT, 'F', IN, mView }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Align View to &Workplane", MNU_ONTO_WORKPLANE, 'W', IN, mView }, +{ 1, "Nearest &Ortho View", MNU_NEAREST_ORTHO, F(2), IN, mView }, +{ 1, "Nearest &Isometric View", MNU_NEAREST_ISO, F(3), IN, mView }, +{ 1, "&Center View At Point", MNU_CENTER_VIEW, F(4), IN, mView }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Show Snap &Grid", MNU_SHOW_GRID, '>', IC, mView }, +{ 1, "Use &Perspective Projection", MNU_PERSPECTIVE_PROJ,'`', IC, mView }, +{ 1, NULL, 0, 0, IN, NULL }, +#if defined(HAVE_FLTK) +{ 1, "Show Menu &Bar", MNU_SHOW_MENU_BAR, F(12), IC, mView }, #endif -{ 1, "Show &Toolbar", MNU_SHOW_TOOLBAR, 0, mView }, -{ 1, "Show Text &Window", MNU_SHOW_TEXT_WND, '\t', mView }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Dimensions in &Inches", MNU_UNITS_INCHES, 0, mView }, -{ 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, mView }, -#ifdef HAVE_FLTK_FULLSCREEN -{ 1, NULL, 0, 0, NULL }, -{ 1, "&Full Screen", MNU_FULL_SCREEN, F(11), mView }, +{ 1, "Show &Toolbar", MNU_SHOW_TOOLBAR, 0, IC, mView }, +{ 1, "Show Text &Window", MNU_SHOW_TEXT_WND, '\t', IC, mView }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Dimensions in &Inches", MNU_UNITS_INCHES, 0, IR, mView }, +{ 1, "Dimensions in &Millimeters", MNU_UNITS_MM, 0, IR, mView }, +#if defined(HAVE_FLTK_FULLSCREEN) || defined(HAVE_GTK) +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "&Full Screen", MNU_FULL_SCREEN, F(11), IC, mView }, #endif -{ 0, "&New Group", 0, 0, NULL }, -{ 1, "Sketch In &3d", MNU_GROUP_3D, S|'3', mGrp }, -{ 1, "Sketch In New &Workplane", MNU_GROUP_WRKPL, S|'W', mGrp }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Step &Translating", MNU_GROUP_TRANS, S|'T', mGrp }, -{ 1, "Step &Rotating", MNU_GROUP_ROT, S|'R', mGrp }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "E&xtrude", MNU_GROUP_EXTRUDE, S|'X', mGrp }, -{ 1, "&Lathe", MNU_GROUP_LATHE, S|'L', mGrp }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Import / Assemble...", MNU_GROUP_IMPORT, S|'I', mGrp }, -{ 1, "Import Recent", MNU_GROUP_RECENT, 0, mGrp }, +{ 0, "&New Group", 0, 0, IN, NULL }, +{ 1, "Sketch In &3d", MNU_GROUP_3D, S|'3', IN, mGrp }, +{ 1, "Sketch In New &Workplane", MNU_GROUP_WRKPL, S|'W', IN, mGrp }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Step &Translating", MNU_GROUP_TRANS, S|'T', IN, mGrp }, +{ 1, "Step &Rotating", MNU_GROUP_ROT, S|'R', IN, mGrp }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "E&xtrude", MNU_GROUP_EXTRUDE, S|'X', IN, mGrp }, +{ 1, "&Lathe", MNU_GROUP_LATHE, S|'L', IN, mGrp }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Import / Assemble...", MNU_GROUP_IMPORT, S|'I', IN, mGrp }, +{ 1, "Import Recent", MNU_GROUP_RECENT, 0, IN, mGrp }, -{ 0, "&Sketch", 0, 0, NULL }, -{ 1, "In &Workplane", MNU_SEL_WORKPLANE, '2', mReq }, -{ 1, "Anywhere In &3d", MNU_FREE_IN_3D, '3', mReq }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Datum &Point", MNU_DATUM_POINT, 'P', mReq }, -{ 1, "&Workplane", MNU_WORKPLANE, 0, mReq }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Line &Segment", MNU_LINE_SEGMENT, 'S', mReq }, -{ 1, "&Rectangle", MNU_RECTANGLE, 'R', mReq }, -{ 1, "&Circle", MNU_CIRCLE, 'C', mReq }, -{ 1, "&Arc of a Circle", MNU_ARC, 'A', mReq }, -{ 1, "&Bezier Cubic Spline", MNU_CUBIC, 'B', mReq }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "&Text in TrueType Font", MNU_TTF_TEXT, 'T', mReq }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "To&ggle Construction", MNU_CONSTRUCTION, 'G', mReq }, -{ 1, "Tangent &Arc at Point", MNU_TANGENT_ARC, S|'A', mReq }, -{ 1, "Split Curves at &Intersection", MNU_SPLIT_CURVES, 'I', mReq }, +{ 0, "&Sketch", 0, 0, IN, NULL }, +{ 1, "In &Workplane", MNU_SEL_WORKPLANE, '2', IR, mReq }, +{ 1, "Anywhere In &3d", MNU_FREE_IN_3D, '3', IR, mReq }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Datum &Point", MNU_DATUM_POINT, 'P', IN, mReq }, +{ 1, "&Workplane", MNU_WORKPLANE, 0, IN, mReq }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Line &Segment", MNU_LINE_SEGMENT, 'S', IN, mReq }, +{ 1, "&Rectangle", MNU_RECTANGLE, 'R', IN, mReq }, +{ 1, "&Circle", MNU_CIRCLE, 'C', IN, mReq }, +{ 1, "&Arc of a Circle", MNU_ARC, 'A', IN, mReq }, +{ 1, "&Bezier Cubic Spline", MNU_CUBIC, 'B', IN, mReq }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "&Text in TrueType Font", MNU_TTF_TEXT, 'T', IN, mReq }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "To&ggle Construction", MNU_CONSTRUCTION, 'G', IN, mReq }, +{ 1, "Tangent &Arc at Point", MNU_TANGENT_ARC, S|'A', IN, mReq }, +{ 1, "Split Curves at &Intersection", MNU_SPLIT_CURVES, 'I', IN, mReq }, -{ 0, "&Constrain", 0, 0, NULL }, -{ 1, "&Distance / Diameter", MNU_DISTANCE_DIA, 'D', mCon }, -{ 1, "A&ngle", MNU_ANGLE, 'N', mCon }, -{ 1, "Other S&upplementary Angle", MNU_OTHER_ANGLE, 'U', mCon }, -{ 1, "Toggle R&eference Dim", MNU_REFERENCE, 'E', mCon }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "&Horizontal", MNU_HORIZONTAL, 'H', mCon }, -{ 1, "&Vertical", MNU_VERTICAL, 'V', mCon }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "&On Point / Curve / Plane", MNU_ON_ENTITY, 'O', mCon }, -{ 1, "E&qual Length / Radius / Angle", MNU_EQUAL, 'Q', mCon }, -{ 1, "Length Ra&tio", MNU_RATIO, 'Z', mCon }, -{ 1, "At &Midpoint", MNU_AT_MIDPOINT, 'M', mCon }, -{ 1, "S&ymmetric", MNU_SYMMETRIC, 'Y', mCon }, -{ 1, "Para&llel / Tangent", MNU_PARALLEL, 'L', mCon }, -{ 1, "&Perpendicular", MNU_PERPENDICULAR, '[', mCon }, -{ 1, "Same Orient&ation", MNU_ORIENTED_SAME, 'X', mCon }, -{ 1, "Lock Point Where &Dragged", MNU_WHERE_DRAGGED, ']', mCon }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Comment", MNU_COMMENT, ';', mCon }, +{ 0, "&Constrain", 0, 0, IN, NULL }, +{ 1, "&Distance / Diameter", MNU_DISTANCE_DIA, 'D', IN, mCon }, +{ 1, "A&ngle", MNU_ANGLE, 'N', IN, mCon }, +{ 1, "Other S&upplementary Angle", MNU_OTHER_ANGLE, 'U', IN, mCon }, +{ 1, "Toggle R&eference Dim", MNU_REFERENCE, 'E', IN, mCon }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "&Horizontal", MNU_HORIZONTAL, 'H', IN, mCon }, +{ 1, "&Vertical", MNU_VERTICAL, 'V', IN, mCon }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "&On Point / Curve / Plane", MNU_ON_ENTITY, 'O', IN, mCon }, +{ 1, "E&qual Length / Radius / Angle", MNU_EQUAL, 'Q', IN, mCon }, +{ 1, "Length Ra&tio", MNU_RATIO, 'Z', IN, mCon }, +{ 1, "At &Midpoint", MNU_AT_MIDPOINT, 'M', IN, mCon }, +{ 1, "S&ymmetric", MNU_SYMMETRIC, 'Y', IN, mCon }, +{ 1, "Para&llel / Tangent", MNU_PARALLEL, 'L', IN, mCon }, +{ 1, "&Perpendicular", MNU_PERPENDICULAR, '[', IN, mCon }, +{ 1, "Same Orient&ation", MNU_ORIENTED_SAME, 'X', IN, mCon }, +{ 1, "Lock Point Where &Dragged", MNU_WHERE_DRAGGED, ']', IN, mCon }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Comment", MNU_COMMENT, ';', IN, mCon }, -{ 0, "&Analyze", 0, 0, NULL }, -{ 1, "Measure &Volume", MNU_VOLUME, C|S|'V',mAna }, -{ 1, "Measure &Area", MNU_AREA, C|S|'A',mAna }, -{ 1, "Show &Interfering Parts", MNU_INTERFERENCE, C|S|'I',mAna }, -{ 1, "Show &Naked Edges", MNU_NAKED_EDGES, C|S|'N',mAna }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "Show Degrees of &Freedom", MNU_SHOW_DOF, C|S|'F',mAna }, -{ 1, NULL, 0, 0, NULL }, -{ 1, "&Trace Point", MNU_TRACE_PT, C|S|'T',mAna }, -{ 1, "&Stop Tracing...", MNU_STOP_TRACING, C|S|'S',mAna }, -{ 1, "Step &Dimension...", MNU_STEP_DIM, C|S|'D',mAna }, +{ 0, "&Analyze", 0, 0, IN, NULL }, +{ 1, "Measure &Volume", MNU_VOLUME, C|S|'V', IN, mAna }, +{ 1, "Measure &Area", MNU_AREA, C|S|'A', IN, mAna }, +{ 1, "Show &Interfering Parts", MNU_INTERFERENCE, C|S|'I', IN, mAna }, +{ 1, "Show &Naked Edges", MNU_NAKED_EDGES, C|S|'N', IN, mAna }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "Show Degrees of &Freedom", MNU_SHOW_DOF, C|S|'F', IN, mAna }, +{ 1, NULL, 0, 0, IN, NULL }, +{ 1, "&Trace Point", MNU_TRACE_PT, C|S|'T', IN, mAna }, +{ 1, "&Stop Tracing...", MNU_STOP_TRACING, C|S|'S', IN, mAna }, +{ 1, "Step &Dimension...", MNU_STEP_DIM, C|S|'D', IN, mAna }, -{ 0, "&Help", 0, 0, NULL }, -{ 1, "&Website / Manual", MNU_WEBSITE, 0, mHelp }, -{ 1, "&About", MNU_ABOUT, 0, mHelp }, +{ 0, "&Help", 0, 0, IN, NULL }, +{ 1, "&Website / Manual", MNU_WEBSITE, 0, IN, mHelp }, +{ 1, "&About", MNU_ABOUT, 0, IN, mHelp }, -{ -1, 0, 0, 0, 0 } +{ -1, 0, 0, 0, IN, 0 } }; #undef DEL @@ -164,6 +167,9 @@ const GraphicsWindow::MenuEntry GraphicsWindow::menu[] = { #undef S #undef C #undef F +#undef IN +#undef IC +#undef IR bool MakeAcceleratorLabel(int accel, char *out) { if(!accel) { @@ -288,7 +294,7 @@ void GraphicsWindow::AnimateOnto(Quaternion quatf, Vector offsetf) { offset = offsetf; InvalidateGraphics(); // If the view screen is open, then we need to refresh it. - SS.later.showTW = true; + SS.ScheduleShowTW(); } void GraphicsWindow::HandlePointForZoomToFit(Vector p, @@ -399,17 +405,17 @@ void GraphicsWindow::MenuView(int id) { switch(id) { case MNU_ZOOM_IN: SS.GW.scale *= 1.2; - SS.later.showTW = true; + SS.ScheduleShowTW(); break; case MNU_ZOOM_OUT: SS.GW.scale /= 1.2; - SS.later.showTW = true; + SS.ScheduleShowTW(); break; case MNU_ZOOM_TO_FIT: SS.GW.ZoomToFit(false); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; case MNU_SHOW_GRID: @@ -442,7 +448,7 @@ void GraphicsWindow::MenuView(int id) { } SS.GW.AnimateOntoWorkplane(); SS.GW.ClearSuper(); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; case MNU_NEAREST_ORTHO: @@ -534,13 +540,13 @@ void GraphicsWindow::MenuView(int id) { case MNU_UNITS_INCHES: SS.viewUnits = SolveSpace::UNIT_INCHES; - SS.later.showTW = true; + SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; case MNU_UNITS_MM: SS.viewUnits = SolveSpace::UNIT_MM; - SS.later.showTW = true; + SS.ScheduleShowTW(); SS.GW.EnsureValidActives(); break; @@ -618,17 +624,17 @@ void GraphicsWindow::EnsureValidActives(void) { ShowTextWindow(SS.GW.showTextWindow); CheckMenuById(MNU_SHOW_TEXT_WND, SS.GW.showTextWindow); -#ifdef HAVE_FLTK +#if defined(HAVE_FLTK) CheckMenuById(MNU_SHOW_MENU_BAR, MenuBarIsVisible()); #endif CheckMenuById(MNU_SHOW_TOOLBAR, SS.showToolbar); CheckMenuById(MNU_PERSPECTIVE_PROJ, SS.usePerspectiveProj); CheckMenuById(MNU_SHOW_GRID, SS.GW.showSnapGrid); -#ifdef HAVE_FLTK_FULLSCREEN +#if defined(HAVE_FLTK_FULLSCREEN) || defined(HAVE_GTK) CheckMenuById(MNU_FULL_SCREEN, FullScreenIsActive()); #endif - if(change) SS.later.showTW = true; + if(change) SS.ScheduleShowTW(); } void GraphicsWindow::SetWorkplaneFreeIn3d(void) { @@ -675,7 +681,7 @@ void GraphicsWindow::DeleteTaggedRequests(void) { // that references it (since the regen code checks for that). SS.GenerateAll(0, INT_MAX); EnsureValidActives(); - SS.later.showTW = true; + SS.ScheduleShowTW(); } Vector GraphicsWindow::SnapToGrid(Vector p) { @@ -739,7 +745,7 @@ void GraphicsWindow::MenuEdit(int id) { SS.GW.MakeSelected(e->h); } InvalidateGraphics(); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } @@ -789,7 +795,7 @@ void GraphicsWindow::MenuEdit(int id) { "selected entities."); } InvalidateGraphics(); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } @@ -882,7 +888,7 @@ void GraphicsWindow::MenuEdit(int id) { case MNU_REGEN_ALL: SS.ReloadAllImported(); SS.GenerateAll(0, INT_MAX); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; default: oops(); @@ -916,13 +922,13 @@ void GraphicsWindow::MenuRequest(int id) { // Align the view with the selected workplane SS.GW.AnimateOntoWorkplane(); SS.GW.ClearSuper(); - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } case MNU_FREE_IN_3D: SS.GW.SetWorkplaneFreeIn3d(); SS.GW.EnsureValidActives(); - SS.later.showTW = true; + SS.ScheduleShowTW(); InvalidateGraphics(); break; @@ -937,7 +943,7 @@ void GraphicsWindow::MenuRequest(int id) { } else { SS.TW.GoToScreen(TextWindow::SCREEN_TANGENT_ARC); SS.GW.ForceTextWindowShown(); - SS.later.showTW = true; + SS.ScheduleShowTW(); } break; @@ -952,7 +958,7 @@ void GraphicsWindow::MenuRequest(int id) { c: SS.GW.pending.operation = id; SS.GW.pending.description = s; - SS.later.showTW = true; + SS.ScheduleShowTW(); break; case MNU_CONSTRUCTION: { @@ -1004,6 +1010,6 @@ void GraphicsWindow::ToggleBool(bool *v) { SS.GenerateAll(); InvalidateGraphics(); - SS.later.showTW = true; + SS.ScheduleShowTW(); } diff --git a/src/group.cpp b/src/group.cpp index 0fc552c7..a0ef4b56 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -243,7 +243,7 @@ void Group::MenuGroup(int id) { gg->Activate(); SS.GW.AnimateOntoWorkplane(); TextWindow::ScreenSelectGroup(0, gg->h.v); - SS.later.showTW = true; + SS.ScheduleShowTW(); } void Group::TransformImportedBy(Vector t, Quaternion q) { @@ -291,8 +291,8 @@ void Group::Activate(void) { SS.GW.showFaces = false; } SS.MarkGroupDirty(h); // for good measure; shouldn't be needed - SS.later.generateAll = true; - SS.later.showTW = true; + SS.ScheduleGenerateAll(); + SS.ScheduleShowTW(); } void Group::Generate(IdList *entity, diff --git a/src/groupmesh.cpp b/src/groupmesh.cpp index 16ef913c..4bd392b3 100644 --- a/src/groupmesh.cpp +++ b/src/groupmesh.cpp @@ -330,7 +330,7 @@ void Group::GenerateShellAndMesh(void) { // for this group. booleanFailed = runningShell.booleanFailed; if(booleanFailed != prevBooleanFailed) { - SS.later.showTW = true; + SS.ScheduleShowTW(); } } else { SMesh prevm, thism; diff --git a/src/gtk/gtkmain.cpp b/src/gtk/gtkmain.cpp new file mode 100644 index 00000000..d06ebc2f --- /dev/null +++ b/src/gtk/gtkmain.cpp @@ -0,0 +1,1511 @@ +//----------------------------------------------------------------------------- +// Our main() function, and GTK3-specific stuff to set up our windows and +// otherwise handle our interface to the operating system. Everything +// outside gtk/... should be standard C++ and OpenGL. +// +// Copyright 2015 +//----------------------------------------------------------------------------- +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_GTK3 +#include +#else +#include +#endif + +#include +#include +#include +#include + +#undef HAVE_STDINT_H /* no thanks, we have our own config.h */ + +#include + +#include "solvespace.h" +#include + +#ifdef HAVE_SPACEWARE +# include +# ifndef SI_APP_FIT_BUTTON +# define SI_APP_FIT_BUTTON 31 +# endif +#endif + +char RecentFile[MAX_RECENT][MAX_PATH]; + +#define GL_CHECK() \ + do { \ + int err = (int)glGetError(); \ + if(err) dbp("%s:%d: glGetError() == 0x%X\n", __FILE__, __LINE__, err); \ + } while (0) + +/* Settings */ + +/* Why not just use GSettings? Two reasons. It doesn't allow to easily see + whether the setting had the default value, and it requires to install + a schema globally. */ +static json_object *settings = NULL; + +static int CnfPrepare(char *path, int pathsz) { + // Refer to http://standards.freedesktop.org/basedir-spec/latest/ + + const char *xdg_home, *home; + xdg_home = getenv("XDG_CONFIG_HOME"); + home = getenv("HOME"); + + char dir[MAX_PATH]; + int dirlen; + if(xdg_home) + dirlen = snprintf(dir, sizeof(dir), "%s/solvespace", xdg_home); + else if(home) + dirlen = snprintf(dir, sizeof(dir), "%s/.config/solvespace", home); + else { + dbp("neither XDG_CONFIG_HOME nor HOME is set"); + return 1; + } + + if(dirlen >= sizeof(dir)) + oops(); + + struct stat st; + if(stat(dir, &st)) { + if(errno == ENOENT) { + if(mkdir(dir, 0777)) { + dbp("cannot mkdir %s: %s", dir, strerror(errno)); + return 1; + } + } else { + dbp("cannot stat %s: %s", dir, strerror(errno)); + return 1; + } + } else if(!S_ISDIR(st.st_mode)) { + dbp("%s is not a directory", dir); + return 1; + } + + int pathlen = snprintf(path, pathsz, "%s/settings.json", dir); + if(pathlen >= pathsz) + oops(); + + return 0; +} + +static void CnfLoad() { + char path[MAX_PATH]; + if(CnfPrepare(path, sizeof(path))) + return; + + if(settings) + json_object_put(settings); // deallocate + + settings = json_object_from_file(path); + if(!settings) { + if(errno != ENOENT) + dbp("cannot load settings: %s", strerror(errno)); + + settings = json_object_new_object(); + } +} + +static void CnfSave() { + char path[MAX_PATH]; + if(CnfPrepare(path, sizeof(path))) + return; + + if(json_object_to_file_ext(path, settings, JSON_C_TO_STRING_PRETTY)) + dbp("cannot save settings: %s", strerror(errno)); +} + +void CnfFreezeInt(uint32_t val, const char *key) { + struct json_object *jval = json_object_new_int(val); + json_object_object_add(settings, key, jval); + CnfSave(); +} + +uint32_t CnfThawInt(uint32_t val, const char *key) { + struct json_object *jval; + if(json_object_object_get_ex(settings, key, &jval)) + return json_object_get_int(jval); + else return val; +} + +void CnfFreezeFloat(float val, const char *key) { + struct json_object *jval = json_object_new_double(val); + json_object_object_add(settings, key, jval); + CnfSave(); +} + +float CnfThawFloat(float val, const char *key) { + struct json_object *jval; + if(json_object_object_get_ex(settings, key, &jval)) + return json_object_get_double(jval); + else return val; +} + +void CnfFreezeString(const char *val, const char *key) { + struct json_object *jval = json_object_new_string(val); + json_object_object_add(settings, key, jval); + CnfSave(); +} + +void CnfThawString(char *val, int valsz, const char *key) { + struct json_object *jval; + if(json_object_object_get_ex(settings, key, &jval)) + snprintf(val, valsz, "%s", json_object_get_string(jval)); +} + +static void CnfFreezeWindowPos(Gtk::Window *win, const char *key) { + int x, y, w, h; + win->get_position(x, y); + win->get_size(w, h); + + char buf[100]; + snprintf(buf, sizeof(buf), "%s_left", key); + CnfFreezeInt(x, buf); + snprintf(buf, sizeof(buf), "%s_top", key); + CnfFreezeInt(y, buf); + snprintf(buf, sizeof(buf), "%s_width", key); + CnfFreezeInt(w, buf); + snprintf(buf, sizeof(buf), "%s_height", key); + CnfFreezeInt(h, buf); + + CnfSave(); +} + +static void CnfThawWindowPos(Gtk::Window *win, const char *key) { + int x, y, w, h; + win->get_position(x, y); + win->get_size(w, h); + + char buf[100]; + snprintf(buf, sizeof(buf), "%s_left", key); + x = CnfThawInt(x, buf); + snprintf(buf, sizeof(buf), "%s_top", key); + y = CnfThawInt(y, buf); + snprintf(buf, sizeof(buf), "%s_width", key); + w = CnfThawInt(w, buf); + snprintf(buf, sizeof(buf), "%s_height", key); + h = CnfThawInt(h, buf); + + win->move(x, y); + win->resize(w, h); +} + +/* Timer */ + +int64_t GetMilliseconds(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return 1000 * (uint64_t) ts.tv_sec + ts.tv_nsec / 1000000; +} + +static bool TimerCallback() { + SS.GW.TimerCallback(); + SS.TW.TimerCallback(); + return false; +} + +void SetTimerFor(int milliseconds) { + Glib::signal_timeout().connect(&TimerCallback, milliseconds); +} + +static bool LaterCallback() { + SS.DoLater(); + return false; +} + +void ScheduleLater() { + Glib::signal_idle().connect(&LaterCallback); +} + +/* GL wrapper */ + +namespace SolveSpacePlatf { +/* Replace this with GLArea when GTK 3.16 is old enough */ +class GlWidget : public Gtk::DrawingArea { +public: + GlWidget() : _xpixmap(0), _glpixmap(0) { + int attrlist[] = { + GLX_RGBA, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_DEPTH_SIZE, 24, + GLX_DOUBLEBUFFER, + None + }; + + _xdisplay = gdk_x11_get_default_xdisplay(); + if(!glXQueryExtension(_xdisplay, NULL, NULL)) { + dbp("OpenGL is not supported"); + oops(); + } + + _xvisual = XDefaultVisual(_xdisplay, gdk_x11_get_default_screen()); + + _xvinfo = glXChooseVisual(_xdisplay, gdk_x11_get_default_screen(), attrlist); + if(!_xvinfo) { + dbp("cannot create glx visual"); + oops(); + } + + _gl = glXCreateContext(_xdisplay, _xvinfo, NULL, true); + } + + ~GlWidget() { + destroy_buffer(); + + glXDestroyContext(_xdisplay, _gl); + + XFree(_xvinfo); + } + +protected: + /* Draw on a GLX pixmap, then read pixels out and draw them on + the Cairo context. Slower, but you get to overlay nice widgets. */ + virtual bool on_draw(const Cairo::RefPtr &cr) { + allocate_buffer(get_allocation()); + + if(!glXMakeCurrent(_xdisplay, _glpixmap, _gl)) + oops(); + + glDrawBuffer(GL_FRONT); + on_gl_draw(); + GL_CHECK(); + + Gdk::Rectangle allocation = get_allocation(); + Cairo::RefPtr surface = + Cairo::XlibSurface::create(_xdisplay, _xpixmap, _xvisual, + allocation.get_width(), allocation.get_height()); + cr->set_source(surface, 0, 0); + cr->paint(); + + if(!glXMakeCurrent(_xdisplay, None, NULL)) + oops(); + + return true; + } + +#ifdef HAVE_GTK2 + virtual bool on_expose_event(GdkEventExpose *event) { + return on_draw(get_window()->create_cairo_context()); + } +#endif + + virtual void on_size_allocate (Gtk::Allocation& allocation) { + destroy_buffer(); + + Gtk::DrawingArea::on_size_allocate(allocation); + } + + virtual void on_gl_draw() = 0; + +private: + Display *_xdisplay; + Visual *_xvisual; + XVisualInfo *_xvinfo; + GLXContext _gl; + Pixmap _xpixmap; + GLXDrawable _glpixmap; + + void destroy_buffer() { + if(_glpixmap) { + glXDestroyGLXPixmap(_xdisplay, _glpixmap); + _glpixmap = 0; + } + + if(_xpixmap) { + XFreePixmap(_xdisplay, _xpixmap); + _xpixmap = 0; + } + } + + void allocate_buffer(Gtk::Allocation allocation) { + if(!_xpixmap) { + _xpixmap = XCreatePixmap(_xdisplay, + XRootWindow(_xdisplay, gdk_x11_get_default_screen()), + allocation.get_width(), allocation.get_height(), 24); + } + + if(!_glpixmap) { + _glpixmap = glXCreateGLXPixmap(_xdisplay, _xvinfo, _xpixmap); + } + } +}; +}; + +/* Editor overlay */ + +namespace SolveSpacePlatf { +class EditorOverlay : public Gtk::Fixed { +public: + EditorOverlay(Gtk::Widget &underlay) : _underlay(underlay) { + add(_underlay); + + Pango::FontDescription desc; + desc.set_family("monospace"); + desc.set_size(7000); +#ifdef HAVE_GTK3 + _entry.override_font(desc); +#else + _entry.modify_font(desc); +#endif + _entry.set_width_chars(30); + _entry.set_no_show_all(true); + add(_entry); + + _entry.signal_activate(). + connect(sigc::mem_fun(this, &EditorOverlay::on_activate)); + } + + void start_editing(int x, int y, const char *val) { + move(_entry, x, y - 4); + _entry.set_text(val); + if(!_entry.is_visible()) { + _entry.show(); + _entry.grab_focus(); + _entry.add_modal_grab(); + } + } + + void stop_editing() { + if(_entry.is_visible()) + _entry.remove_modal_grab(); + _entry.hide(); + } + + bool is_editing() const { + return _entry.is_visible(); + } + + sigc::signal signal_editing_done() { + return _signal_editing_done; + } + + Gtk::Entry &get_entry() { + return _entry; + } + +protected: + virtual bool on_key_press_event(GdkEventKey *event) { + if(event->keyval == GDK_KEY_Escape) { + stop_editing(); + return true; + } + + return false; + } + + virtual void on_size_allocate(Gtk::Allocation& allocation) { + Gtk::Fixed::on_size_allocate(allocation); + + _underlay.size_allocate(allocation); + } + + virtual void on_activate() { + _signal_editing_done(_entry.get_text()); + } + +private: + Gtk::Widget &_underlay; + Gtk::Entry _entry; + sigc::signal _signal_editing_done; +}; +}; + +/* Graphics window */ + +namespace SolveSpacePlatf { +int DeltaYOfScrollEvent(GdkEventScroll *event) { +#ifdef HAVE_GTK3 + int delta_y = event->delta_y; +#else + int delta_y = 0; +#endif + if(delta_y == 0) { + switch(event->direction) { + case GDK_SCROLL_UP: + delta_y = -1; + break; + + case GDK_SCROLL_DOWN: + delta_y = 1; + break; + + default: + /* do nothing */ + return false; + } + } + + return delta_y; +} + +class GraphicsWidget : public GlWidget { +public: + GraphicsWidget() { + set_events(Gdk::POINTER_MOTION_MASK | + Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::BUTTON_MOTION_MASK | + Gdk::SCROLL_MASK | + Gdk::LEAVE_NOTIFY_MASK); + set_double_buffered(true); + } + + void emulate_key_press(GdkEventKey *event) { + on_key_press_event(event); + } + +protected: + virtual bool on_configure_event(GdkEventConfigure *event) { + _w = event->width; + _h = event->height; + + return GlWidget::on_configure_event(event);; + } + + virtual void on_gl_draw() { + SS.GW.Paint(); + } + + virtual bool on_motion_notify_event(GdkEventMotion *event) { + int x, y; + ij_to_xy(event->x, event->y, x, y); + + SS.GW.MouseMoved(x, y, + event->state & GDK_BUTTON1_MASK, + event->state & GDK_BUTTON2_MASK, + event->state & GDK_BUTTON3_MASK, + event->state & GDK_SHIFT_MASK, + event->state & GDK_CONTROL_MASK); + + return true; + } + + virtual bool on_button_press_event(GdkEventButton *event) { + int x, y; + ij_to_xy(event->x, event->y, x, y); + + switch(event->button) { + case 1: + if(event->type == GDK_BUTTON_PRESS) + SS.GW.MouseLeftDown(x, y); + else if(event->type == GDK_2BUTTON_PRESS) + SS.GW.MouseLeftDoubleClick(x, y); + break; + + case 2: + case 3: + SS.GW.MouseMiddleOrRightDown(x, y); + break; + } + + return true; + } + + virtual bool on_button_release_event(GdkEventButton *event) { + int x, y; + ij_to_xy(event->x, event->y, x, y); + + switch(event->button) { + case 1: + SS.GW.MouseLeftUp(x, y); + break; + + case 3: + SS.GW.MouseRightUp(x, y); + break; + } + + return true; + } + + virtual bool on_scroll_event(GdkEventScroll *event) { + int x, y; + ij_to_xy(event->x, event->y, x, y); + + SS.GW.MouseScroll(x, y, -DeltaYOfScrollEvent(event)); + + return true; + } + + virtual bool on_leave_notify_event (GdkEventCrossing*event) { + SS.GW.MouseLeave(); + + return true; + } + + virtual bool on_key_press_event(GdkEventKey *event) { + int chr; + + switch(event->keyval) { + case GDK_KEY_Escape: + chr = GraphicsWindow::ESCAPE_KEY; + break; + + case GDK_KEY_Delete: + chr = GraphicsWindow::DELETE_KEY; + break; + + case GDK_KEY_Tab: + chr = '\t'; + break; + + case GDK_KEY_BackSpace: + case GDK_KEY_Back: + chr = '\b'; + break; + + default: + if(event->keyval >= GDK_KEY_F1 && event->keyval <= GDK_KEY_F12) + chr = GraphicsWindow::FUNCTION_KEY_BASE + (event->keyval - GDK_KEY_F1); + else + chr = gdk_keyval_to_unicode(event->keyval); + } + + if(event->state & GDK_SHIFT_MASK) + chr |= GraphicsWindow::SHIFT_MASK; + if(event->state & GDK_CONTROL_MASK) + chr |= GraphicsWindow::CTRL_MASK; + + if(chr && SS.GW.KeyDown(chr)) + return true; + + return false; + } + +private: + int _w, _h; + void ij_to_xy(int i, int j, int &x, int &y) { + // Convert to xy (vs. ij) style coordinates, + // with (0, 0) at center + x = i - _w / 2; + y = _h / 2 - j; + } +}; + +class GraphicsWindow : public Gtk::Window { +public: + GraphicsWindow() : _overlay(_widget) { + set_default_size(900, 600); + + _box.pack_start(_menubar, false, true); + _box.pack_start(_overlay, true, true); + + add(_box); + + _overlay.signal_editing_done(). + connect(sigc::mem_fun(this, &GraphicsWindow::on_editing_done)); + } + + GraphicsWidget &get_widget() { + return _widget; + } + + EditorOverlay &get_overlay() { + return _overlay; + } + + Gtk::MenuBar &get_menubar() { + return _menubar; + } + + bool is_fullscreen() const { + return _is_fullscreen; + } + +protected: + virtual void on_show() { + Gtk::Window::on_show(); + + CnfThawWindowPos(this, "GraphicsWindow"); + } + + virtual void on_hide() { + CnfFreezeWindowPos(this, "GraphicsWindow"); + + Gtk::Window::on_hide(); + } + + virtual bool on_delete_event(GdkEventAny *event) { + SS.Exit(); + + return true; + } + + virtual bool on_window_state_event(GdkEventWindowState *event) { + _is_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN; + + /* The event arrives too late for the caller of ToggleFullScreen + to notice state change; and it's possible that the WM will + refuse our request, so we can't just toggle the saved state */ + SS.GW.EnsureValidActives(); + + return Gtk::Window::on_window_state_event(event); + } + + virtual void on_editing_done(Glib::ustring value) { + SS.GW.EditControlDone(value.c_str()); + } + +private: + GraphicsWidget _widget; + EditorOverlay _overlay; + Gtk::MenuBar _menubar; + Gtk::VBox _box; + + bool _is_fullscreen; +}; +}; + +SolveSpacePlatf::GraphicsWindow *GW = NULL; + +void GetGraphicsWindowSize(int *w, int *h) { + Gdk::Rectangle allocation = GW->get_widget().get_allocation(); + *w = allocation.get_width(); + *h = allocation.get_height(); +} + +void InvalidateGraphics(void) { + GW->get_widget().queue_draw(); +} + +void PaintGraphics(void) { + GW->get_widget().queue_draw(); + /* Process animation */ + Glib::MainContext::get_default()->iteration(false); +} + +void SetWindowTitle(const char *str) { + GW->set_title(str); +} + +void ToggleFullScreen(void) { + if(GW->is_fullscreen()) + GW->unfullscreen(); + else + GW->fullscreen(); +} + +bool FullScreenIsActive(void) { + return GW->is_fullscreen(); +} + +void ShowGraphicsEditControl(int x, int y, char *val) { + Gdk::Rectangle rect = GW->get_widget().get_allocation(); + + // Convert to ij (vs. xy) style coordinates, + // and compensate for the input widget height due to inverse coord + int i, j; + i = x + rect.get_width() / 2; + j = -y + rect.get_height() / 2 - 24; + + GW->get_overlay().start_editing(i, j, val); +} + +void HideGraphicsEditControl(void) { + GW->get_overlay().stop_editing(); +} + +bool GraphicsEditControlIsVisible(void) { + return GW->get_overlay().is_editing(); +} + +/* TODO: removing menubar breaks accelerators. */ +void ToggleMenuBar(void) { + GW->get_menubar().set_visible(!GW->get_menubar().is_visible()); +} + +bool MenuBarIsVisible(void) { + return GW->get_menubar().is_visible(); +} + +/* Context menus */ + +namespace SolveSpacePlatf { +class ContextMenuItem : public Gtk::MenuItem { +public: + static int choice; + + ContextMenuItem(const Glib::ustring &label, int id, bool mnemonic=false) : + Gtk::MenuItem(label, mnemonic), _id(id) { + } + +protected: + virtual void on_activate() { + Gtk::MenuItem::on_activate(); + + if(has_submenu()) + return; + + choice = _id; + } + + /* Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=695488. + This is used in addition to on_activate() to catch mouse events. + Without on_activate(), it would be impossible to select a menu item + via keyboard. + This selects the item twice in some cases, but we are idempotent. + */ + virtual bool on_button_press_event(GdkEventButton *event) { + if(event->button == 1 && event->type == GDK_BUTTON_PRESS) { + on_activate(); + return true; + } + + return Gtk::MenuItem::on_button_press_event(event); + } + +private: + int _id; +}; + +int ContextMenuItem::choice = 0; +}; + +static Gtk::Menu *context_menu = NULL, *context_submenu = NULL; + +void AddContextMenuItem(const char *label, int id) { + Gtk::MenuItem *menu_item; + if(label) + menu_item = new SolveSpacePlatf::ContextMenuItem(label, id); + else + menu_item = new Gtk::SeparatorMenuItem(); + + if(id == CONTEXT_SUBMENU) { + menu_item->set_submenu(*context_submenu); + context_submenu = NULL; + } + + if(context_submenu) { + context_submenu->append(*menu_item); + } else { + if(!context_menu) + context_menu = new Gtk::Menu; + + context_menu->append(*menu_item); + } +} + +void CreateContextSubmenu(void) { + if(context_submenu) oops(); + + context_submenu = new Gtk::Menu; +} + +int ShowContextMenu(void) { + if(!context_menu) + return -1; + + Glib::RefPtr loop = Glib::MainLoop::create(); + context_menu->signal_deactivate(). + connect(sigc::mem_fun(loop.operator->(), &Glib::MainLoop::quit)); + + SolveSpacePlatf::ContextMenuItem::choice = -1; + + context_menu->show_all(); + context_menu->popup(3, GDK_CURRENT_TIME); + + loop->run(); + + delete context_menu; + context_menu = NULL; + + return SolveSpacePlatf::ContextMenuItem::choice; +} + +/* Main menu */ + +namespace SolveSpacePlatf { +template class MainMenuItem : public MenuItem { +public: + MainMenuItem(const ::GraphicsWindow::MenuEntry &entry) : + MenuItem(), _entry(entry), _synthetic(false) { + Glib::ustring label(_entry.label); + for(int i = 0; i < label.length(); i++) { + if(label[i] == '&') + label.replace(i, 1, "_"); + } + + guint accel_key = 0; + Gdk::ModifierType accel_mods = Gdk::ModifierType(); + switch(_entry.accel) { + case ::GraphicsWindow::DELETE_KEY: + accel_key = GDK_KEY_Delete; + break; + + case ::GraphicsWindow::ESCAPE_KEY: + accel_key = GDK_KEY_Escape; + break; + + default: + accel_key = _entry.accel & ~(::GraphicsWindow::SHIFT_MASK | ::GraphicsWindow::CTRL_MASK); + if(accel_key > ::GraphicsWindow::FUNCTION_KEY_BASE && + accel_key <= ::GraphicsWindow::FUNCTION_KEY_BASE + 12) + accel_key = GDK_KEY_F1 + (accel_key - ::GraphicsWindow::FUNCTION_KEY_BASE - 1); + else + accel_key = gdk_unicode_to_keyval(accel_key); + + if(_entry.accel & ::GraphicsWindow::SHIFT_MASK) + accel_mods |= Gdk::SHIFT_MASK; + if(_entry.accel & ::GraphicsWindow::CTRL_MASK) + accel_mods |= Gdk::CONTROL_MASK; + } + + MenuItem::set_label(label); + MenuItem::set_use_underline(true); + if(!(accel_key & 0x01000000)) + MenuItem::set_accel_key(Gtk::AccelKey(accel_key, accel_mods)); + } + + void set_active(bool checked) { + if(MenuItem::get_active() == checked) + return; + + _synthetic = true; + MenuItem::set_active(checked); + } + +protected: + virtual void on_activate() { + MenuItem::on_activate(); + + if(_synthetic) + _synthetic = false; + else if(!MenuItem::has_submenu() && _entry.fn) + _entry.fn(_entry.id); + } + +private: + const ::GraphicsWindow::MenuEntry &_entry; + bool _synthetic; +}; +}; + +static std::map main_menu_items; + +static void InitMainMenu(Gtk::MenuShell *menu_shell) { + Gtk::MenuItem *menu_item = NULL; + Gtk::MenuShell *levels[5] = {menu_shell, 0}; + + const GraphicsWindow::MenuEntry *entry = &GraphicsWindow::menu[0]; + int current_level = 0; + while(entry->level >= 0) { + if(entry->level > current_level) { + Gtk::Menu *menu = new Gtk::Menu; + menu_item->set_submenu(*menu); + + if(entry->level >= sizeof(levels) / sizeof(levels[0])) + oops(); + + levels[entry->level] = menu; + } + + current_level = entry->level; + + if(entry->label) { + switch(entry->kind) { + case GraphicsWindow::MENU_ITEM_NORMAL: + menu_item = new SolveSpacePlatf::MainMenuItem(*entry); + break; + + case GraphicsWindow::MENU_ITEM_CHECK: + menu_item = new SolveSpacePlatf::MainMenuItem(*entry); + break; + + case GraphicsWindow::MENU_ITEM_RADIO: + SolveSpacePlatf::MainMenuItem *radio_item = + new SolveSpacePlatf::MainMenuItem(*entry); + radio_item->set_draw_as_radio(true); + menu_item = radio_item; + break; + } + } else { + menu_item = new Gtk::SeparatorMenuItem(); + } + + levels[entry->level]->append(*menu_item); + + main_menu_items[entry->id] = menu_item; + + ++entry; + } +} + +void EnableMenuById(int id, bool enabled) { + main_menu_items[id]->set_sensitive(enabled); +} + +static void ActivateMenuById(int id) { + main_menu_items[id]->activate(); +} + +void CheckMenuById(int id, bool checked) { + ((SolveSpacePlatf::MainMenuItem*)main_menu_items[id])-> + set_active(checked); +} + +void RadioMenuById(int id, bool selected) { + CheckMenuById(id, selected); +} + +namespace SolveSpacePlatf { +class RecentMenuItem : public Gtk::MenuItem { +public: + RecentMenuItem(const Glib::ustring& label, int id) : + MenuItem(label), _id(id) { + } + +protected: + virtual void on_activate() { + if(_id >= RECENT_OPEN && _id < (RECENT_OPEN + MAX_RECENT)) + SolveSpace::MenuFile(_id); + else if(_id >= RECENT_IMPORT && _id < (RECENT_IMPORT + MAX_RECENT)) + Group::MenuGroup(_id); + } + +private: + int _id; +}; +}; + +static void RefreshRecentMenu(int id, int base) { + Gtk::MenuItem *recent = static_cast(main_menu_items[id]); + recent->unset_submenu(); + + Gtk::Menu *menu = new Gtk::Menu; + recent->set_submenu(*menu); + + if(std::string(RecentFile[0]).empty()) { + Gtk::MenuItem *placeholder = new Gtk::MenuItem("(no recent files)"); + placeholder->set_sensitive(false); + menu->append(*placeholder); + } else { + for(int i = 0; i < MAX_RECENT; i++) { + if(std::string(RecentFile[i]).empty()) + break; + + SolveSpacePlatf::RecentMenuItem *item = + new SolveSpacePlatf::RecentMenuItem(RecentFile[i], base + i); + menu->append(*item); + } + } + + menu->show_all(); +} + +void RefreshRecentMenus(void) { + RefreshRecentMenu(GraphicsWindow::MNU_OPEN_RECENT, RECENT_OPEN); + RefreshRecentMenu(GraphicsWindow::MNU_GROUP_RECENT, RECENT_IMPORT); +} + +/* Save/load */ + +static void FiltersFromPattern(const char *active, const char *patterns, + Gtk::FileChooser &chooser) { + Glib::ustring uactive = "*." + Glib::ustring(active); + Glib::ustring upatterns = patterns; + +#ifdef HAVE_GTK3 + Glib::RefPtr filter = Gtk::FileFilter::create(); +#else + Gtk::FileFilter *filter = new Gtk::FileFilter; +#endif + Glib::ustring desc = ""; + bool has_name = false, is_active = false; + int last = 0; + for(int i = 0; i <= upatterns.length(); i++) { + if(upatterns[i] == '\t' || upatterns[i] == '\n' || upatterns[i] == '\0') { + Glib::ustring frag = upatterns.substr(last, i - last); + if(!has_name) { + filter->set_name(frag); + has_name = true; + } else { + filter->add_pattern(frag); + if(uactive == frag) + is_active = true; + if(desc == "") + desc = frag; + else + desc += ", " + frag; + } + } else continue; + + if(upatterns[i] == '\n' || upatterns[i] == '\0') { + filter->set_name(filter->get_name() + " (" + desc + ")"); +#ifdef HAVE_GTK3 + chooser.add_filter(filter); + if(is_active) + chooser.set_filter(filter); + + filter = Gtk::FileFilter::create(); +#else + chooser.add_filter(*filter); + if(is_active) + chooser.set_filter(*filter); + + filter = new Gtk::FileFilter(); +#endif + has_name = false; + is_active = false; + desc = ""; + } + + last = i + 1; + } +} + +bool GetOpenFile(char *file, const char *active, const char *patterns) { + Gtk::FileChooserDialog chooser(*GW, "SolveSpace - Open File"); + chooser.set_filename(file); + chooser.add_button("_Cancel", Gtk::RESPONSE_CANCEL); + chooser.add_button("_Open", Gtk::RESPONSE_OK); + + char current_folder[MAX_PATH]; + CnfThawString(current_folder, sizeof(current_folder), "FileChooserPath"); + chooser.set_current_folder(current_folder); + + FiltersFromPattern(active, patterns, chooser); + + if(chooser.run() == Gtk::RESPONSE_OK) { + CnfFreezeString(chooser.get_current_folder().c_str(), "FileChooserPath"); + strcpy(file, chooser.get_filename().c_str()); + return true; + } else { + return false; + } +} + +/* Glib::path_get_basename got /removed/ in 3.0?! Come on */ +static std::string Basename(std::string filename) { + int slash = filename.rfind('/'); + if(slash >= 0) + return filename.substr(slash + 1, filename.length()); + return ""; +} + +static void ChooserFilterChanged(Gtk::FileChooserDialog *chooser) +{ + /* Extract the pattern from the filter. GtkFileFilter doesn't provide + any way to list the patterns, so we extract it from the filter name. + Gross. */ + std::string filter_name = chooser->get_filter()->get_name(); + int lparen = filter_name.find('(') + 1; + int rdelim = filter_name.find(',', lparen); + if(rdelim < 0) + rdelim = filter_name.find(')', lparen); + if(lparen < 0 || rdelim < 0) + oops(); + + std::string extension = filter_name.substr(lparen, rdelim - lparen); + if(extension == "*") + return; + + if(extension.length() > 2 && extension.substr(0, 2) == "*.") + extension = extension.substr(2, extension.length() - 2); + + std::string basename = Basename(chooser->get_filename()); + int dot = basename.rfind('.'); + if(dot >= 0) { + basename.replace(dot + 1, basename.length() - dot - 1, extension); + chooser->set_current_name(basename); + } else { + chooser->set_current_name(basename + "." + extension); + } +} + +bool GetSaveFile(char *file, const char *active, const char *patterns) { + Gtk::FileChooserDialog chooser(*GW, "SolveSpace - Save File", + Gtk::FILE_CHOOSER_ACTION_SAVE); + chooser.set_do_overwrite_confirmation(true); + chooser.add_button("_Cancel", Gtk::RESPONSE_CANCEL); + chooser.add_button("_Save", Gtk::RESPONSE_OK); + + FiltersFromPattern(active, patterns, chooser); + + char current_folder[MAX_PATH]; + CnfThawString(current_folder, sizeof(current_folder), "FileChooserPath"); + chooser.set_current_folder(current_folder); + chooser.set_current_name(std::string("untitled.") + active); + + /* Gtk's dialog doesn't change the extension when you change the filter, + and makes it extremely hard to do so. Gtk is garbage. */ + chooser.property_filter().signal_changed(). + connect(sigc::bind(sigc::ptr_fun(&ChooserFilterChanged), &chooser)); + + if(chooser.run() == Gtk::RESPONSE_OK) { + CnfFreezeString(chooser.get_current_folder().c_str(), "FileChooserPath"); + strcpy(file, chooser.get_filename().c_str()); + return true; + } else { + return false; + } +} + +int SaveFileYesNoCancel(void) { + Glib::ustring message = + "The file has changed since it was last saved.\n" + "Do you want to save the changes?"; + Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, Gtk::MESSAGE_QUESTION, + Gtk::BUTTONS_NONE, /*is_modal*/ true); + dialog.set_title("SolveSpace - Modified File"); + dialog.add_button("_Save", Gtk::RESPONSE_YES); + dialog.add_button("Do_n't save", Gtk::RESPONSE_NO); + dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); + + switch(dialog.run()) { + case Gtk::RESPONSE_YES: + return SAVE_YES; + + case Gtk::RESPONSE_NO: + return SAVE_NO; + + case Gtk::RESPONSE_CANCEL: + default: + return SAVE_CANCEL; + } +} + +/* Text window */ + +namespace SolveSpacePlatf { +class TextWidget : public GlWidget { +public: +#ifdef HAVE_GTK3 + TextWidget(Glib::RefPtr adjustment) : _adjustment(adjustment) { +#else + TextWidget(Gtk::Adjustment* adjustment) : _adjustment(adjustment) { +#endif + set_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | + Gdk::LEAVE_NOTIFY_MASK); + } + + void set_cursor_hand(bool is_hand) { + Glib::RefPtr gdkwin = get_window(); + if(gdkwin) { // returns NULL if not realized + Gdk::CursorType type = is_hand ? Gdk::HAND1 : Gdk::ARROW; +#ifdef HAVE_GTK3 + gdkwin->set_cursor(Gdk::Cursor::create(type)); +#else + gdkwin->set_cursor(Gdk::Cursor(type)); +#endif + } + } + +protected: + virtual void on_gl_draw() { + SS.TW.Paint(); + } + + virtual bool on_motion_notify_event(GdkEventMotion *event) { + SS.TW.MouseEvent(/*leftClick*/ false, + /*leftDown*/ event->state & GDK_BUTTON1_MASK, + event->x, event->y); + + return true; + } + + virtual bool on_button_press_event(GdkEventButton *event) { + SS.TW.MouseEvent(/*leftClick*/ event->type == GDK_BUTTON_PRESS, + /*leftDown*/ event->state & GDK_BUTTON1_MASK, + event->x, event->y); + + return true; + } + + virtual bool on_scroll_event(GdkEventScroll *event) { + _adjustment->set_value(_adjustment->get_value() + + DeltaYOfScrollEvent(event) * _adjustment->get_page_increment()); + + return true; + } + + virtual bool on_leave_notify_event (GdkEventCrossing*event) { + SS.TW.MouseLeave(); + + return true; + } + +private: +#ifdef HAVE_GTK3 + Glib::RefPtr _adjustment; +#else + Gtk::Adjustment *_adjustment; +#endif +}; + +class TextWindow : public Gtk::Window { +public: + TextWindow() : _scrollbar(), _widget(_scrollbar.get_adjustment()), + _box(), _overlay(_widget) { + set_keep_above(true); + set_type_hint(Gdk::WINDOW_TYPE_HINT_UTILITY); + set_skip_taskbar_hint(true); + set_skip_pager_hint(true); + set_title("SolveSpace - Browser"); + set_default_size(420, 300); + + _box.pack_start(_overlay, true, true); + _box.pack_start(_scrollbar, false, true); + add(_box); + + _scrollbar.get_adjustment()->signal_value_changed(). + connect(sigc::mem_fun(this, &TextWindow::on_scrollbar_value_changed)); + + _overlay.signal_editing_done(). + connect(sigc::mem_fun(this, &TextWindow::on_editing_done)); + + _overlay.get_entry().signal_motion_notify_event(). + connect(sigc::mem_fun(this, &TextWindow::on_editor_motion_notify_event)); + _overlay.get_entry().signal_button_press_event(). + connect(sigc::mem_fun(this, &TextWindow::on_editor_button_press_event)); + } + + Gtk::VScrollbar &get_scrollbar() { + return _scrollbar; + } + + TextWidget &get_widget() { + return _widget; + } + + EditorOverlay &get_overlay() { + return _overlay; + } + +protected: + virtual void on_show() { + Gtk::Window::on_show(); + + CnfThawWindowPos(this, "TextWindow"); + } + + virtual void on_hide() { + CnfFreezeWindowPos(this, "TextWindow"); + + Gtk::Window::on_hide(); + } + + virtual bool on_delete_event(GdkEventAny *event) { + /* trigger the action and ignore the request */ + ::GraphicsWindow::MenuView(::GraphicsWindow::MNU_SHOW_TEXT_WND); + + return false; + } + + virtual void on_scrollbar_value_changed() { + SS.TW.ScrollbarEvent(_scrollbar.get_adjustment()->get_value()); + } + + virtual void on_editing_done(Glib::ustring value) { + SS.TW.EditControlDone(value.c_str()); + } + + virtual bool on_editor_motion_notify_event(GdkEventMotion *event) { + return _widget.event((GdkEvent*) event); + } + + virtual bool on_editor_button_press_event(GdkEventButton *event) { + return _widget.event((GdkEvent*) event); + } + +private: + Gtk::VScrollbar _scrollbar; + TextWidget _widget; + EditorOverlay _overlay; + Gtk::HBox _box; +}; +}; + +SolveSpacePlatf::TextWindow *TW = NULL; + +void ShowTextWindow(bool visible) { + if(visible) + TW->show(); + else + TW->hide(); +} + +void GetTextWindowSize(int *w, int *h) { + Gdk::Rectangle allocation = TW->get_widget().get_allocation(); + *w = allocation.get_width(); + *h = allocation.get_height(); +} + +void InvalidateText(void) { + TW->get_widget().queue_draw(); +} + +void MoveTextScrollbarTo(int pos, int maxPos, int page) { + TW->get_scrollbar().get_adjustment()->configure(pos, 0, maxPos, 1, 10, page); +} + +void SetMousePointerToHand(bool is_hand) { + TW->get_widget().set_cursor_hand(is_hand); +} + +void ShowTextEditControl(int x, int y, char *val) { + TW->get_overlay().start_editing(x, y, val); +} + +void HideTextEditControl(void) { + TW->get_overlay().stop_editing(); + GW->raise(); +} + +bool TextEditControlIsVisible(void) { + return TW->get_overlay().is_editing(); +} + +/* Miscellanea */ + + +void DoMessageBox(const char *message, int rows, int cols, bool error) { + Gtk::MessageDialog dialog(*GW, message, /*use_markup*/ true, + error ? Gtk::MESSAGE_ERROR : Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, + /*is_modal*/ true); + dialog.set_title(error ? "SolveSpace - Error" : "SolveSpace - Message"); + dialog.run(); +} + +void OpenWebsite(const char *url) { + gtk_show_uri(Gdk::Screen::get_default()->gobj(), url, GDK_CURRENT_TIME, NULL); +} + +/* fontconfig is already initialized by GTK */ +void LoadAllFontFiles(void) { + FcPattern *pat = FcPatternCreate(); + FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); + FcFontSet *fs = FcFontList(0, pat, os); + + for(int i = 0; i < fs->nfont; i++) { + FcChar8 *filename = FcPatternFormat(fs->fonts[i], (const FcChar8*) "%{file}"); + Glib::ustring ufilename = (char*) filename; + if(ufilename.length() > 4 && + ufilename.substr(ufilename.length() - 4, 4).lowercase() == ".ttf") { + TtfFont tf; + ZERO(&tf); + strcpy(tf.fontFile, (char*) filename); + SS.fonts.l.Add(&tf); + } + FcStrFree(filename); + } + + FcFontSetDestroy(fs); + FcObjectSetDestroy(os); + FcPatternDestroy(pat); +} + +/* Space Navigator support */ + +#ifdef HAVE_SPACEWARE +static GdkFilterReturn GdkSpnavFilter(GdkXEvent *gxevent, GdkEvent *event, gpointer data) { + XEvent *xevent = (XEvent*) gxevent; + + spnav_event sev; + if(!spnav_x11_event(xevent, &sev)) + return GDK_FILTER_CONTINUE; + + switch(sev.type) { + case SPNAV_EVENT_MOTION: + SS.GW.SpaceNavigatorMoved( + (double)sev.motion.x, + (double)sev.motion.y, + (double)sev.motion.z * -1.0, + (double)sev.motion.rx * 0.001, + (double)sev.motion.ry * 0.001, + (double)sev.motion.rz * -0.001, + xevent->xmotion.state & ShiftMask); + break; + + case SPNAV_EVENT_BUTTON: + if(!sev.button.press && sev.button.bnum == SI_APP_FIT_BUTTON) { + SS.GW.SpaceNavigatorButtonUp(); + } + break; + } + + return GDK_FILTER_REMOVE; +} +#endif + +/* Application lifecycle */ + +void ExitNow(void) { + GW->hide(); + TW->hide(); +} + +int main(int argc, char** argv) { + /* If we don't call this, gtk_init will set the C standard library + locale, and printf will format floats using ",". We will then + fail to parse these. Also, many text window lines will become + ambiguous. */ + gtk_disable_setlocale(); + + Gtk::Main main(argc, argv); + +#ifdef HAVE_SPACEWARE + gdk_window_add_filter(NULL, GdkSpnavFilter, NULL); +#endif + + CnfLoad(); + + TW = new SolveSpacePlatf::TextWindow; + GW = new SolveSpacePlatf::GraphicsWindow; + InitMainMenu(&GW->get_menubar()); + GW->get_menubar().accelerate(*TW); + + TW->show_all(); + GW->show_all(); + + if(argc >= 2) { + if(argc > 2) { + std::cerr << "Only the first file passed on command line will be opened." + << std::endl; + } + + SS.Init(argv[1]); + } else { + SS.Init(""); + } + + main.run(*GW); + + delete GW; + delete TW; + + SK.Clear(); + SS.Clear(); + + return 0; +} diff --git a/src/modify.cpp b/src/modify.cpp index c2cba6df..0a1847f0 100644 --- a/src/modify.cpp +++ b/src/modify.cpp @@ -403,7 +403,7 @@ void GraphicsWindow::MakeTangentArc(void) { DeleteTaggedRequests(); } - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } hEntity GraphicsWindow::SplitLine(hEntity he, Vector pinter) { @@ -647,6 +647,6 @@ void GraphicsWindow::SplitLinesOrCurves(void) { sbla.Clear(); sblb.Clear(); ClearSelection(); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } diff --git a/src/mouse.cpp b/src/mouse.cpp index 235120fe..e968147a 100644 --- a/src/mouse.cpp +++ b/src/mouse.cpp @@ -159,7 +159,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { if(havePainted) { - SS.later.showTW = true; + SS.ScheduleShowTW(); } } InvalidateGraphics(); @@ -455,7 +455,7 @@ void GraphicsWindow::MouseMoved(double x, double y, bool leftDown, void GraphicsWindow::ClearPending(void) { pending.points.Clear(); ZERO(&pending); - SS.later.showTW = true; + SS.ScheduleShowTW(); } void GraphicsWindow::MouseMiddleOrRightDown(double x, double y) { @@ -511,7 +511,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { if(!hover.IsEmpty()) { MakeSelected(&hover); - SS.later.showTW = true; + SS.ScheduleShowTW(); } GroupSelection(); @@ -664,7 +664,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { SS.TW.GoToScreen(TextWindow::SCREEN_GROUP_INFO); SS.TW.shown.group = hg; - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } @@ -684,7 +684,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { SS.TW.GoToScreen(TextWindow::SCREEN_STYLE_INFO); SS.TW.shown.style = hs; - SS.later.showTW = true; + SS.ScheduleShowTW(); break; } @@ -709,7 +709,7 @@ void GraphicsWindow::MouseRightUp(double x, double y) { } context.active = false; - SS.later.showTW = true; + SS.ScheduleShowTW(); } hRequest GraphicsWindow::AddRequest(int type) { @@ -979,7 +979,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { r->extraPoints -= 2; // And we're done. SS.MarkGroupDirty(r->group); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); ClearPending(); break; } @@ -1060,7 +1060,7 @@ void GraphicsWindow::MouseLeftDown(double mx, double my) { break; } - SS.later.showTW = true; + SS.ScheduleShowTW(); InvalidateGraphics(); } @@ -1227,9 +1227,9 @@ void GraphicsWindow::MouseScroll(double x, double y, int delta) { if(delta > 0) { scale *= 1.2; - } else { + } else if(delta < 0) { scale /= 1.2; - } + } else return; double rightf = x/scale - offsetRight; double upf = y/scale - offsetUp; @@ -1239,7 +1239,7 @@ void GraphicsWindow::MouseScroll(double x, double y, int delta) { if(SS.TW.shown.screen == TextWindow::SCREEN_EDIT_VIEW) { if(havePainted) { - SS.later.showTW = true; + SS.ScheduleShowTW(); } } havePainted = false; @@ -1303,7 +1303,7 @@ void GraphicsWindow::SpaceNavigatorMoved(double tx, double ty, double tz, lastSpaceNavigatorTime = now; lastSpaceNavigatorGroup = g->h; SS.MarkGroupDirty(g->h); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } else { // Apply the transformation to the view of the everything. The // x and y components are translation; but z component is scale, diff --git a/src/solvespace.cpp b/src/solvespace.cpp index 40f27b11..ad24c565 100644 --- a/src/solvespace.cpp +++ b/src/solvespace.cpp @@ -9,7 +9,7 @@ SolveSpace SS; Sketch SK; -void SolveSpace::Init(char *cmdLine) { +void SolveSpace::Init(const char *cmdLine) { SS.tangentArcRadius = 10.0; // Then, load the registry settings. @@ -187,6 +187,18 @@ void SolveSpace::Exit(void) { ExitNow(); } +void SolveSpace::ScheduleGenerateAll() { + if(!later.scheduled) ScheduleLater(); + later.scheduled = true; + later.generateAll = true; +} + +void SolveSpace::ScheduleShowTW() { + if(!later.scheduled) ScheduleLater(); + later.scheduled = true; + later.showTW = true; +} + void SolveSpace::DoLater(void) { if(later.generateAll) GenerateAll(); if(later.showTW) TW.Show(); @@ -284,7 +296,7 @@ void SolveSpace::AfterNewFile(void) { GW.ZoomToFit(true); GenerateAll(0, INT_MAX); - later.showTW = true; + SS.ScheduleShowTW(); // Then zoom to fit again, to fit the triangles GW.ZoomToFit(false); @@ -295,7 +307,7 @@ void SolveSpace::AfterNewFile(void) { UpdateWindowTitle(); } -void SolveSpace::RemoveFromRecentList(char *file) { +void SolveSpace::RemoveFromRecentList(const char *file) { int src, dest; dest = 0; for(src = 0; src < MAX_RECENT; src++) { @@ -307,7 +319,7 @@ void SolveSpace::RemoveFromRecentList(char *file) { while(dest < MAX_RECENT) strcpy(RecentFile[dest++], ""); RefreshRecentMenus(); } -void SolveSpace::AddToRecentList(char *file) { +void SolveSpace::AddToRecentList(const char *file) { RemoveFromRecentList(file); int src; @@ -507,7 +519,7 @@ void SolveSpace::MenuAnalyze(int id) { // so force that to be shown. SS.GW.ForceTextWindowShown(); - SS.later.showTW = true; + SS.ScheduleShowTW(); SS.GW.ClearSelection(); } else { Error("Constraint must have a label, and must not be " @@ -738,7 +750,12 @@ void SolveSpace::MenuHelp(int id) { "There is NO WARRANTY, to the extent permitted by\n" "law. For details, visit http://gnu.org/licenses/\n" "\n" -"\xa9 2008-2013 Jonathan Westhues and other authors.\n" +#ifdef WIN32 +"\xa9 " +#else +"© " +#endif + "2008-2013 Jonathan Westhues and other authors.\n" ); break; diff --git a/src/solvespace.h b/src/solvespace.h index 29d22b7b..bf452271 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -131,7 +131,7 @@ void RefreshRecentMenus(void); #define SAVE_CANCEL (0) int SaveFileYesNoCancel(void); -#ifdef HAVE_FLTK +#if defined(HAVE_FLTK) // Selection pattern format for FLTK's file chooser classes: // "PNG File\t*.png\n" // "JPEG File\t*.{jpg,jpeg}\n" @@ -139,6 +139,14 @@ int SaveFileYesNoCancel(void); # define PAT1(desc,e1) desc "\t*." e1 "\n" # define PAT2(desc,e1,e2) desc "\t*.{" e1 "," e2 "}\n" # define ENDPAT "All Files\t*" +#elif defined(HAVE_GTK) + // Selection pattern format to be parsed by GTK3 glue code: + // "PNG File\t*.png\n" + // "JPEG File\t*.jpg\t*.jpeg\n" + // "All Files\t*" +# define PAT1(desc,e1) desc "\t*." e1 "\n" +# define PAT2(desc,e1,e2) desc "\t*." e1 "\t*." e2 "\n" +# define ENDPAT "All Files\t*" #else // Selection pattern format for Win32's OPENFILENAME.lpstrFilter: // "PNG File (*.png)\0*.png\0" @@ -231,6 +239,7 @@ void SetWindowTitle(const char *str); void SetMousePointerToHand(bool yes); void DoMessageBox(const char *str, int rows, int cols, bool error); void SetTimerFor(int milliseconds); +void ScheduleLater(); void ExitNow(void); void CnfFreezeString(const char *str, const char *name); @@ -522,7 +531,7 @@ public: static double MmToPts(double mm); - static VectorFileWriter *ForFile(char *file); + static VectorFileWriter *ForFile(const char *file); void Output(SBezierLoopSetSet *sblss, SMesh *sm); @@ -775,15 +784,15 @@ public: bool tangentArcDeleteOld; // The platform-dependent code calls this before entering the msg loop - void Init(char *cmdLine); + void Init(const char *cmdLine); void Exit(void); // File load/save routines, including the additional files that get // loaded when we have import groups. FILE *fh; void AfterNewFile(void); - static void RemoveFromRecentList(char *file); - static void AddToRecentList(char *file); + static void RemoveFromRecentList(const char *file); + static void AddToRecentList(const char *file); char saveFile[MAX_PATH]; bool fileLoadError; bool unsaved; @@ -811,18 +820,18 @@ public: void UpdateWindowTitle(void); void ClearExisting(void); void NewFile(void); - bool SaveToFile(char *filename); - bool LoadFromFile(char *filename); - bool LoadEntitiesFromFile(char *filename, EntityList *le, - SMesh *m, SShell *sh); + bool SaveToFile(const char *filename); + bool LoadFromFile(const char *filename); + bool LoadEntitiesFromFile(const char *filename, EntityList *le, + SMesh *m, SShell *sh); void ReloadAllImported(void); // And the various export options - void ExportAsPngTo(char *file); - void ExportMeshTo(char *file); + void ExportAsPngTo(const char *file); + void ExportMeshTo(const char *file); void ExportMeshAsStlTo(FILE *f, SMesh *sm); void ExportMeshAsObjTo(FILE *f, SMesh *sm); - void ExportViewOrWireframeTo(char *file, bool wireframe); - void ExportSectionTo(char *file); + void ExportViewOrWireframeTo(const char *file, bool wireframe); + void ExportSectionTo(const char *file); void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl, VectorFileWriter *out); void ExportLinesAndMesh(SEdgeList *sel, SBezierList *sbl, SMesh *sm, @@ -905,9 +914,12 @@ public: bool allConsistent; struct { + bool scheduled; bool showTW; bool generateAll; } later; + void ScheduleShowTW(); + void ScheduleGenerateAll(); void DoLater(void); static void MenuHelp(int id); diff --git a/src/style.cpp b/src/style.cpp index 13d1bb8e..8810a2b3 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -167,12 +167,12 @@ void Style::AssignSelectionToStyle(uint32_t v) { SS.GW.ClearSelection(); InvalidateGraphics(); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); // 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; + SS.ScheduleShowTW(); } //----------------------------------------------------------------------------- @@ -424,7 +424,7 @@ err: if(png_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL); if(f) fclose(f); } - SS.later.showTW = true; + SS.ScheduleShowTW(); } void TextWindow::ScreenChangeBackgroundImageScale(int link, uint32_t v) { diff --git a/src/textscreens.cpp b/src/textscreens.cpp index 6e5d5d59..0e0af1e4 100644 --- a/src/textscreens.cpp +++ b/src/textscreens.cpp @@ -78,7 +78,7 @@ void TextWindow::ReportHowGroupSolved(hGroup hg) { SS.GW.ClearSuper(); SS.TW.GoToScreen(SCREEN_GROUP_SOLVE_INFO); SS.TW.shown.group.v = hg.v; - SS.later.showTW = true; + SS.ScheduleShowTW(); } void TextWindow::ScreenHowGroupSolved(int link, uint32_t v) { if(SS.GW.activeGroup.v != v) { @@ -650,7 +650,7 @@ void TextWindow::EditControlDone(const char *s) { } SS.MarkGroupDirty(g->h); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } break; } @@ -675,7 +675,7 @@ void TextWindow::EditControlDone(const char *s) { Group *g = SK.GetGroup(edit.group); g->scale = ev; SS.MarkGroupDirty(g->h); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } } break; @@ -690,7 +690,7 @@ void TextWindow::EditControlDone(const char *s) { g->color = RGBf(rgb.x, rgb.y, rgb.z); SS.MarkGroupDirty(g->h); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); SS.GW.ClearSuper(); } else { Error("Bad format: specify color as r, g, b"); @@ -703,7 +703,7 @@ void TextWindow::EditControlDone(const char *s) { if(r) { r->str.strcpy(s); SS.MarkGroupDirty(r->group); - SS.later.generateAll = true; + SS.ScheduleGenerateAll(); } break; } @@ -748,7 +748,7 @@ void TextWindow::EditControlDone(const char *s) { } } InvalidateGraphics(); - SS.later.showTW = true; + SS.ScheduleShowTW(); if(!edit.showAgain) { HideEditControl(); diff --git a/src/textwin.cpp b/src/textwin.cpp index db91e528..10554552 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -90,7 +90,7 @@ void TextWindow::ShowEditControlWithColorPicker(int halfRow, int col, RgbColor r char str[1024]; sprintf(str, "%.2f, %.2f, %.2f", rgb.redF(), rgb.greenF(), rgb.blueF()); - SS.later.showTW = true; + SS.ScheduleShowTW(); editControl.colorPicker.show = true; editControl.colorPicker.rgb = rgb; diff --git a/src/ui.h b/src/ui.h index 9c307008..ecda5e33 100644 --- a/src/ui.h +++ b/src/ui.h @@ -427,12 +427,18 @@ public: SHIFT_MASK = 0x100, CTRL_MASK = 0x200 }; + enum MenuItemKind { + MENU_ITEM_NORMAL = 0, + MENU_ITEM_CHECK, + MENU_ITEM_RADIO + }; typedef struct { - int level; // 0 == on menu bar, 1 == one level down - const char *label; // or NULL for a separator - int id; // unique ID - int accel; // keyboard accelerator - MenuHandler *fn; + int level; // 0 == on menu bar, 1 == one level down + const char *label; // or NULL for a separator + int id; // unique ID + int accel; // keyboard accelerator + MenuItemKind kind; + MenuHandler *fn; } MenuEntry; static const MenuEntry menu[]; static void MenuView(int id); diff --git a/src/undoredo.cpp b/src/undoredo.cpp index 7ce1b6b5..126dc5d9 100644 --- a/src/undoredo.cpp +++ b/src/undoredo.cpp @@ -128,7 +128,7 @@ void SolveSpace::PopOntoCurrentFrom(UndoStack *uk) { SS.TW.ClearSuper(); SS.ReloadAllImported(); SS.GenerateAll(0, INT_MAX); - later.showTW = true; + SS.ScheduleShowTW(); } void SolveSpace::UndoClearStack(UndoStack *uk) { diff --git a/src/unix/unixutil.cpp b/src/unix/unixutil.cpp index 7326f306..90665627 100644 --- a/src/unix/unixutil.cpp +++ b/src/unix/unixutil.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "solvespace.h" @@ -34,6 +35,13 @@ void GetAbsoluteFilename(char *file) strcpy(file, expanded); } +int64_t GetUnixTime(void) +{ + time_t ret; + time(&ret); + return (int64_t)ret; +} + //----------------------------------------------------------------------------- // A separate heap, on which we allocate expressions. Maybe a bit faster, // since fragmentation is less of a concern, and it also makes it possible diff --git a/src/win32/w32main.cpp b/src/win32/w32main.cpp index b58ad457..d21f6f42 100644 --- a/src/win32/w32main.cpp +++ b/src/win32/w32main.cpp @@ -240,6 +240,10 @@ void SetTimerFor(int milliseconds) SetTimer(GraphicsWnd, 1, milliseconds, TimerCallback); } +void ScheduleLater() +{ +} + static void GetWindowSize(HWND hwnd, int *w, int *h) { RECT r;