From c60e3dd34ebfbc7ae95b4aadacef38876b518baf Mon Sep 17 00:00:00 2001 From: Daniel Richard G Date: Mon, 28 Oct 2013 01:28:42 -0400 Subject: [PATCH] Initial Autotools and FLTK support With this commit, SolveSpace gains an Autotools build system and a new platform-dependent backend implemented using the FLTK GUI toolkit. These will allow the application to be built and run on Linux and other Unix-like operating systems, and prospectively, MacOS X. A number of new files have been added: * Makefile.am: Automake makefile template; this contains some experimental support for MinGW and MSVC++ builds that needs further development * ac-aux/ax_fltk.m4: Autoconf M4 macro to locate and query the system's installation of FLTK; this will eventually be contributed to the GNU Autoconf Archive * autogen.sh: Script to bootstrap the Autotools build system, usually for a tree just checked out from source control * configure.ac: Source for the Autoconf configure script; note that this file specifies a version of 2.1, near the top * fltk/fltkmain.cpp: Main FLTK backend implementation * fltk/fltkutil.cpp: Utility functions for the FLTK backend * fltk/xFl_Gl_Window_Group.{H,cxx}: Implementation of a new Fl_Gl_Window_Group widget for FLTK, needed to facilitate drawing FLTK widgets on top of OpenGL graphics as SolveSpace does. This has been submitted to the FLTK project for (hopefully) eventual upstream inclusion: http://www.fltk.org/str.php?L2992 The following minor changes are also a part of this commit: * Makefile.msvc: Define PACKAGE_VERSION=2.1 for the benefit of solvespace.cpp in MSVC++ builds * solvespace.cpp: In the About dialog text, use PACKAGE_VERSION rather than hard-coding the version of the program * solvespace.h: Don't define the C99 integer types if HAVE_C99_INTEGER_TYPES is defined, to facilitate MinGW builds --- Makefile.am | 246 +++++++ Makefile.msvc | 2 +- ac-aux/ax_fltk.m4 | 179 +++++ autogen.sh | 10 + configure.ac | 136 ++++ fltk/fltkmain.cpp | 1279 ++++++++++++++++++++++++++++++++++ fltk/fltkutil.cpp | 107 +++ fltk/xFl_Gl_Window_Group.H | 103 +++ fltk/xFl_Gl_Window_Group.cxx | 292 ++++++++ solvespace.cpp | 2 +- solvespace.h | 2 +- 11 files changed, 2355 insertions(+), 3 deletions(-) create mode 100644 Makefile.am create mode 100644 ac-aux/ax_fltk.m4 create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 fltk/fltkmain.cpp create mode 100644 fltk/fltkutil.cpp create mode 100644 fltk/xFl_Gl_Window_Group.H create mode 100644 fltk/xFl_Gl_Window_Group.cxx diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 00000000..a73df992 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,246 @@ +## Makefile.am + +ACLOCAL_AMFLAGS = -I ac-aux + +AM_CPPFLAGS = $(FLTK_CXXFLAGS) + +if WIN32 +AM_CPPFLAGS += \ + -I$(srcdir)/extlib/libpng \ + -I$(srcdir)/extlib/si \ + -I$(srcdir)/extlib/zlib +endif + +bin_PROGRAMS = solvespace + +icons_src = \ + icons.h \ + icons-proto.h + +BUILT_SOURCES = $(icons_src) + +solvespace_SOURCES = \ + $(icons_src) \ + bsp.cpp \ + clipboard.cpp \ + confscreen.cpp \ + constraint.cpp \ + constrainteq.cpp \ + describescreen.cpp \ + draw.cpp \ + drawconstraint.cpp \ + drawentity.cpp \ + dsc.h \ + entity.cpp \ + export.cpp \ + exportstep.cpp \ + exportvector.cpp \ + expr.h \ + expr.cpp \ + file.cpp \ + generate.cpp \ + glhelper.cpp \ + graphicswin.cpp \ + group.cpp \ + groupmesh.cpp \ + mesh.cpp \ + modify.cpp \ + mouse.cpp \ + polygon.h \ + polygon.cpp \ + request.cpp \ + sketch.h \ + solvespace.h \ + solvespace.cpp \ + style.cpp \ + system.cpp \ + textscreens.cpp \ + textwin.cpp \ + toolbar.cpp \ + ttf.cpp \ + ui.h \ + undoredo.cpp \ + util.cpp \ + view.cpp \ + srf/boolean.cpp \ + srf/curve.cpp \ + srf/merge.cpp \ + srf/ratpoly.cpp \ + srf/raycast.cpp \ + srf/surface.h \ + srf/surface.cpp \ + srf/surfinter.cpp \ + srf/triangulate.cpp + +if HAVE_FLTK +solvespace_SOURCES += \ + fltk/xFl_Gl_Window_Group.H \ + fltk/xFl_Gl_Window_Group.cxx \ + fltk/fltkmain.cpp \ + fltk/fltkutil.cpp + +solvespace_LDADD = $(FLTK_LDSTATICFLAGS) -lGLU +endif + +if WIN32 +solvespace_SOURCES += \ + win32/freeze.h \ + win32/freeze.cpp \ + win32/w32main.cpp \ + win32/w32util.cpp + +if MINGW +solvespace_LDFLAGS = \ + -llibpng \ + -lzlib \ + -luser32 -lgdi32 -lcomctl32 -ladvapi32 -lshell32 \ + -lopengl32 -lglu32 +else +solvespace_LDFLAGS = -link \ + -libpath:$(srcdir)/extlib/libpng libpng.lib \ + -libpath:$(srcdir)/extlib/zlib zlib.lib \ + user32.lib gdi32.lib comctl32.lib advapi32.lib shell32.lib \ + opengl32.lib glu32.lib +endif # MINGW +endif # WIN32 + +icons = \ + icon.ico \ + icons/angle.png \ + icons/arc.png \ + icons/assemble.png \ + icons/bezier.png \ + icons/char-0-check-false.png \ + icons/char-1-check-true.png \ + icons/char-2-radio-false.png \ + icons/char-3-radio-true.png \ + icons/circle.png \ + icons/constraint.png \ + icons/construction.png \ + icons/edges.png \ + icons/equal.png \ + icons/extrude.png \ + icons/faces.png \ + icons/hidden-lines.png \ + icons/horiz.png \ + icons/in3d.png \ + icons/length.png \ + icons/line.png \ + icons/mesh.png \ + icons/normal.png \ + icons/ontoworkplane.png \ + icons/other-supp.png \ + icons/parallel.png \ + icons/perpendicular.png \ + icons/point.png \ + icons/pointonx.png \ + icons/rectangle.png \ + icons/ref.png \ + icons/same-orientation.png \ + icons/shaded.png \ + icons/sketch-in-3d.png \ + icons/sketch-in-plane.png \ + icons/step-rotate.png \ + icons/step-translate.png \ + icons/symmetric.png \ + icons/tangent-arc.png \ + icons/text.png \ + icons/trim.png \ + icons/vert.png \ + icons/workplane.png + +tables = \ + bitmapextra.table.h \ + bitmapfont.table.h \ + font.table.h + +exposed = \ + exposed/CDemo.c \ + exposed/DOC.txt \ + exposed/Makefile \ + exposed/VbDemo.vb \ + exposed/lib.cpp \ + exposed/slvs.h + +EXTRA_DIST = \ + $(icons) \ + $(tables) \ + $(exposed) \ + COPYING.txt \ + Makefile.msvc \ + extlib/build-fltk.sh \ + png2c.pl \ + pngchar2c.pl \ + tools/Makefile \ + tools/ttf2c.cpp \ + wishlist.txt \ + win32/manifest.xml \ + win32/resource.rc + +optional_dist = \ + extlib/libpng/png.h \ + extlib/libpng/pngconf.h \ + extlib/libpng/pnglibconf.h \ + extlib/libpng/libpng.lib \ + extlib/si/si.h \ + extlib/si/siSync.h \ + extlib/si/siSyncPriv.h \ + extlib/si/siapp.h \ + extlib/si/spwdata.h \ + extlib/si/spwerror.h \ + extlib/si/spwmacro.h \ + extlib/si/siapp.lib \ + extlib/zlib/zconf.h \ + extlib/zlib/zlib.h \ + extlib/zlib/zutil.h \ + extlib/zlib/zlib.lib \ + extlib/fltk-1.3.2-source.tar.gz + +dist-hook: + for file in $(optional_dist); do \ + test -f $(srcdir)/$$file || continue; \ + dir=`dirname $$file`; \ + test -d $(distdir)/$$dir || mkdir $(distdir)/$$dir || exit; \ + cp -p $(srcdir)/$$file $(distdir)/$$file || exit; \ + done + @if fgrep '/DPACKAGE_VERSION="\"$(PACKAGE_VERSION)\""' $(srcdir)/Makefile.msvc >/dev/null; \ + then :; \ + else \ + echo 'error: /DPACKAGE_VERSION flag in Makefile.msvc is out-of-date'; \ + echo '(current package version is $(PACKAGE_VERSION))'; \ + exit 1; \ + fi + +if MAINTAINER_MODE + +icons.h: $(icons) $(srcdir)/png2c.pl + $(PERL) $(srcdir)/png2c.pl $@ icons-proto.h $(srcdir) + +icons-proto.h: icons.h + @exit 0 + +bitmapextra.table.h: $(icons) $(srcdir)/pngchar2c.pl + $(PERL) $(srcdir)/pngchar2c.pl $(srcdir) >$@.tmp + mv -f $@.tmp $@ + +endif # MAINTAINER_MODE + +run-valgrind: solvespace$(EXEEXT) + @test -z "$$VALGRIND_OPTS" || echo VALGRIND_OPTS = $$VALGRIND_OPTS + valgrind \ + --tool=memcheck \ + --verbose \ + --track-fds=yes \ + --log-file=vg.%p.out \ + --num-callers=50 \ + --error-limit=no \ + --read-var-info=yes \ + --leak-check=full \ + --leak-resolution=high \ + --show-reachable=yes \ + --track-origins=yes \ + --malloc-fill=0xac \ + --free-fill=0xde \ + ./solvespace$(EXEEXT) + +## end Makefile.am diff --git a/Makefile.msvc b/Makefile.msvc index 2848db76..e1fd85f5 100644 --- a/Makefile.msvc +++ b/Makefile.msvc @@ -3,7 +3,7 @@ HAVE_SPACEWARE_INPUT = 1 -DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32 +DEFINES = /D_WIN32_WINNT=0x500 /DISOLATION_AWARE_ENABLED /D_WIN32_IE=0x500 /DWIN32_LEAN_AND_MEAN /DWIN32 /DPACKAGE_VERSION="\"2.1\"" # Use the multi-threaded static libc because libpng and zlib do; not sure if anything bad # happens if those mix, but don't want to risk it. CXXFLAGS = /W3 /nologo /MT /D_DEBUG /D_CRT_SECURE_NO_DEPRECATE /D_CRT_SECURE_NO_WARNINGS /I. /Iextlib /Zi /EHs # /O2 diff --git a/ac-aux/ax_fltk.m4 b/ac-aux/ax_fltk.m4 new file mode 100644 index 00000000..b12e7b82 --- /dev/null +++ b/ac-aux/ax_fltk.m4 @@ -0,0 +1,179 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_fltk.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_FLTK(API-VERSION[, +# USE-OPTIONS[, +# ACTION-IF-FOUND[, +# ACTION-IF-NOT-FOUND]]]) +# +# DESCRIPTION +# +# This macro checks for the presence of an installed copy of FLTK +# matching the specified API-VERSION (which can be "1.1", "1.3" and so +# on). If found, the following variables are set and AC_SUBST'ed with +# values obtained from the fltk-config script: +# +# FLTK_VERSION +# FLTK_API_VERSION +# FLTK_CC +# FLTK_CXX +# FLTK_OPTIM +# FLTK_CFLAGS +# FLTK_CXXFLAGS +# FLTK_LDFLAGS +# FLTK_LDSTATICFLAGS +# FLTK_LIBS +# FLTK_PREFIX +# FLTK_INCLUDEDIR +# +# USE-OPTIONS is a space-separated set of --use-* options to pass to the +# fltk-config script when populating the above variables. +# +# If you are using FLTK extensions (e.g. OpenGL support, extra image +# libraries, Forms compatibility), then you can specify a set of +# space-separated options like "--use-gl", "--use-images" etc. for +# USE-OPTIONS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a matching +# version of FLTK is found, and ACTION-IF-NOT-FOUND is a list of commands +# to run it if it is not found. If ACTION-IF-FOUND is not specified, the +# default action will define HAVE_FLTK. +# +# Please let the author(s) know if this macro fails on any platform, or +# if you have any other suggestions or comments. +# +# LICENSE +# +# Copyright (c) 2013 Daniel Richard G. +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 0 + +AC_DEFUN([AX_FLTK], [ +AC_LANG_PUSH([C++]) + +m4_bmatch([$1], [^[1-9]\.[0-9]$], [], + [m4_fatal([invalid FLTK API version "$1"])]) +m4_bmatch([$2], [^\(\(--use-[a-z]+\)\( +--use-[a-z]+\)*\)?$], [], + [m4_fatal([invalid fltk-config use-options string "$2"])]) + +AC_ARG_WITH([fltk], + [AS_HELP_STRING([--with-fltk=PREFIX], + [use FLTK $1 libraries installed in PREFIX])]) + +case "_$with_fltk" in + _no) + FLTK_CONFIG= + ;; + + _|_yes) + AC_PATH_PROG([FLTK_CONFIG], [fltk-config]) + ;; + + *) + AC_PATH_PROG([FLTK_CONFIG], [fltk-config], , [$with_fltk/bin]) + ;; +esac + +FLTK_VERSION= +FLTK_API_VERSION= + +FLTK_CC= +FLTK_CXX= +FLTK_OPTIM= +FLTK_CFLAGS= +FLTK_CXXFLAGS= +FLTK_LDFLAGS= +FLTK_LDSTATICFLAGS= +FLTK_LIBS= +FLTK_PREFIX= +FLTK_INCLUDEDIR= + +have_fltk=no + +if test -n "$FLTK_CONFIG" +then + FLTK_VERSION=`$FLTK_CONFIG --version` + FLTK_API_VERSION=`$FLTK_CONFIG --api-version` + + AC_MSG_CHECKING([for FLTK API version $1]) + + if test "_$FLTK_API_VERSION" = "_$1" + then + have_fltk=yes + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + + fl_opt="$2" + + FLTK_CC=`$FLTK_CONFIG $fl_opt --cc` + FLTK_CXX=`$FLTK_CONFIG $fl_opt --cxx` + FLTK_OPTIM=`$FLTK_CONFIG $fl_opt --optim` + FLTK_CFLAGS=`$FLTK_CONFIG $fl_opt --cflags` + FLTK_CXXFLAGS=`$FLTK_CONFIG $fl_opt --cxxflags` + FLTK_LDFLAGS=`$FLTK_CONFIG $fl_opt --ldflags` + FLTK_LDSTATICFLAGS=`$FLTK_CONFIG $fl_opt --ldstaticflags` + FLTK_LIBS=`$FLTK_CONFIG $fl_opt --libs` + FLTK_PREFIX=`$FLTK_CONFIG $fl_opt --prefix` + FLTK_INCLUDEDIR=`$FLTK_CONFIG $fl_opt --includedir` +fi + +# Finally, execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND: +# +if test "$have_fltk" = yes +then +: +ifelse([$3], , + [AC_DEFINE([HAVE_FLTK], [1], [Define if you have the FLTK libraries.])], + [$3]) +else +: +$4 +fi + +AC_SUBST([FLTK_VERSION]) +AC_SUBST([FLTK_API_VERSION]) + +AC_SUBST([FLTK_CC]) +AC_SUBST([FLTK_CXX]) +AC_SUBST([FLTK_OPTIM]) +AC_SUBST([FLTK_CFLAGS]) +AC_SUBST([FLTK_CXXFLAGS]) +AC_SUBST([FLTK_LDFLAGS]) +AC_SUBST([FLTK_LDSTATICFLAGS]) +AC_SUBST([FLTK_LIBS]) +AC_SUBST([FLTK_PREFIX]) +AC_SUBST([FLTK_INCLUDEDIR]) + +AC_LANG_POP +])dnl AX_FLTK diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 00000000..53676bfc --- /dev/null +++ b/autogen.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -ex + +autoreconf --force --install --warnings=all + +rm -f config.h.in~ +rm -rf autom4te.cache + +# EOF diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..89347623 --- /dev/null +++ b/configure.ac @@ -0,0 +1,136 @@ +## configure.ac + +AC_PREREQ([2.59]) +AC_INIT([SolveSpace], [2.1], [jwesthues@cq.cx], [solvespace]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_SRCDIR([solvespace.cpp]) +AC_CONFIG_AUX_DIR([ac-aux]) +AC_CONFIG_MACRO_DIR([ac-aux]) + +AM_INIT_AUTOMAKE([1.9.6 foreign tar-ustar]) +AM_MAINTAINER_MODE + +AC_PROG_CC +AC_PROG_CXX + +AC_PROG_INSTALL +dnl AC_PROG_MAKE_SET +AC_HEADER_STDC +AC_C_BIGENDIAN +AC_C_CONST +AC_C_INLINE + +# Check for Libtool +# +dnl AC_DISABLE_SHARED +LT_INIT + +# Do we have Perl? +# +AC_CHECK_PROG([PERL], [perl], [perl], [false]) + +# Check for headers that define integer types +# +AC_CHECK_HEADERS([inttypes.h stdint.h sys/socket.h]) + +# Check for various integer types +# +AC_TYPE_INT8_T +AC_TYPE_INT16_T +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_UINT8_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_SSIZE_T +AC_DEFINE([HAVE_C99_INTEGER_TYPES], [1], [Define to 1 if (self-explanatory).]) + +# Check the size of various types +# +AC_CHECK_SIZEOF([char]) +AC_CHECK_SIZEOF([short]) +AC_CHECK_SIZEOF([int]) +AC_CHECK_SIZEOF([long]) +AC_CHECK_SIZEOF([float]) +AC_CHECK_SIZEOF([double]) +AC_CHECK_SIZEOF([long double]) +AC_CHECK_SIZEOF([void *]) +AC_CHECK_SIZEOF([size_t]) + +## +## Windows support +## + +AC_EXEEXT +AC_OBJEXT + +win32=no +AC_EGREP_CPP([SOLVESPACE_WIN32],dnl +[#if (defined(_MSC_VER) && defined(_WIN32)) || defined(__MINGW32__) +SOLVESPACE_WIN32 +#endif], [win32=yes]) + +AM_CONDITIONAL([WIN32], [test "$win32" = yes]) + +mingw=no +AC_EGREP_CPP([SOLVESPACE_MINGW],dnl +[#if defined(__MINGW32__) +SOLVESPACE_MINGW +#endif], [mingw=yes]) + +AM_CONDITIONAL([MINGW], [test "$mingw" = yes]) + +AH_VERBATIM([MSVC_FLAGS], [#if defined(_MSC_VER) && defined(_WIN32) +# define _CRT_SECURE_NO_DEPRECATE 1 +# define _CRT_SECURE_NO_WARNINGS 1 +# define _WIN32_WINNT 0x500 +# define _WIN32_IE _WIN32_WINNT +# define ISOLATION_AWARE_ENABLED 1 +# define WIN32 1 +# define WIN32_LEAN_AND_MEAN 1 +#endif]) + +## +## FLTK +## + +AX_FLTK([1.3], [--use-gl --use-images --use-forms]) +AM_CONDITIONAL([HAVE_FLTK], [test "$have_fltk" = yes]) + +if test "$have_fltk" = yes +then + x=`echo "$FLTK_VERSION" | cut -d. -f1` + y=`echo "$FLTK_VERSION" | cut -d. -f2` + z=`echo "$FLTK_VERSION" | cut -d. -f3` + + if test `expr 10000 '*' $x + 100 '*' $y + $z` -ge 10301 + then + AC_DEFINE([HAVE_FLTK_FULLSCREEN], [1], + [Define to 1 if your copy of FLTK has proper fullscreen support.]) + fi +fi + +## +## Wrap it up +## + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT + +cat < +//----------------------------------------------------------------------------- +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#ifdef HAVE_FONTCONFIG_FONTCONFIG_H +# include +#endif + +#ifdef HAVE_LIBSPNAV +# include +# ifndef SI_APP_FIT_BUTTON +# define SI_APP_FIT_BUTTON 31 +# endif +#endif + +#include //#include +#include +#include +#include +#include // for fl_gettime() +#include + +#include "solvespace.h" + +#define fl_snprintf snprintf + +static Fl_Preferences *Preferences = NULL; + +class Graphics_Gl_Window; +class Text_Gl_Window; + +static Fl_Window *GraphicsWnd = NULL; +static Graphics_Gl_Window *GraphicsGlWnd = NULL; +static Fl_Input *GraphicsEditControl = NULL; +static Fl_Sys_Menu_Bar *MenuBar = NULL; +static Fl_Menu_Item MenuBarItems[120]; +static bool MenuBarVisible = true; + +// Static window object to hold the non-fullscreen size of GraphicsWnd +static Fl_Window GraphicsWndOldSize(100, 100); + +static Fl_Window *TextWnd = NULL; +static Text_Gl_Window *TextGlWnd = NULL; +static Fl_Scrollbar *TextWndScrollBar = NULL; +static Fl_Input *TextEditControl = NULL; + +static struct { + int x, y; +} LastMousePos = { 0, 0 }; + +char RecentFile[MAX_RECENT][MAX_PATH]; +static Fl_Menu_Item RecentOpenMenu[MAX_RECENT+1], RecentImportMenu[MAX_RECENT+1]; + +static Fl_Menu_Item ContextMenu[100]; +static int ContextMenuCount = -1; +static Fl_Menu_Item ContextSubmenu[100]; +static int ContextSubmenuCount = -1; + +static long StartTimeSeconds = 0; +static const Fl_Font SS_FONT_MONOSPACE = FL_FREE_FONT + 8; + +#define GL_CHECK() \ + do { \ + int err = (int)glGetError(); \ + if(err) dbp("%s:%d: glGetError() == 0x%X\n", __FILE__, __LINE__, err); \ + } while (0) + +void DoMessageBox(const char *str, int rows, int cols, bool error) +{ + fl_message_title(error ? "SolveSpace - Error" : "SolveSpace - Message"); + if(error) + fl_alert("%s", str); + else + fl_message("%s", str); +} + +void AddContextMenuItem(const char *label, int id) +{ + if(ContextMenuCount < 0) { + ZERO(ContextMenu); + ContextMenuCount = 0; + } + + // ContextMenu and ContextSubmenu are fixed-size arrays, because + // dynamic Fl_Menu_Item arrays are a PITA to work with + if(ContextMenuCount + 2 > (int)arraylen(ContextMenu)) oops(); + if(ContextSubmenuCount > 0) { + if(ContextSubmenuCount + 2 > (int)arraylen(ContextSubmenu)) oops(); + if(ContextMenuCount + ContextSubmenuCount + 3 > (int)arraylen(ContextMenu)) oops(); + } + + if(id == CONTEXT_SUBMENU) { + if(ContextSubmenuCount <= 0) oops(); + + Fl_Menu_Item *mi = ContextMenu + ContextMenuCount; + mi->label(label); + mi->flags = FL_SUBMENU; + ContextMenuCount++; + + memcpy(ContextMenu + ContextMenuCount, + ContextSubmenu, + ContextSubmenuCount * sizeof(Fl_Menu_Item)); + ContextMenuCount += ContextSubmenuCount + 1; + // (the +1 is for the null item that ends the submenu) + ContextSubmenuCount = -1; + } else { + Fl_Menu_Item *mi = ContextSubmenuCount >= 0 ? + ContextSubmenu + ContextSubmenuCount : + ContextMenu + ContextMenuCount; + + int *cnt = ContextSubmenuCount >= 0 ? + &ContextSubmenuCount : &ContextMenuCount; + + if(id == CONTEXT_SEPARATOR) { + if(*cnt < 1) oops(); + (mi - 1)->flags |= FL_MENU_DIVIDER; + } else { + mi->label(label); + mi->argument(id); + ++*cnt; + } + } +} + +void CreateContextSubmenu(void) +{ + if(ContextSubmenuCount >= 0) oops(); + ZERO(ContextSubmenu); + ContextSubmenuCount = 0; +} + +int ShowContextMenu(void) +{ + int r = 0; + if(ContextMenuCount > 0) { + const Fl_Menu_Item *mi = + ContextMenu->popup(Fl::event_x(), Fl::event_y()); + if(mi) r = (int)mi->argument(); + ContextMenuCount = -1; + } + return r; +} + +static void TimerCallback(void *) +{ + SS.GW.TimerCallback(); + SS.TW.TimerCallback(); +} + +void SetTimerFor(int milliseconds) +{ + Fl::add_timeout((double)milliseconds / 1000.0, TimerCallback); +} + +void OpenWebsite(const char *url) +{ + fl_open_uri(url, NULL, 0); +} + +void ExitNow(void) +{ + // This will make Fl::wait() return zero + GraphicsWnd->hide(); + TextWnd->hide(); +} + +//----------------------------------------------------------------------------- +// Helpers so that we can read/write preference keys from the platform- +// independent code. +//----------------------------------------------------------------------------- +void CnfFreezeString(const char *str, const char *name) +{ + if(Preferences) Preferences->set(name, str); +} + +void CnfFreezeInt(uint32_t v, const char *name) +{ + if(Preferences) Preferences->set(name, (int)v); +} + +void CnfFreezeFloat(float v, const char *name) +{ + if(Preferences) Preferences->set(name, v); +} + +static void CnfFreezeWindowPos(Fl_Window *wnd, const char *name) +{ + char buf[100]; + fl_snprintf(buf, sizeof(buf), "%s_left", name); + CnfFreezeInt(wnd->x(), buf); + fl_snprintf(buf, sizeof(buf), "%s_top", name); + CnfFreezeInt(wnd->y(), buf); + fl_snprintf(buf, sizeof(buf), "%s_width", name); + CnfFreezeInt(wnd->w(), buf); + fl_snprintf(buf, sizeof(buf), "%s_height", name); + CnfFreezeInt(wnd->h(), buf); +} + +void CnfThawString(char *str, int maxLen, const char *name) +{ + char *def = strdup(str); + if(Preferences) Preferences->get(name, str, def, maxLen - 1); + free(def); +} + +uint32_t CnfThawInt(uint32_t v, const char *name) +{ + int r = 0; + if(Preferences) Preferences->get(name, r, (int)v); + return (uint32_t)r; +} + +float CnfThawFloat(float v, const char *name) +{ + float r = 0.0; + if(Preferences) Preferences->get(name, r, v); + return r; +} + +static void CnfThawWindowPos(Fl_Window *wnd, const char *name) +{ + char buf[100]; + fl_snprintf(buf, sizeof(buf), "%s_left", name); + int x = CnfThawInt(wnd->x(), buf); + fl_snprintf(buf, sizeof(buf), "%s_top", name); + int y = CnfThawInt(wnd->y(), buf); + fl_snprintf(buf, sizeof(buf), "%s_width", name); + int w = CnfThawInt(wnd->w(), buf); + fl_snprintf(buf, sizeof(buf), "%s_height", name); + int h = CnfThawInt(wnd->h(), buf); + +#define MARGIN 32 + + if(x < -MARGIN || y < -MARGIN) return; + if(x > Fl::w() - MARGIN || y > Fl::h() - MARGIN) return; + if(w < 100 || h < 100) return; + +#undef MARGIN + + wnd->resize(x, y, w, h); +} + +static void LoadPreferences(void) +{ + const char *xchome, *home; + char dir[MAX_PATH]; + int r = 0; + + // Refer to http://standards.freedesktop.org/basedir-spec/latest/ + + xchome = fl_getenv("XDG_CONFIG_HOME"); + home = fl_getenv("HOME"); + + if(xchome) + r = fl_snprintf(dir, sizeof(dir), "%s/solvespace", xchome); + else if(home) + r = fl_snprintf(dir, sizeof(dir), "%s/.config/solvespace", home); + else + return; + + if(r >= (int)sizeof(dir)) + return; + + if(!fl_filename_isdir(dir) && mkdir(dir, 0777) != 0) { + r = fl_snprintf(dir, sizeof(dir), "%s/.solvespace", home); + if(r >= (int)sizeof(dir)) + return; + if(!fl_filename_isdir(dir)) + if(mkdir(dir, 0777) != 0) + return; + } + + Preferences = new Fl_Preferences(dir, "solvespace.org", "solvespace"); +} + +void SetWindowTitle(const char *str) { + GraphicsWnd->label(str); +} + +void SetMousePointerToHand(bool yes) { + Fl_Cursor cur = yes ? FL_CURSOR_HAND : FL_CURSOR_ARROW; + GraphicsWnd->cursor(cur); + TextWnd->cursor(cur); +} + +void MoveTextScrollbarTo(int pos, int maxPos, int page) +{ + TextWndScrollBar->value(pos, page, 0, maxPos); +} + +static void HandleTextWindowScrollBar(Fl_Widget *w) +{ + if(w != TextWndScrollBar) oops(); + SS.TW.ScrollbarEvent(TextWndScrollBar->value()); +} + +void ShowTextWindow(bool visible) +{ + if(visible) { + TextWnd->show(); + +#ifdef InputHint + { + // Prevent the text window from gaining window manager focus by + // setting the appropriate WM hint via direct X calls + + XWMHints *hints = XAllocWMHints(); + hints->input = False; + hints->flags = InputHint; + XSetWMHints(fl_display, fl_xid(TextWnd), hints); + XFree(hints); + + // In case the text window got the focus before we could set + // the hint, switch the focus back to the graphics window + + Window xid = 0; + int revert_to = 0; + XGetInputFocus(fl_display, &xid, &revert_to); + if(xid == fl_xid(TextWnd)) { + XSetInputFocus( + fl_display, + fl_xid(GraphicsWnd), + RevertToParent, + CurrentTime); + } + } +#endif + } else { + TextWnd->hide(); + } +} + +int64_t GetMilliseconds(void) +{ + long sec = StartTimeSeconds, usec = 0; + fl_gettime(&sec, &usec); + if(!StartTimeSeconds) StartTimeSeconds = sec; + sec -= StartTimeSeconds; + return 1000 * (int64_t)sec + (int64_t)usec / 1000; +} + +int64_t GetUnixTime(void) +{ + time_t ret; + time(&ret); + return (int64_t)ret; +} + +void ShowTextEditControl(int x, int y, char *s) +{ + if(GraphicsEditControlIsVisible()) return; + + // Note: TextEditControl->position() does NOT set (x,y) position! + TextEditControl->resize(x, y, TextEditControl->w(), TextEditControl->h()); + if(s) TextEditControl->value(s); + TextEditControl->show(); +} + +void HideTextEditControl(void) +{ + TextEditControl->hide(); +} + +bool TextEditControlIsVisible(void) +{ + return TextEditControl->visible(); +} + +void ShowGraphicsEditControl(int x, int y, char *s) +{ + if(GraphicsEditControlIsVisible()) return; + + GraphicsEditControl->position(x, y); + GraphicsEditControl->value(s); + GraphicsEditControl->show(); +} + +void HideGraphicsEditControl(void) +{ + GraphicsEditControl->hide(); +} + +bool GraphicsEditControlIsVisible(void) +{ + return GraphicsEditControl->visible(); +} + +class Graphics_Gl_Window : public Fl_Gl_Window_Group +{ +public: + + Graphics_Gl_Window(int x, int y, int w, int h) + : Fl_Gl_Window_Group(x, y, w, h) + { + mode(FL_RGB | FL_DOUBLE); + } + + int handle_gl(int event) + { + switch(event) + { +#ifdef HAVE_LIBSPNAV + case FL_NO_EVENT: { + spnav_event sev; + if(!spnav_x11_event(fl_xevent, &sev)) break; + 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, + Fl::event_shift()); + break; + + case SPNAV_EVENT_BUTTON: + if(!sev.button.press && sev.button.bnum == SI_APP_FIT_BUTTON) { + SS.GW.SpaceNavigatorButtonUp(); + } + break; + } + return 1; + } +#endif // HAVE_LIBSPNAV + + case FL_PUSH: // mouse button click... + case FL_RELEASE: // ...and release + case FL_DRAG: + case FL_MOVE: { + int x = Fl::event_x(); + int y = Fl::event_y(); + + // Convert to xy (vs. ij) style coordinates, + // with (0, 0) at center + x = x - w() / 2; + y = h() / 2 - y; + + LastMousePos.x = x; + LastMousePos.y = y; + + // Don't go any further if the OpenGL context hasn't been + // initialized/updated by a draw() + if(!valid()) return 1; + + if(event == FL_DRAG || event == FL_MOVE) { + int state = Fl::event_state(); + SS.GW.MouseMoved(x, y, + state & FL_BUTTON1, + state & FL_BUTTON2, + state & FL_BUTTON3, + state & FL_SHIFT, + state & FL_CTRL); + return 1; + } + +#if FL_RIGHT_MOUSE != 3 +# error "MOUSE() macro may need revising" +#endif +#define MOUSE(btn,ev) (16 * ev + btn) + + switch(MOUSE(Fl::event_button(), event)) + { + case MOUSE(FL_LEFT_MOUSE, FL_PUSH): + if(Fl::event_clicks()) { + SS.GW.MouseLeftDoubleClick(x, y); + } else { + SS.GW.MouseLeftDown(x, y); + } + break; + + case MOUSE(FL_LEFT_MOUSE, FL_RELEASE): + SS.GW.MouseLeftUp(x, y); break; + + case MOUSE(FL_MIDDLE_MOUSE, FL_PUSH): + case MOUSE(FL_RIGHT_MOUSE, FL_PUSH): + SS.GW.MouseMiddleOrRightDown(x, y); break; + + case MOUSE(FL_MIDDLE_MOUSE, FL_RELEASE): + /* Not used */ break; + + case MOUSE(FL_RIGHT_MOUSE, FL_RELEASE): + SS.GW.MouseRightUp(x, y); break; + + default: oops(); break; + } +#undef MOUSE + return 1; + } + + case FL_ENTER: + return 1; + + case FL_LEAVE: + SS.GW.MouseLeave(); + return 1; + + case FL_FOCUS: + return 1; + + case FL_UNFOCUS: + return 1; + + case FL_KEYDOWN: { + int key = Fl::event_key(); + int c = key; + switch(key) { + case FL_Escape: + c = GraphicsWindow::ESCAPE_KEY; + break; + case FL_Delete: + c = GraphicsWindow::DELETE_KEY; + break; + case FL_Tab: + c = '\t'; + break; + + case FL_Back: + case FL_BackSpace: + c = '\b'; + break; + } + if(key >= (FL_F+1) && key <= (FL_F+12)) { + c = GraphicsWindow::FUNCTION_KEY_BASE + (key - FL_F); + } + if(Fl::event_shift()) c |= GraphicsWindow::SHIFT_MASK; + if(Fl::event_ctrl()) c |= GraphicsWindow::CTRL_MASK; + + if(SS.GW.KeyDown(c)) return 1; + + // No accelerator; process the key as normal. + break; + } + + case FL_KEYUP: + return 1; + + case FL_CLOSE: + // GraphicsGlWnd does not receive this event; we intercept + // close events in WindowCloseHandler() + oops(); + return 0; + + case FL_MOUSEWHEEL: + SS.GW.MouseScroll(LastMousePos.x, LastMousePos.y, Fl::event_dy()); + return 1; + } + + return 0; + } + +protected: + + void draw_gl(void) + { + // Actually paint the window, with gl. + SS.GW.Paint(); + GL_CHECK(); + } + + virtual void dummy(void); +}; + +void Graphics_Gl_Window::dummy(void) +{ + // sop to Clang++'s -Wweak-vtables warning +} + +void PaintGraphics(void) +{ + GraphicsGlWnd->redraw(); +} + +void InvalidateGraphics(void) +{ + GraphicsGlWnd->redraw(); +} + +void ToggleFullScreen(void) +{ +#ifdef HAVE_FLTK_FULLSCREEN + if(GraphicsWnd->fullscreen_active()) { + GraphicsWnd->fullscreen_off(); + } else { + GraphicsWndOldSize.resize( + GraphicsWnd->x(), + GraphicsWnd->y(), + GraphicsWnd->w(), + GraphicsWnd->h()); + + GraphicsWnd->fullscreen(); + } +#endif +} + +bool FullScreenIsActive(void) +{ +#ifdef HAVE_FLTK_FULLSCREEN + return GraphicsWnd->fullscreen_active(); +#else + return false; +#endif +} + +void GetGraphicsWindowSize(int *w, int *h) +{ + *w = GraphicsGlWnd->w(); + *h = GraphicsGlWnd->h(); +} + +void ToggleMenuBar(void) +{ + int y = 0; + + MenuBarVisible = !MenuBarVisible; + + // We hide the menu bar by expanding the GL area over it, instead of + // calling hide(). This way, F10/Alt+F/etc. remain usable. + + if(MenuBarVisible) y = MenuBar->h(); + + GraphicsGlWnd->resize( + 0, y, + GraphicsWnd->w(), GraphicsWnd->h() - y); + + // Make GraphicsWnd forget about the previous sizes of its children, or + // else the menu bar will {dis,re}appear when the window is resized + GraphicsWnd->init_sizes(); +} + +bool MenuBarIsVisible(void) +{ + return MenuBarVisible; +} + +class Text_Gl_Window : public Fl_Gl_Window_Group +{ +public: + + Text_Gl_Window(int x, int y, int w, int h) + : Fl_Gl_Window_Group(x, y, w, h) + { + mode(FL_RGB | FL_DOUBLE); + } + + int handle_gl(int event) + { + switch(event) + { + case FL_PUSH: // mouse button click + case FL_MOVE: + if(valid()) { + SS.TW.MouseEvent( + event == FL_PUSH && Fl::event_button() == FL_LEFT_MOUSE, + Fl::event_button1(), + Fl::event_x(), Fl::event_y()); + } + return 1; + + case FL_ENTER: + case FL_FOCUS: + return 1; + + case FL_LEAVE: + SS.TW.MouseLeave(); + return 1; + + case FL_KEYDOWN: + case FL_KEYUP: + return GraphicsGlWnd->handle(event); + + case FL_CLOSE: + // TextGlWnd does not receive this event; we intercept + // close events in WindowCloseHandler() + oops(); + return 0; + + case FL_MOUSEWHEEL: + return TextWndScrollBar->handle(event); + } + + return 0; + } + +protected: + + void draw_gl(void) + { + // Actually paint the text window, with gl. + SS.TW.Paint(); + GL_CHECK(); + } + + virtual void dummy(void); +}; + +void Text_Gl_Window::dummy(void) +{ + // sop to Clang++'s -Wweak-vtables warning +} + +void InvalidateText(void) +{ + TextGlWnd->redraw(); +} + +void GetTextWindowSize(int *w, int *h) +{ + *w = TextGlWnd->w(); + *h = TextGlWnd->h(); +} + +static void EditControlCallback(Fl_Widget *w) +{ + if(w == GraphicsEditControl) { + SS.GW.EditControlDone(GraphicsEditControl->value()); + } else if(w == TextEditControl) { + SS.TW.EditControlDone(TextEditControl->value()); + } else { + oops(); + } +} + +static void WindowCloseHandler(Fl_Window *wnd, void *data) +{ + if(wnd == GraphicsWnd) { + SolveSpace::MenuFile(GraphicsWindow::MNU_EXIT); + } + else if(wnd == TextWnd) { + if(SS.GW.showTextWindow) { + GraphicsWindow::MenuView(GraphicsWindow::MNU_SHOW_TEXT_WND); + } + } else { + oops(); + } +} + +//----------------------------------------------------------------------------- +// Common dialog routines, to open or save a file. +//----------------------------------------------------------------------------- +bool GetOpenFile(char *file, const char *defExtension, const char *selPattern) +{ +#ifdef USE_FLTK_FILE_CHOOSER + char *f = fl_file_chooser( + "Open File", + selPattern, + file[0] ? file : NULL, + 0); + if(strlen(f)+1 > MAX_PATH) return false; + strcpy(file, f); + return true; +#else + Fl_Native_File_Chooser fc; + fc.title("Open File"); + fc.type(Fl_Native_File_Chooser::BROWSE_FILE); + if(file[0]) fc.preset_file(file); + fc.filter(selPattern); + fc.options(Fl_Native_File_Chooser::PREVIEW); + if(fc.show() != 0) return false; + if(strlen(fc.filename())+1 > MAX_PATH) return false; + strcpy(file, fc.filename()); + return true; +#endif +} + +bool GetSaveFile(char *file, const char *defExtension, const char *selPattern) +{ +#ifdef USE_FLTK_FILE_CHOOSER + char *f = fl_file_chooser( + "Save File", + selPattern, + file[0] ? file : NULL, + 0); + if(strlen(f)+1 > MAX_PATH) return false; + strcpy(file, f); + return true; +#else + Fl_Native_File_Chooser fc; + fc.title("Save File"); + fc.type(Fl_Native_File_Chooser::BROWSE_SAVE_FILE); + if(file[0]) fc.preset_file(file); + fc.filter(selPattern); + fc.options( + Fl_Native_File_Chooser::NEW_FOLDER | + Fl_Native_File_Chooser::SAVEAS_CONFIRM); + if(fc.show() != 0) return false; + if(strlen(fc.filename())+1 > MAX_PATH) return false; + strcpy(file, fc.filename()); + return true; +#endif +} + +int SaveFileYesNoCancel(void) +{ + int ycn[] = { SAVE_YES, SAVE_CANCEL, SAVE_NO }; + int r; + + fl_message_title("SolveSpace"); + r = fl_choice( + "The program has changed since it was last saved.\n\n" + "Do you want to save the changes?", + "Yes", + "Cancel", // default + "No"); + + return ycn[r]; +} + +#ifndef HAVE_FONTCONFIG +static void ScanFontDirectory(const char *dir) +{ + dirent **list = NULL; + char path[MAX_PATH]; + + int n = fl_filename_list(dir, &list, fl_alphasort); + if(n < 0) + return; + + for(int i = 0; i < n; i++) + { + int len = fl_snprintf(path, sizeof(path), "%s/%s", dir, list[i]->d_name); + if(len >= MAX_PATH) continue; + + if(fl_filename_isdir(path)) { + ScanFontDirectory(path); + } + else if(fl_filename_match(path, "*.{TTF,ttf}")) { + TtfFont tf; + ZERO(&tf); + strcpy(tf.fontFile, path); + SS.fonts.l.Add(&tf); + } + } + + fl_filename_free_list(&list, n); +} +#endif // ndef HAVE_FONTCONFIG + +void LoadAllFontFiles(void) +{ +#ifdef HAVE_FONTCONFIG + + if(!FcInit()) + return; + + FcPattern *pat = FcPatternCreate(); + FcObjectSet *os = FcObjectSetBuild(FC_FILE, (char *)0); + FcFontSet *fs = FcFontList(0, pat, os); + + for(int i = 0; i < fs->nfont; i++) { + char *s = FcPatternFormat(fs->fonts[i], "%{file}"); + if(strlen(s)+1 <= MAX_PATH && fl_filename_match(s, "*.{TTF,ttf}") { + TtfFont tf; + ZERO(&tf); + strcpy(tf.fontFile, s); + SS.fonts.l.Add(&tf); + } + FcStrFree(s); + } + + FcFontSetDestroy(fs); + FcObjectSetDestroy(os); + FcPatternDestroy(pat); + FcFini(); + +#else + +# ifdef __APPLE__ + ScanFontDirectory("/System/Library/Fonts"); + ScanFontDirectory("/Library/Fonts"); +# else + ScanFontDirectory("/usr/lib/X11/fonts"); + ScanFontDirectory("/usr/openwin/lib/X11/fonts/TrueType"); + ScanFontDirectory("/usr/share/fonts/truetype"); +# endif + +#endif +} + +enum { + CHECK, + RADIO, + ACTIVE +}; + +static void MenuById(int id, bool yes, int what) +{ + const Fl_Menu_Item *menu = MenuBar->menu(); + int size = MenuBar->size(); + + for(int i = 0; i < size; i++) { + const Fl_Menu_Item *m = &menu[i]; + if(m->submenu()) continue; + if(m->argument() != (long)id) continue; + + int flags = MenuBar->mode(i); + switch(what) { + case CHECK: + if(!m->checkbox()) flags |= FL_MENU_TOGGLE; + if(yes) flags |= FL_MENU_VALUE; + else flags &= ~FL_MENU_VALUE; + break; + case RADIO: + if(!m->radio()) flags |= FL_MENU_RADIO; + if(yes) flags |= FL_MENU_VALUE; + else flags &= ~FL_MENU_VALUE; + break; + case ACTIVE: + if(yes) flags &= ~FL_MENU_INACTIVE; + else flags |= FL_MENU_INACTIVE; + break; + default: oops(); break; + } + MenuBar->mode(i, flags); + return; + } + oops(); +} + +void CheckMenuById(int id, bool checked) +{ + MenuById(id, checked, CHECK); +} + +void RadioMenuById(int id, bool selected) +{ + MenuById(id, selected, RADIO); +} + +void EnableMenuById(int id, bool enabled) +{ + MenuById(id, enabled, ACTIVE); +} + +static void RecentMenuCallback(Fl_Widget *w, long data) +{ + int id = (int)data; + 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); + } +} + +static void DoRecent(Fl_Menu_Item *m, int base) +{ + int c = 0; + for(int i = 0; i < MAX_RECENT; i++) { + char *s = RecentFile[i]; + if(*s) { + m[c].label(s); + m[c].callback(RecentMenuCallback); + m[c].argument(base + i); + c++; + } + } + if(c == 0) { + m[0].label("(no recent files)"); + m[0].deactivate(); + } +} + +void RefreshRecentMenus(void) +{ + ZERO(RecentOpenMenu); + ZERO(RecentImportMenu); + DoRecent(RecentOpenMenu, RECENT_OPEN); + DoRecent(RecentImportMenu, RECENT_IMPORT); +} + +static void GraphicsWndMenuCallback(Fl_Widget *w, long data) +{ + int id = (int)data; + + for(int i = 0; SS.GW.menu[i].level >= 0; i++) + if(SS.GW.menu[i].id == id) + return SS.GW.menu[i].fn(id); + oops(); +} + +static void CreateGraphicsWindowMenus(void) +{ + MenuBar = new Fl_Sys_Menu_Bar(0, 0, GraphicsWnd->w(), 100); + if(!MenuBar) oops(); + + RefreshRecentMenus(); + ZERO(MenuBarItems); + + int c = 0; + for(int i = 0; SS.GW.menu[i].level >= 0; i++) { + int accel = SS.GW.menu[i].accel; + int shortcut = accel & 0xff; + if(shortcut >= 'A' && shortcut <= 'Z') shortcut |= 0x20; + switch(shortcut) { + case GraphicsWindow::ESCAPE_KEY: + shortcut = FL_Escape; + break; + case GraphicsWindow::DELETE_KEY: + shortcut = FL_Delete; + break; + default: + if(accel & GraphicsWindow::SHIFT_MASK) + shortcut += FL_SHIFT; + if(accel & GraphicsWindow::CTRL_MASK) + shortcut += FL_CTRL; + break; + } + if(accel >= (GraphicsWindow::FUNCTION_KEY_BASE + 1) && + accel <= (GraphicsWindow::FUNCTION_KEY_BASE + 12)) { + shortcut = FL_F + (accel - GraphicsWindow::FUNCTION_KEY_BASE); + } + + Fl_Menu_Item *m = &MenuBarItems[c]; + switch(SS.GW.menu[i].level) { + case 0: + m->label(SS.GW.menu[i].label); + m->shortcut(shortcut); + m->flags = FL_SUBMENU; + c++; + break; + + case 1: + if(!SS.GW.menu[i].label) break; // divider + m->label(SS.GW.menu[i].label); + m->shortcut(shortcut); + switch(SS.GW.menu[i].id) { + case GraphicsWindow::MNU_OPEN_RECENT: + m->user_data(RecentOpenMenu); + m->flags = FL_SUBMENU_POINTER; + break; + case GraphicsWindow::MNU_GROUP_RECENT: + m->user_data(RecentImportMenu); + m->flags = FL_SUBMENU_POINTER; + break; + default: + m->callback(GraphicsWndMenuCallback); + m->argument(SS.GW.menu[i].id); + m->flags = SS.GW.menu[i+1].label + || SS.GW.menu[i+1].level < 0 ? 0 : FL_MENU_DIVIDER; + break; + } + c++; + break; + + default: oops(); break; + } + + if(!SS.GW.menu[i+1].level) { + if(!SS.GW.menu[i].label) oops(); + c++; // leave null item to end current submenu + } + } + + // Make F10 bring up the File menu + MenuBarItems[0].shortcut(FL_F+10); + + MenuBar->menu(MenuBarItems); + MenuBar->size(MenuBar->w(), 2 * MenuBar->textsize()); // fudge + MenuBar->global(); +} + +static void CreateMainWindows(void) +{ + // Graphics window + + GraphicsWnd = new Fl_Window( + 3 * Fl::w() / 4, 3 * Fl::h() / 4, + "SolveSpace (not yet saved)"); + if(!GraphicsWnd) oops(); + + CreateGraphicsWindowMenus(); + + // Avoid momentary grey flicker + GraphicsWnd->color(FL_BLACK); + + GraphicsGlWnd = new Graphics_Gl_Window( + 0, MenuBar->h(), + GraphicsWnd->w(), GraphicsWnd->h() - MenuBar->h()); + + GraphicsWnd->resizable(GraphicsGlWnd); + GraphicsWnd->size_range(Fl::w() / 4, Fl::h() / 4); + + GraphicsEditControl = new Fl_Input(0, 20, 120, 30); + GraphicsEditControl->textfont(SS_FONT_MONOSPACE); + GraphicsEditControl->callback(EditControlCallback); + GraphicsEditControl->when(FL_WHEN_ENTER_KEY | FL_WHEN_NOT_CHANGED); + GraphicsEditControl->hide(); + + GraphicsGlWnd->end(); + GraphicsWnd->end(); + + // Text window + + TextWnd = new Fl_Window(480, 320, "SolveSpace - Browser"); + if(!TextWnd) oops(); + + TextWnd->color(FL_BLACK); + + TextWndScrollBar = new Fl_Scrollbar( + TextWnd->w() - Fl::scrollbar_size(), 0, + Fl::scrollbar_size(), TextWnd->h()); + + //TextWndScrollBar->value(0, 1, 0, 1); + TextWndScrollBar->callback(HandleTextWindowScrollBar); + + TextGlWnd = new Text_Gl_Window( + 0, 0, + TextWnd->w() - TextWndScrollBar->w(), TextWnd->h()); + + TextWnd->resizable(TextGlWnd); + TextWnd->size_range(Fl::w() / 8, Fl::h() / 8); + + // We get the desired Alt+Tab behaviour by specifying that the text + // window is "non-modal". + + TextWnd->set_non_modal(); + + TextEditControl = new Fl_Input( + 0, 0, + 20 * TextWindow::CHAR_WIDTH, TextWindow::LINE_HEIGHT); + TextEditControl->textfont(SS_FONT_MONOSPACE); + TextEditControl->callback(EditControlCallback); + TextEditControl->when(FL_WHEN_ENTER_KEY | FL_WHEN_NOT_CHANGED); + TextEditControl->hide(); + + TextGlWnd->end(); + TextWnd->end(); + + Fl::set_atclose(WindowCloseHandler); +} + +static void LoadFixedFont(void) +{ + const char *names[] = { + "DejaVu Sans Mono", + "Bitstream Vera Sans Mono", + "Liberation Mono", + "monospace", + NULL + }; + int i; + + for (i = 0; names[i] != NULL; i++) + { + Fl::set_font(SS_FONT_MONOSPACE, names[i]); + fl_font(SS_FONT_MONOSPACE, 144); + if (fl_width("abcd1234") >= 1.0) + return; + } + + oops(); +} + +static int ArgHandler(int argc, char **argv, int &i) +{ + fprintf(stderr, "option %d = '%s'\n", i, argv[i]); + + + + return 0; +} + +//----------------------------------------------------------------------------- +// Entry point into the program. +//----------------------------------------------------------------------------- +int main(int argc, char **argv) +{ + // Parse command-line options + int optndx = 0; + if (!Fl::args(argc, argv, optndx, ArgHandler)) { + Fl::fatal(Fl::help); + } + + // Initialize StartTimeSeconds + GetMilliseconds(); + +#ifndef USE_FLTK_FILE_CHOOSER + // The docs for Fl_Native_File_Chooser recommend doing this + Fl_File_Icon::load_system_icons(); +#endif + + // Don't make message dialogs show up under the mouse pointer + fl_message_hotspot(0); + + LoadPreferences(); + + // A monospaced font + LoadFixedFont(); + + // Create the root windows: one for control, with text, and one for + // the graphics + CreateMainWindows(); + + CnfThawWindowPos(TextWnd, "TextWindow"); + CnfThawWindowPos(GraphicsWnd, "GraphicsWindow"); + + GraphicsWnd->show(argc, argv); + ShowTextWindow(true); + + // Don't use the default (FL_CURSOR_DEFAULT) arrow pointer, as it can't + // be resized by the user + SetMousePointerToHand(false); + + // A filename may have been specified on the command line; if so, then + // make it absolute. + char file[MAX_PATH] = ""; + if(optind < argc && strlen(argv[optind])+1 < MAX_PATH) { + strcpy(file, argv[optind]); + } + if(*file != '\0') { + GetAbsoluteFilename(file); + } + +#ifdef HAVE_LIBSPNAV + bool spacenavd_active = + spnav_x11_open(fl_display, fl_xid(GraphicsWnd)) == 0; +#endif + + // Call in to the platform-independent code, and let them do their init + SS.Init(file); + + // And now it's the main event loop. All calls in to the rest of the + // code will be from the callbacks. + for(;;) { + // This call to Fl::first_window() ensures that Fl::flush() draws + // TextGlWnd before TextWnd, which allows MoveTextScrollbarTo() + // (normally called while TextGlWnd is being drawn) to update the + // text-window scrollbar (a child widget of TextWnd) immediately, + // rather than being delayed until the next redraw. This has to be + // done at every iteration because FLTK constantly updates the + // window order, placing those which received events most recently + // at the beginning of the list. + Fl::first_window(TextGlWnd); + if(!Fl::wait()) break; +// TODO: invoke DoLater() at the right time + SS.DoLater(); + } + +#ifdef HAVE_LIBSPNAV + if(spacenavd_active) { + spnav_close(); + } +#endif + + // Write everything back into preferences + CnfFreezeWindowPos(TextWnd, "TextWindow"); + CnfFreezeWindowPos( +#ifdef HAVE_FLTK_FULLSCREEN + GraphicsWnd->fullscreen_active() ? &GraphicsWndOldSize : GraphicsWnd, +#else + GraphicsWnd, +#endif + "GraphicsWindow"); + + delete TextWnd; + delete GraphicsWnd; + delete Preferences; + + // Free the memory we've used; anything that remains is a leak. + SK.Clear(); + SS.Clear(); + + return 0; +} diff --git a/fltk/fltkutil.cpp b/fltk/fltkutil.cpp new file mode 100644 index 00000000..ff541e67 --- /dev/null +++ b/fltk/fltkutil.cpp @@ -0,0 +1,107 @@ +//----------------------------------------------------------------------------- +// Utility functions used by the FLTK port. Notably, our memory allocation; +// we use two separate allocators, one for long-lived stuff and one for +// stuff that gets freed after every regeneration of the model, to save us +// the trouble of freeing the latter explicitly. +// +// Copyright 2008-2013 Jonathan Westhues. +// Copyright 2013 Daniel Richard G. +//----------------------------------------------------------------------------- +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include + +#include "solvespace.h" + +void dbp(const char *str, ...) +{ + va_list f; + static char buf[1024*50]; + va_start(f, str); + vsnprintf(buf, sizeof(buf), str, f); + va_end(f); + + fputs(buf, stderr); +} + +void GetAbsoluteFilename(char *file) +{ + char absoluteFile[PATH_MAX]; + fl_filename_absolute(absoluteFile, sizeof(absoluteFile), file); + strcpy(file, absoluteFile); +} + +//----------------------------------------------------------------------------- +// 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 +// to be sloppy with our memory management, and just free everything at once +// at the end. +//----------------------------------------------------------------------------- + +typedef struct _AllocTempHeader AllocTempHeader; + +typedef struct _AllocTempHeader { + AllocTempHeader *prev; + AllocTempHeader *next; +} AllocTempHeader; + +static AllocTempHeader *Head = NULL; + +void *AllocTemporary(size_t n) +{ + AllocTempHeader *h = + (AllocTempHeader *)malloc(n + sizeof(AllocTempHeader)); + h->prev = NULL; + h->next = Head; + Head = h; + return (void *)&h[1]; +} + +void FreeTemporary(void *p) +{ + AllocTempHeader *h = (AllocTempHeader *)p - 1; + if(h->prev) { + h->prev->next = h->next; + } else { + Head = h->next; + } + if(h->next) h->next->prev = h->prev; + free(h); +} + +void FreeAllTemporary(void) +{ + AllocTempHeader *h = Head; + while(h) { + AllocTempHeader *f = h; + h = h->next; + free(f); + } + Head = NULL; +} + +void *MemRealloc(void *p, size_t n) { + if(!p) { + return MemAlloc(n); + } + + p = realloc(p, n); + if(!p) oops(); + return p; +} + +void *MemAlloc(size_t n) { + void *p = malloc(n); + if(!p) oops(); + return p; +} + +void MemFree(void *p) { + free(p); +} diff --git a/fltk/xFl_Gl_Window_Group.H b/fltk/xFl_Gl_Window_Group.H new file mode 100644 index 00000000..05d2abde --- /dev/null +++ b/fltk/xFl_Gl_Window_Group.H @@ -0,0 +1,103 @@ +// +// "$Id$" +// +// OpenGL window group widget for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2010 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// http://www.fltk.org/COPYING.php +// +// Please report all bugs and problems on the following page: +// +// http://www.fltk.org/str.php +// + +/* \file + Fl_Gl_Window_Group widget . */ + +#ifndef Fl_Gl_Window_Group_H +#define Fl_Gl_Window_Group_H + +#include //#include "Fl_Gl_Window.H" +#include //#include "x.H" + +#define Fl_Gl_Window_Group xFl_Gl_Window_Group + +class Fl_Gl_Window_Group_INTERNAL; + +/** + The Fl_Gl_Window_Group widget is an extended form of Fl_Gl_Window that + can contain child widgets not specially modified to use OpenGL calls. + After the main OpenGL area is drawn, child widgets are each drawn into an + offscreen buffer (using the standard FLTK drawing routines), and then + copied into the OpenGL window using a textured quad. +*/ +class FL_EXPORT Fl_Gl_Window_Group : public Fl_Gl_Window { + + class Fl_Gl_Window_Group_INTERNAL *glstandin; + + Fl_Offscreen offscr; + int offscr_w, offscr_h; + uchar *imgbuf; + + GLContext children_context; + unsigned int texid; + + void init(void); + void adjust_offscr(int w, int h); + +public: + + ~Fl_Gl_Window_Group(void); + + /** + Creates a new Fl_Gl_Window_Group widget using the given size and label string. + */ + Fl_Gl_Window_Group(int W, int H, const char *l=0) + : Fl_Gl_Window(W,H,l) {init();} + + /** + Creates a new Fl_Gl_Window_Group widget using the given position, + size, and label string. + */ + Fl_Gl_Window_Group(int X, int Y, int W, int H, const char *l=0) + : Fl_Gl_Window(X,Y,W,H,l) {init();} + + void show(void); + void hide(void); + void clear(void); + + void flush(void); + + /** + Handles the specified event. + + This method only receives events that were not used by any of the child + widgets. Unlike most handle() methods, this should not call the + inherited handle() method, or else an infinite loop will result. + */ + virtual int handle_gl(int event); + +protected: + + void draw(void); + void draw_children(void); + void draw_child(Fl_Widget& widget); + + /** + Draws the main OpenGL area of the Fl_Gl_Window_Group. + + You \e \b must override the draw_gl() method. + */ + virtual void draw_gl(void); +}; + +#endif + +// +// End of "$Id:$". +// diff --git a/fltk/xFl_Gl_Window_Group.cxx b/fltk/xFl_Gl_Window_Group.cxx new file mode 100644 index 00000000..8b62a682 --- /dev/null +++ b/fltk/xFl_Gl_Window_Group.cxx @@ -0,0 +1,292 @@ +// +// "$Id$" +// +// OpenGL window group widget for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2010 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// http://www.fltk.org/COPYING.php +// +// Please report all bugs and problems on the following page: +// +// http://www.fltk.org/str.php +// + +#include +#include +#include +#include +#include +//#include "Fl_Gl_Choice.H" +#if 1 //---------------- Extract from Fl_Gl_Choice.H +#ifdef WIN32 +# include +# define GLContext HGLRC +#elif defined(__APPLE_QUARTZ__) +# include +# include +# define GLContext AGLContext +#else +# include +# define GLContext GLXContext +#endif +GLContext fl_create_gl_context(XVisualInfo*); +void fl_set_gl_context(Fl_Window*, GLContext); +void fl_delete_gl_context(GLContext); +#endif //---------------- +#include //#include +#include +#include + +#if 1 //HAVE_GL + +#if !defined(GL_TEXTURE_RECTANGLE) && defined(WIN32) +# define GL_TEXTURE_RECTANGLE 0x84F5 +#endif + +#define Gl_Stand_In Fl_Gl_Window_Group_INTERNAL + +#define RESET_FIELDS() \ + imgbuf = NULL; \ + offscr_w = -1; \ + offscr_h = -1 + +class Gl_Stand_In : public Fl_Box { + + Fl_Gl_Window_Group *glwg; + +public: + + Gl_Stand_In(int W, int H, Fl_Gl_Window_Group *w) + : Fl_Box(0, 0, W, H) { + glwg = w; + } + + int handle(int event) { return glwg->handle_gl(event); } + +protected: + + void draw(void) { Fl::fatal("Never call this"); } + + virtual void dummy(void); +}; + +void Gl_Stand_In::dummy(void) {} + +void Fl_Gl_Window_Group::init(void) { + begin(); // Revert the end() in the Fl_Gl_Window constructor + glstandin = new Gl_Stand_In(w(), h(), this); + children_context = NULL; + texid = 0; + RESET_FIELDS(); +} + +Fl_Gl_Window_Group::~Fl_Gl_Window_Group(void) { + delete glstandin; +} + +void Fl_Gl_Window_Group::adjust_offscr(int w, int h) { + if (imgbuf == NULL) { + // Find maximum width and height across all visible child widgets + // (except for the GL stand-in widget) + Fl_Widget*const* a = array(); + for (int i = children(); i--;) { + Fl_Widget* o = a[i]; + if (o == glstandin) continue; + if (!o->visible()) continue; + int cw = o->w(); + int ch = o->h(); + if (offscr_w < cw) offscr_w = cw; + if (offscr_h < ch) offscr_h = ch; + } + } else { + fl_delete_offscreen(offscr); + } + + if (w > offscr_w) offscr_w = w; + if (h > offscr_h) offscr_h = h; + + offscr = fl_create_offscreen(offscr_w, offscr_h); + + int imgbuf_size = offscr_w * offscr_h * 3; // GL_RGB + imgbuf = (uchar *)realloc(imgbuf, (size_t)imgbuf_size); +} + +void Fl_Gl_Window_Group::show() { + Fl_Gl_Window::show(); + + if (!children_context) { + children_context = fl_create_gl_context(fl_visual); + fl_set_gl_context(this, children_context); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_TEXTURE_RECTANGLE); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glGenTextures(1, &texid); + glBindTexture(GL_TEXTURE_RECTANGLE, texid); + make_current(); + } +} + +void Fl_Gl_Window_Group::hide() { + if (children_context) { + fl_set_gl_context(this, children_context); + glDeleteTextures(1, &texid); + texid = 0; + make_current(); + fl_delete_gl_context(children_context); + children_context = NULL; + } + + Fl_Gl_Window::hide(); +} + +/** + Deletes all child widgets from memory recursively. + + This method differs from the remove() method in that it + affects all child widgets and deletes them from memory. +*/ +void Fl_Gl_Window_Group::clear(void) { + if (imgbuf != NULL) { + fl_delete_offscreen(offscr); + free(imgbuf); + RESET_FIELDS(); + } + remove(glstandin); + Fl_Gl_Window::clear(); + add(glstandin); +} + +int Fl_Gl_Window_Group::handle_gl(int event) { + // Override me + return 0; +} + +void Fl_Gl_Window_Group::flush(void) { + // Fl_Window::make_current() does this, but not + // Fl_Gl_Window::make_current(), and we can't override make_current() + // and have Fl_Gl_Window::flush() call us + + Fl_Window::make_current(); + Fl_Gl_Window::make_current(); + + Fl_Gl_Window::flush(); +} + +void Fl_Gl_Window_Group::draw(void) { + if (damage()) { + draw_gl(); + draw_children(); + } +} + +/** + Draws all children of the group. +*/ +void Fl_Gl_Window_Group::draw_children(void) { + fl_set_gl_context(this, children_context); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glOrtho(0, w(), 0, h(), -1.0, 1.0); + glTranslatef(0.0, h(), 0.0); + glScalef(1.0, -1.0, 1.0); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glViewport(0, 0, w(), h()); + + Fl_Widget*const* a = array(); + for (int i = children(); i--;) draw_child(**a++); + +#if 1 + int err = glGetError(); + if (err != GL_NO_ERROR) { + Fl::warning("OpenGL error after drawing Fl_Gl_Window_Group children: 0x0%X", err); + } +#endif + + make_current(); +} + +/** + This draws a child widget, if it is not clipped. + The damage bits are cleared after drawing. +*/ +void Fl_Gl_Window_Group::draw_child(Fl_Widget& widget) { + if (&widget == glstandin) return; + + if (!widget.visible() || widget.type() >= FL_WINDOW || + !fl_not_clipped(widget.x(), widget.y(), widget.w(), widget.h())) return; + + if (widget.w() > offscr_w || widget.h() > offscr_h) { + adjust_offscr(widget.w(), widget.h()); + } + + int widget_x = widget.x(); + int widget_y = widget.y(); + int widget_w = widget.w(); + int widget_h = widget.h(); + + widget.position(0, 0); + + fl_begin_offscreen(offscr); + fl_rectf(0, 0, widget_w, widget_h, FL_MAGENTA); + widget.clear_damage(FL_DAMAGE_ALL); + widget.draw(); + widget.clear_damage(); + fl_read_image(imgbuf, 0, 0, widget_w, widget_h); + fl_end_offscreen(); + + widget.position(widget_x, widget_y); + +#ifdef USE_GLDRAWPIXELS // Note: glDrawPixels() is deprecated + + glRasterPos2i(widget_x, widget_y); + glPixelZoom(1.0, -1.0); + glDrawPixels(widget_w, widget_h, GL_RGB, GL_UNSIGNED_BYTE, imgbuf); + +#else // ! USE_GLDRAWPIXELS + + glTexImage2D( + GL_TEXTURE_RECTANGLE, + 0, + GL_RGB, + widget_w, widget_h, + 0, + GL_RGB, + GL_UNSIGNED_BYTE, + imgbuf); + +#define CORNER(x,y) glTexCoord2f(x, y); glVertex2f(widget_x + x, widget_y + y) + glBegin(GL_QUADS); + CORNER(0, 0); + CORNER(widget_w, 0); + CORNER(widget_w, widget_h); + CORNER(0, widget_h); + glEnd(); +#undef CORNER + +#endif // ! USE_GLDRAWPIXELS +} + +void Fl_Gl_Window_Group::draw_gl(void) { + Fl::fatal("Fl_Gl_Window_Group::draw_gl() *must* be overriden. Please refer to the documentation."); +} + +#else // ! HAVE_GL + +typedef int no_opengl_support; + +#endif // ! HAVE_GL + +// +// End of "$Id:$". +// diff --git a/solvespace.cpp b/solvespace.cpp index fc8fea38..e0352392 100644 --- a/solvespace.cpp +++ b/solvespace.cpp @@ -722,7 +722,7 @@ void SolveSpace::MenuHelp(int id) { case GraphicsWindow::MNU_ABOUT: Message( -"This is SolveSpace version 2.0.\n" +"This is SolveSpace version " PACKAGE_VERSION ".\n" "\n" "Built " __TIME__ " " __DATE__ ".\n" "\n" diff --git a/solvespace.h b/solvespace.h index 67248a52..500834d3 100644 --- a/solvespace.h +++ b/solvespace.h @@ -85,7 +85,7 @@ inline double ffabs(double v) { return (v > 0) ? v : (-v); } #define isforname(c) (isalnum(c) || (c) == '_' || (c) == '-' || (c) == '#') -#ifdef WIN32 +#if defined(WIN32) && !defined(HAVE_C99_INTEGER_TYPES) // Define some useful C99 integer types. typedef UINT64 uint64_t; typedef INT64 int64_t;