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;