Merge pull request #111 - refactor test/demo.cxx

Simplify, clarify and fix bugs in test/demo.cxx

As discussed and confirmed test/demo.cxx works now in all known and
supported build systems.
This commit is contained in:
Albrecht Schlosser 2020-07-21 22:46:48 +02:00 committed by GitHub
commit b2ad5e4cfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 349 additions and 199 deletions

View File

@ -15,8 +15,8 @@
#
#######################################################################
set (EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/../bin/examples)
set (TESTFILE_PATH ${EXECUTABLE_OUTPUT_PATH})
set (EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/../bin)
set (TESTFILE_PATH ${CMAKE_CURRENT_BINARY_DIR}/../data)
include (../CMake/FLTK-Functions.cmake)
include (../CMake/fl_create_example.cmake)
@ -204,13 +204,11 @@ endif (NOT ANDROID)
# We need some support files for the demo programs:
# Note: this is incomplete as of 11 Feb 2015
# Todo: currently all files are copied, but some of them need configuration:
# - demo.menu: fluid can't be started (wrong path)
# Note: this is incomplete as of July 2020
# Todo: currently all files are copied, but some of them may need configuration:
# - demo.menu: help_dialog (help_dialog.html) can't find its images (not copied)
# - maybe more ...
# create data directory for test and demo programs
# create data directory for test and demo programs if it doesn't exist
file (MAKE_DIRECTORY ${TESTFILE_PATH})
# copy the test files
@ -221,13 +219,16 @@ file (COPY
DESTINATION ${TESTFILE_PATH}
)
# the main test program 'demo' needs additional hints and configurations
target_compile_definitions (demo PRIVATE GENERATED_BY_CMAKE)
target_compile_definitions (demo PRIVATE CMAKE_SOURCE_PATH="${CMAKE_CURRENT_SOURCE_DIR}")
# Apple macOS creates bundles instead of executables and needs a little bit
# more help for demos to run correctly
if (APPLE AND (NOT OPTION_APPLE_X11) AND (NOT OPTION_APPLE_SDL))
target_compile_definitions (demo PUBLIC USING_XCODE)
# make the menu structure part of the app
target_sources (demo PRIVATE demo.menu)
set_target_properties (demo PROPERTIES MACOSX_BUNDLE TRUE RESOURCE demo.menu)

View File

@ -14,9 +14,56 @@
// https://www.fltk.org/bugs.php
//
/*
General information on directory structure and file handling.
The "classic" autotools/make system creates executables in their source
folders, i.e. fluid/fluid, test/demo and test/xyz, resp.. The menu file is
in folder test/, as is the main demo(.exe) program. In the following text
and directory lists all test and demo executables are represented by "demo"
and the fluid executable by "fluid", no matter what OS (under Windows: *.exe).
The CMake build system generates all executables in the build tree and copies
the supporting test data files to the build tree as well. This structure is
different and needs to be handled separately in this program.
Additionally, different OS platforms create different types of files, for
instance "app bundles" on macOS. All this needs to be considered.
The overall structure, relative to the FLTK source dir (fltk) and the build
tree (build):
(1) Autotools / Make:
fltk/fluid fluid (../fluid/fluid)
fltk/test demo, demo.menu, working directory, data files
fltk/documentation/src images for help_dialog(.html)
(2) CMake + make (e.g. Unix)
build/bin fluid, demo
build/data demo.menu, working directory, data files
(3) CMake + Visual Studio (TYPE == build type: Debug, Release, ...)
build/bin/TYPE fluid, demo
build/data demo.menu, working directory, data files
(4) macOS The setup is similar to Windows and Linux:
Makefiles: like (1) or (2)
Xcode: like (3), i.e. similar to VS layout
The built executable 'demo' can also be executed with the menu filename
as commandline argument. In this case all the support (data) files are
expected to be in the same directory as the menu file or relative paths
as needed by the test programs, for instance help_dialog which needs
help_dialog.html and related image files.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#if defined __APPLE__
#include <ApplicationServices/ApplicationServices.h>
@ -26,24 +73,18 @@
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Menu_Button.H> // right click popup menu
#include <FL/Fl_Choice.H>
#include <FL/Fl_Simple_Terminal.H> // tty
#include <FL/filename.H>
#include <FL/platform.H>
#include <FL/fl_ask.H> // fl_alert()
#include <FL/fl_utf8.h> // fl_getcwd()
/* Define a macro to decide whether a trailing 'd' needs to be removed
from the executable file name. Previous versions of Visual Studio
added a 'd' to the executable file name ('demod.exe') in Debug
configurations that needed to be removed.
This is no longer true with CMake-generated IDE's since FLTK 1.4.
Just in case we add it again: leave macro DEBUG_EXE_WITH_D defined
and leave the code using this macro as-is.
*/
// #if defined(_MSC_VER) && defined(_DEBUG) // Visual Studio in Debug mode
// # define DEBUG_EXE_WITH_D 1
// #else
# define DEBUG_EXE_WITH_D 0
// #endif
#define FORM_W 350
#define FORM_H 440
#define TTY_W int(FORM_W*2.5)
#define TTY_H 200
/* The form description */
@ -54,12 +95,81 @@ void doscheme(Fl_Choice *c, void *) {
Fl::scheme(c->text(c->value()));
}
Fl_Double_Window *form;
Fl_Double_Window *form = 0;
Fl_Group *demogrp = 0;
Fl_Simple_Terminal *tty = 0;
Fl_Button *but[9];
// Allocate space to edit commands and arguments from demo.menu.
// We "trust demo.menu" that strings don't overflow
char cmdbuf[256]; // commandline w/o arguments
char params[256]; // commandline arguments
// Global path variables for all platforms and build systems
// to avoid duplication and dynamic allocation
char src_path [FL_PATH_MAX]; // directory of this souce file
char app_path [FL_PATH_MAX]; // directory of all demo binaries
char fluid_path [FL_PATH_MAX]; // binary directory of fluid
char data_path [FL_PATH_MAX]; // working directory of all demos
char command [2 * FL_PATH_MAX + 40]; // command to be executed
// platform specific suffix for executable files
#ifdef _WIN32
const char *suffix = ".exe";
#elif defined __APPLE__
const char *suffix = ".app";
#else
const char *suffix = "";
#endif
// debug output function
void debug_var(const char *varname, const char *value) {
tty->printf("%-10s = '%s'\n", varname, value);
}
// Show or hide the tty window
void show_tty(int val) {
if ( val ) {
form->size_range(FORM_W,FORM_H+TTY_H,0,0); // allow resizing
form->size(TTY_W,FORM_H+TTY_H); // demo + height for tty
demogrp->size(FORM_W,FORM_H);
tty->show(); // show tty
tty->resize(0, FORM_H, TTY_W, TTY_H); // force tty position
} else {
form->size_range(FORM_W,FORM_H,FORM_W,FORM_H); // no resizing
tty->hide(); // hide tty
form->size(FORM_W, FORM_H); // normal demo size
}
demogrp->size(FORM_W, FORM_H);
form->init_sizes();
}
// Right click popup menu handler
void popup_menu_cb(Fl_Widget*, void *userdata) {
const char *cmd = (const char*)userdata;
if ( strcmp(cmd, "showtty")==0 ) { show_tty(1); }
if ( strcmp(cmd, "hidetty")==0 ) { show_tty(0); }
}
void create_the_forms() {
Fl_Widget *obj;
form = new Fl_Double_Window(350, 440);
Fl_Menu_Button *popup;
form = new Fl_Double_Window(FORM_W,FORM_H);
form->size_range(FORM_W,FORM_H,FORM_W+1,FORM_H+1); // XXX: +1 needed or window can't be made resizable later
// Small terminal window parented to window, not demogrp
tty = new Fl_Simple_Terminal(0, form->h(), form->w(), form->h());
tty->history_lines(50);
tty->ansi(true);
tty->hide();
tty->textsize(10);
// Parent group for demo
demogrp = new Fl_Group(0,0,FORM_W,FORM_H);
demogrp->resizable(0);
demogrp->begin();
// Demo
obj = new Fl_Box(FL_FRAME_BOX,10,15,330,40,"FLTK Demonstration");
obj->color(FL_GRAY-4);
obj->labelsize(24);
@ -97,7 +207,16 @@ void create_the_forms() {
but[i]->align(FL_ALIGN_WRAP);
but[i]->callback(dobut, i);
}
demogrp->end();
// Right click popup menu
popup = new Fl_Menu_Button(0,0,FORM_W,FORM_H);
popup->box(FL_NO_BOX);
popup->type(Fl_Menu_Button::POPUP3); // pop menu on right-click
popup->add("Show debug terminal", 0, popup_menu_cb, (void*)"showtty");
popup->add("Hide debug terminal", 0, popup_menu_cb, (void*)"hidetty");
// End window
form->end();
form->resizable(tty);
}
/* Maintaining and building up the menus. */
@ -214,190 +333,116 @@ void dobut(Fl_Widget *, long arg) {
int men = find_menu(stack[stsize-1]);
int n = menus[men].numb;
int bn = but2numb( (int) arg, n-1);
// menu ?
if (menus[men].icommand[bn][0] == '@') {
push_menu(menus[men].icommand[bn]);
return;
}
// not a menu: run test/demo/fluid executable
// find and separate "command" and "params"
// skip leading spaces in command
char *start_command = menus[men].icommand[bn];
while (*start_command == ' ') ++start_command;
strcpy(cmdbuf, start_command); // here still full command w/params
// find the space between the command and parameters if one exists
char *start_params = strchr(cmdbuf, ' ');
if (start_params) {
*start_params = '\0'; // terminate command
start_params++; // skip space
strcpy(params, start_params); // copy parameters
} else {
params[0] = '\0'; // empty string
}
// select application path: either app_path or fluid_path
const char *path = app_path;
if (!strncmp(cmdbuf, "fluid", 5))
path = fluid_path;
// format commandline with optional parameters
#if defined(__APPLE__) // macOS
if (params[0]) {
// we assume that we have only one argument which is a filename in 'data_path'
sprintf(command, "open '%s/%s%s' --args '%s/%s'", path, cmdbuf, suffix, data_path, params);
} else {
sprintf(command, "open '%s/%s%s'", path, cmdbuf, suffix);
}
#else // other platforms
if (params[0])
sprintf(command, "%s/%s%s %s", path, cmdbuf, suffix, params);
else
sprintf(command, "%s/%s%s", path, cmdbuf, suffix);
#endif
// finally, execute program (the system specific part)
#ifdef _WIN32
STARTUPINFO suInfo; // Process startup information
PROCESS_INFORMATION prInfo; // Process information
# if DEBUG_EXE_WITH_D
const char *exe = "d.exe"; // exe name with trailing 'd'
# else
const char *exe = ".exe"; // exe name w/o trailing 'd'
# endif
memset(&suInfo, 0, sizeof(suInfo));
suInfo.cb = sizeof(suInfo);
int icommand_length = strlen(menus[men].icommand[bn]);
debug_var("Command", command);
char* copy_of_icommand = new char[icommand_length+1];
strcpy(copy_of_icommand,menus[men].icommand[bn]);
// On Windows the .exe suffix needs to be appended to the command
// whilst leaving any additional parameters unchanged - this
// is required to handle the correct conversion of cases such as :
// `../fluid/fluid valuators.fl' to '../fluid/fluid.exe valuators.fl'.
// skip leading spaces.
char* start_command = copy_of_icommand;
while (*start_command == ' ') ++start_command;
// find the space between the command and parameters if one exists.
char* start_parameters = strchr(start_command,' ');
char* command = new char[icommand_length+6]; // 6 for extra 'd.exe\0'
if (start_parameters==NULL) { // no parameters required.
sprintf(command, "%s%s", start_command, exe);
} else { // parameters required.
// break the start_command at the intermediate space between
// start_command and start_parameters.
*start_parameters = 0;
// move start_paremeters to skip over the intermediate space.
++start_parameters;
sprintf(command, "%s%s %s", start_command, exe, start_parameters);
BOOL stat = CreateProcess(NULL, command, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS, NULL,
NULL, &suInfo, &prInfo);
if (!stat) {
DWORD err = GetLastError();
fl_alert("Error starting process, error #%d\n'%s'", err, command);
}
CreateProcess(NULL, command, NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS, NULL, NULL, &suInfo, &prInfo);
delete[] command;
delete[] copy_of_icommand;
#elif defined __APPLE__
/*
Starting with version 1.4.0, FLTK uses CMake as the only supported build
system. On macOS, the app developer is expected to run CMake in a
directory named './build/Xcode' or './build/Makefiles' to generate the
build environment.
When building FLTK in the next step, teh macOS app bundles are then
stored in either:
'./build/Xcode/bin/examples/hello.app/' for Makefiles
'./build/Xcode/bin/examples/Debug/hello.app/' for XCode Debug
or
'./build/Xcode/bin/examples/Release/hello.app/' as a symbolic
into the Archive system of macOS
debug_var("Command", command);
'Demo' needs to find and run all of these app bundles, some requiring
an additional file name and path for resource files.
This is my attempt to find the bundles and resources so that Demo.app
and all its dependencies will run without any further configuration.
They will stop running however if any of the bundles or rresources
are moved.
*/
{
char src_path[PATH_MAX];
char app_path[PATH_MAX];
char app_name[PATH_MAX];
char command[2*PATH_MAX+2];
char *cmd = strdup(menus[men].icommand[bn]);
char *args = strchr(cmd, ' ');
/*
Get the path to the source code at compile time. This is where the other
resources are located.
*/
strcpy(src_path, __FILE__);
char *src_path_end = (char*)fl_filename_name(src_path);
if (src_path_end) *src_path_end = 0;
/*
All example app bundles are in the same directory as 'Demo', so set the
current dir to the location of Demo.app .
Starting with macOS 10.12, the actual location of the app has a randomized
path to fix a vulnerability. This still works in Debug mode which is
*/
{
app_path[0] = 0;
CFBundleRef app = CFBundleGetMainBundle();
CFURLRef url = CFBundleCopyBundleURL(app);
CFStringRef cc_app_path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
CFRelease(url);
CFStringGetCString(cc_app_path, app_path, 2048, kCFStringEncodingUTF8);
CFRelease(cc_app_path);
if (app_path[0]) {
char *app_path_end = (char*)fl_filename_name(app_path);
if (app_path_end) *app_path_end = 0;
fl_chdir(app_path);
}
}
// extract the executable name from the command in the menu file
strcpy(app_name, cmd);
// remove any additioanl command line arguments
if (args) app_name[args-cmd] = 0;
// make the file name into a bundle name
strcat(app_name, ".app");
if (args) {
if (strcmp(app_name, "../fluid/fluid.app")==0) {
// CMake -G 'Unix Makefiles' ... : ./bin/fluid.app
// CMake -G 'Xcode' ... : ./bin/Debug/fluid.app or ./bin/Release/fluid.app
// so removing the '/example' path segment from the app_path should
// always work.
char *examples = strstr(app_path, "/examples");
if (examples) {
memmove(examples, examples+9, strlen(examples+9)+1);
}
sprintf(command, "open '%sfluid.app' --args '%s%s'", app_path, src_path, args+1);
} else {
// we assume that we have only one argument which is a filename, so we add a path
sprintf(command, "open '%s%s' --args '%s%s'", app_path, app_name, src_path, args+1);
}
} else {
sprintf(command, "open '%s%s'", app_path, app_name);
}
system(command);
free(cmd);
#else // other platforms (Unix, Linux)
strcat(command, " &"); // run in background
debug_var("Command", command);
if (system(command) == -1) {
fl_alert("Could not start program, errno = %d\n'%s'", errno, command);
}
#else // Non Windows systems.
int icommand_length = strlen(menus[men].icommand[bn]);
char* command = new char[icommand_length+5]; // 5 for extra './' and ' &\0'
sprintf(command, "./%s &", menus[men].icommand[bn]);
if (system(command)==-1) { /* ignore */ }
delete[] command;
#endif // _WIN32
}
}
void doback(Fl_Widget *, void *) {pop_menu();}
void doexit(Fl_Widget *, void *) {exit(0);}
/* Load the menu file. Returns whether successful. */
int load_the_menu(char* fname) {
/*
Load the menu file. Returns whether successful.
*/
int load_the_menu(char *menu) {
FILE *fin = 0;
char line[256], mname[64],iname[64],cname[64];
int i, j;
fin = fl_fopen(fname,"r");
#if defined ( __APPLE__ )
if (fin == NULL) {
// mac os bundle menu detection:
char* pos = strrchr(fname,'/');
if (!pos) return 0;
*pos = '\0';
pos = strrchr(fname,'/');
if (!pos) return 0;
strcpy(pos,"/Resources/demo.menu");
fin = fl_fopen(fname,"r");
}
#endif
if (fin == NULL) {
fin = fl_fopen(menu, "r");
if (fin == NULL)
return 0;
}
for (;;) {
if (fgets(line,256,fin) == NULL) break;
// remove all carriage returns that Cygwin may have inserted
@ -436,32 +481,136 @@ int load_the_menu(char* fname) {
return 1;
}
// Fix '\' in Windows paths (convert to '/') and cut off filename (optional, default)
void fix_path(char *path, int strip_filename = 1) {
if (!path[0])
return;
#ifdef _WIN32 // convert '\' to '/'
char *p = path;
while (*p) {
if (*p == '\\')
*p = '/';
p++;
}
#endif // _WIN32
if (strip_filename) {
char *pos = strrchr(path, '/');
if (pos)
*pos = 0;
}
}
int main(int argc, char **argv) {
fl_putenv("FLTK_DOCDIR=../documentation/html");
char buf[FL_PATH_MAX];
strcpy(buf, argv[0]);
#if DEBUG_EXE_WITH_D
// MS_Visual Studio appends a 'd' to debugging executables. Remove it.
fl_filename_setext( buf, "" );
buf[ strlen(buf)-1 ] = 0;
fl_putenv("FLTK_DOCDIR=../documentation/html"); // not sure if this is needed
char menu[FL_PATH_MAX];
// construct app_path for all executable files
#ifdef __APPLE__
{
// Starting with macOS 10.12, the actual location of the app has a
// randomized path to fix a vulnerability.
// We need some "Apple magic" ;-) to find the actual app_path.
app_path[0] = 0;
CFBundleRef app = CFBundleGetMainBundle();
CFURLRef url = CFBundleCopyBundleURL(app);
CFStringRef cc_app_path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
CFRelease(url);
CFStringGetCString(cc_app_path, app_path, 2048, kCFStringEncodingUTF8);
CFRelease(cc_app_path);
}
#else
fl_filename_absolute(app_path, sizeof(app_path), argv[0]);
#endif
fl_filename_setext(buf,".menu");
char *fname = buf;
fix_path(app_path);
// construct src_path in case we want to use it (macOS ?)
#if defined(GENERATED_BY_CMAKE)
strcpy(src_path, CMAKE_SOURCE_PATH);
#else
strcpy(src_path, app_path);
#endif
fix_path(src_path, 0);
// fluid's path is the same for CMake builds but not for autoconf/make
strcpy(fluid_path, app_path);
#if !defined(GENERATED_BY_CMAKE)
fix_path(fluid_path); // removes folder name (test)
strcat(fluid_path, "/fluid");
#endif
// construct data_path for the menu file and all resources (data files)
// CMake: replace "/bin/*"" with "/data"
// autotools: use app_path directly
strcpy(data_path, app_path);
#if defined(GENERATED_BY_CMAKE)
{
char *pos = strstr(data_path, "/bin");
if (pos)
strcpy(pos, "/data");
}
#endif
// construct the menu file name, optionally overridden by command args
// CMake: use data_path instead of app_path
const char *fn = fl_filename_name(argv[0]);
#if defined(GENERATED_BY_CMAKE)
strcpy(menu, data_path);
#else
strcpy(menu, app_path);
#endif
// append "/<exe-file-name>.menu"
strcat(menu, "/");
strcat(menu, fn);
fl_filename_setext(menu, sizeof(menu), ".menu");
// parse commandline
int i = 0;
if (!Fl::args(argc,argv,i) || i < argc-1)
Fl::fatal("Usage: %s <switches> <menufile>\n%s", argv[0], Fl::help);
if (i < argc) fname = argv[i];
if (i < argc) {
// override menu file *and* data path !
fl_filename_absolute(menu, sizeof(menu), (const char *)argv[i]);
strcpy(data_path, menu);
fix_path(data_path);
}
// set current work directory to 'app_path'
if (fl_chdir(data_path) == -1) { /* ignore */ }
// Create forms first
// tty needs to exist before we can print debug msgs
//
create_the_forms();
if (!load_the_menu(fname)) Fl::fatal("Can't open %s",fname);
if (buf != fname)
strcpy(buf,fname);
const char *c = fl_filename_name(buf);
if (c > buf) {
buf[c-buf] = 0;
if (fl_chdir(buf) == -1) { /* ignore */ }
{
char cwd[1024];
debug_var("src_path", src_path);
debug_var("app_path", app_path);
debug_var("fluid_path", fluid_path);
debug_var("data_path", data_path);
debug_var("Menu file", menu);
debug_var("Cwd", fl_getcwd(cwd,sizeof(cwd)));
tty->printf("\n");
}
// note: load_the_menu() *may* change the `menu` buffer contents !
if (!load_the_menu(menu))
Fl::fatal("Can't open %s", menu);
push_menu("@main");
form->show(argc,argv);
Fl::run();

View File

@ -58,7 +58,7 @@
@u:fast && slow widgets:fast_slow
@u:inactive:inactive
@main:Fluid\n(UI design tool):../fluid/fluid valuators.fl
@main:Fluid\n(UI design tool):fluid valuators.fl
@main:Cool\nDemos...:@e
@e:X Color\nBrowser:colbrowser