solvespace/fltk/fltkmain.cpp

1280 lines
33 KiB
C++
Raw Normal View History

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
2013-10-28 05:28:42 +00:00
//-----------------------------------------------------------------------------
// Our main() function, and FLTK-specific stuff to set up our windows and
// otherwise handle our interface to the operating system. Everything
// outside fltk/... should be standard C++ and OpenGL.
//
// Copyright 2008-2013 Jonathan Westhues.
// Copyright 2013 Daniel Richard G. <skunk@iSKUNK.ORG>
//-----------------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#ifdef HAVE_FONTCONFIG_FONTCONFIG_H
# include <fontconfig/fontconfig.h>
#endif
#ifdef HAVE_LIBSPNAV
# include <spnav.h>
# ifndef SI_APP_FIT_BUTTON
# define SI_APP_FIT_BUTTON 31
# endif
#endif
#include <fltk/xFl_Gl_Window_Group.H> //#include <FL/Fl_Gl_Window_Group.H>
#include <FL/Fl_Native_File_Chooser.H>
#include <FL/Fl_Preferences.H>
#include <FL/Fl_Sys_Menu_Bar.H>
#include <FL/forms.H> // for fl_gettime()
#include <FL/gl.h>
#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;
}