1 //
2 // Main demo program for the Fast Light Tool Kit (FLTK).
3 //
4 // Copyright 1998-2021 by Bill Spitzak and others.
5 //
6 // This library is free software. Distribution and use rights are outlined in
7 // the file "COPYING" which should have been included with this file. If this
8 // file is missing or damaged, see the license at:
9 //
10 // https://www.fltk.org/COPYING.php
11 //
12 // Please see the following page on how to report bugs and issues:
13 //
14 // https://www.fltk.org/bugs.php
15 //
16
17 /*
18 General information on directory structure and file handling.
19
20 The "classic" autotools/make system creates executables in their source
21 folders, i.e. fluid/fluid, test/demo and test/xyz, resp.. The menu file is
22 in folder test/, as is the main demo(.exe) program. In the following text
23 and directory lists all test and demo executables are represented by "demo"
24 and the fluid executable by "fluid", no matter what OS (under Windows: *.exe).
25
26 The CMake build system generates all executables in the build tree and copies
27 the supporting test data files to the build tree as well. This structure is
28 different and needs to be handled separately in this program.
29
30 Additionally, different OS platforms create different types of files, for
31 instance "app bundles" on macOS. All this needs to be considered.
32
33 The overall structure, relative to the FLTK source dir (fltk) and the build
34 tree (build):
35
36 (1) Autotools / Make:
37
38 fltk/fluid fluid (../fluid/fluid)
39 fltk/test demo, demo.menu, working directory, data files
40 fltk/test/images images for help_dialog(.html)
41
42 (2) CMake + make (e.g. Unix)
43
44 build/bin fluid
45 build/bin/test test and demo programs
46 build/data demo.menu, working directory, data files
47 build/data/images images for help_dialog(.html)
48
49 (3) CMake + Visual Studio (TYPE == build type: Debug, Release, ...)
50
51 build/bin/TYPE fluid
52 build/bin/test/TYPE test and demo programs
53 build/data demo.menu, working directory, data files
54 build/data/images images for help_dialog(.html)
55
56 (4) macOS The setup is similar to Windows and Linux:
57 Makefiles: like (1) or (2)
58 Xcode: like (3), i.e. similar to VS layout
59
60 The built executable 'demo' can also be executed with the menu filename
61 as commandline argument. In this case all the support (data) files are
62 expected to be in the same directory as the menu file or relative paths
63 as needed by the test programs, for instance help_dialog which needs
64 help_dialog.html and related image files.
65 */
66
67 #include <stdio.h>
68 #include <string.h>
69 #include <stdlib.h>
70 #if defined(WIN32) && !defined(__CYGWIN__)
71 # include <direct.h>
72 # ifndef __WATCOMC__
73 // Visual C++ 2005 incorrectly displays a warning about the use of POSIX APIs
74 // on Windows, which is supposed to be POSIX compliant...
75 # define chdir _chdir
76 # define putenv _putenv
77 # endif // !__WATCOMC__
78 #elif defined __APPLE__
79 #include <ApplicationServices/ApplicationServices.h>
80 #include <unistd.h> // for chdir()
81 #include <stdio.h>
82 #include <stdlib.h> // for system()
83 #include <string.h>
84 #else
85 # include <unistd.h>
86 #endif
87 #include <errno.h>
88
89 #include <FL/Fl.H>
90 #include <FL/Fl_Double_Window.H>
91 #include <FL/Fl_Box.H>
92 #include <FL/Fl_Button.H>
93 #include <FL/Fl_Choice.H>
94 #include <FL/filename.H>
95 #include <FL/platform.H>
96 #include <FL/fl_ask.H> // fl_alert()
97 #include <FL/fl_utf8.h> // fl_getcwd()
98
99 #define FORM_W 350
100 #define FORM_H 440
101
102 /* Define a macro to decide if a trailing 'd' needs to be removed
103 from the executable file name. Current versions of Visual Studio
104 bundled IDE solutions add a 'd' to the executable file name
105 ('demod.exe') in Debug configurations that needs to be removed.
106 This is no longer true with CMake-generated IDE's starting with
107 FLTK 1.3.6, but in FLTK 1.3.x the OLD behavior is still used if
108 the provided Visual Studio IDE projects are used.
109 */
110
111 #if defined(_MSC_VER) && defined(_DEBUG) && !defined(CMAKE_INTDIR)
112 # define DEBUG_EXE_WITH_D 1
113 #else
114 # define DEBUG_EXE_WITH_D 0
115 #endif
116
117 /* The form description */
118
119 void doexit(Fl_Widget *, void *);
120 void doback(Fl_Widget *, void *);
121 void dobut(Fl_Widget *, long);
doscheme(Fl_Choice * c,void *)122 void doscheme(Fl_Choice *c, void *) {
123 Fl::scheme(c->text(c->value()));
124 }
125
126 Fl_Double_Window *form = 0;
127 Fl_Group *demogrp = 0;
128 Fl_Button *but[9];
129
130 // Allocate space to edit commands and arguments from demo.menu.
131 // We "trust demo.menu" that strings don't overflow
132
133 char cmdbuf[256]; // commandline w/o arguments
134 char params[256]; // commandline arguments
135
136 // Global path variables for all platforms and build systems
137 // to avoid duplication and dynamic allocation
138
139 char app_path [FL_PATH_MAX]; // directory of all demo binaries
140 char fluid_path [FL_PATH_MAX]; // binary directory of fluid
141 char data_path [FL_PATH_MAX]; // working directory of all demos
142 char command [2 * FL_PATH_MAX + 40]; // command to be executed
143
144 // platform specific suffix for executable files
145
146 #ifdef _WIN32
147 # if DEBUG_EXE_WITH_D
148 const char* suffix = "d.exe"; // exe name with trailing 'd'
149 # else
150 const char* suffix = ".exe"; // exe name w/o trailing 'd'
151 # endif
152 #elif defined __APPLE__
153 const char *suffix = ".app";
154 #else
155 const char *suffix = "";
156 #endif
157
158 // CMake defines the "build type" subdirectory for multi configuration
159 // build setups like Visual Studio and Xcode
160
161 #ifdef CMAKE_INTDIR
162 const char *cmake_intdir = "/" CMAKE_INTDIR;
163 #else
164 const char *cmake_intdir = 0;
165 #endif
166
167 // debug output function
debug_var(const char * varname,const char * value)168 void debug_var(const char *varname, const char *value) {
169 printf("%-10s = %s\n", varname, value);
170 fflush(stdout); // Windows needs that
171 }
172
create_the_forms()173 void create_the_forms() {
174 Fl_Widget *obj;
175 form = new Fl_Double_Window(FORM_W,FORM_H);
176 form->size_range(FORM_W,FORM_H,FORM_W+1,FORM_H+1); // XXX: +1 needed or window can't be made resizable later
177 // Parent group for demo
178 demogrp = new Fl_Group(0,0,FORM_W,FORM_H);
179 demogrp->resizable(0);
180 demogrp->begin();
181 // Demo
182 obj = new Fl_Box(FL_FRAME_BOX,10,15,330,40,"FLTK Demonstration");
183 obj->color(FL_GRAY-4);
184 obj->labelsize(24);
185 obj->labelfont(FL_BOLD);
186 obj->labeltype(FL_ENGRAVED_LABEL);
187 obj = new Fl_Box(FL_FRAME_BOX,10,65,330,330,0);
188 obj->color(FL_GRAY-8);
189 obj = new Fl_Button(280,405,60,25,"Exit");
190 obj->callback(doexit);
191 Fl_Choice *choice = new Fl_Choice(75, 405, 100, 25, "Scheme:");
192 choice->labelfont(FL_HELVETICA_BOLD);
193 choice->add("none");
194 choice->add("gtk+");
195 choice->add("gleam");
196 choice->add("plastic");
197 choice->callback((Fl_Callback *)doscheme);
198 Fl::scheme(NULL);
199 if (!Fl::scheme()) choice->value(0);
200 else if (!strcmp(Fl::scheme(), "gtk+")) choice->value(1);
201 else if (!strcmp(Fl::scheme(), "gleam")) choice->value(2);
202 else if (!strcmp(Fl::scheme(), "plastic")) choice->value(3);
203 else choice->value(0);
204 obj = new Fl_Button(10,15,330,380); obj->type(FL_HIDDEN_BUTTON);
205 obj->callback(doback);
206 obj = but[0] = new Fl_Button( 30, 85,90,90);
207 obj = but[1] = new Fl_Button(130, 85,90,90);
208 obj = but[2] = new Fl_Button(230, 85,90,90);
209 obj = but[3] = new Fl_Button( 30,185,90,90);
210 obj = but[4] = new Fl_Button(130,185,90,90);
211 obj = but[5] = new Fl_Button(230,185,90,90);
212 obj = but[6] = new Fl_Button( 30,285,90,90);
213 obj = but[7] = new Fl_Button(130,285,90,90);
214 obj = but[8] = new Fl_Button(230,285,90,90);
215 for (int i=0; i<9; i++) {
216 but[i]->align(FL_ALIGN_WRAP);
217 but[i]->callback(dobut, i);
218 }
219 demogrp->end();
220 // End window
221 form->end();
222 form->resizable(form);
223 }
224
225 /* Maintaining and building up the menus. */
226
227 typedef struct {
228 char name[64];
229 int numb;
230 char iname[9][64];
231 char icommand[9][64];
232 } MENU;
233
234 #define MAXMENU 32
235
236 MENU menus[MAXMENU];
237 int mennumb = 0;
238
239 /* Return the number of a given menu name. */
find_menu(const char * nnn)240 int find_menu(const char* nnn) {
241 int i;
242 for (i=0; i<mennumb; i++)
243 if (strcmp(menus[i].name,nnn) == 0) return i;
244 return -1;
245 }
246
247 /* Create a new menu with name nnn */
create_menu(const char * nnn)248 void create_menu(const char* nnn) {
249 if (mennumb == MAXMENU -1) return;
250 strcpy(menus[mennumb].name,nnn);
251 menus[mennumb].numb = 0;
252 mennumb++;
253 }
254
255 /* Add an item to a menu */
addto_menu(const char * men,const char * item,const char * comm)256 void addto_menu(const char* men, const char* item, const char* comm) {
257 int n = find_menu(men);
258 if (n<0) { create_menu(men); n = find_menu(men); }
259 if (menus[n].numb == 9) return;
260 strcpy(menus[n].iname[menus[n].numb],item);
261 strcpy(menus[n].icommand[menus[n].numb],comm);
262 menus[n].numb++;
263 }
264
265 /* Button to Item conversion and back. */
266
267 int b2n[][9] = {
268 { -1, -1, -1, -1, 0, -1, -1, -1, -1},
269 { -1, -1, -1, 0, -1, 1, -1, -1, -1},
270 { 0, -1, -1, -1, 1, -1, -1, -1, 2},
271 { 0, -1, 1, -1, -1, -1, 2, -1, 3},
272 { 0, -1, 1, -1, 2, -1, 3, -1, 4},
273 { 0, -1, 1, 2, -1, 3, 4, -1, 5},
274 { 0, -1, 1, 2, 3, 4, 5, -1, 6},
275 { 0, 1, 2, 3, -1, 4, 5, 6, 7},
276 { 0, 1, 2, 3, 4, 5, 6, 7, 8}
277 };
278 int n2b[][9] = {
279 { 4, -1, -1, -1, -1, -1, -1, -1, -1},
280 { 3, 5, -1, -1, -1, -1, -1, -1, -1},
281 { 0, 4, 8, -1, -1, -1, -1, -1, -1},
282 { 0, 2, 6, 8, -1, -1, -1, -1, -1},
283 { 0, 2, 4, 6, 8, -1, -1, -1, -1},
284 { 0, 2, 3, 5, 6, 8, -1, -1, -1},
285 { 0, 2, 3, 4, 5, 6, 8, -1, -1},
286 { 0, 1, 2, 3, 5, 6, 7, 8, -1},
287 { 0, 1, 2, 3, 4, 5, 6, 7, 8}
288 };
289
290 /* Transform a button number to an item number when there are
291 maxnumb items in total. -1 if the button should not exist. */
but2numb(int bnumb,int maxnumb)292 int but2numb(int bnumb, int maxnumb)
293 { return b2n[maxnumb][bnumb]; }
294
295 /* Transform an item number to a button number when there are
296 maxnumb items in total. -1 if the item should not exist. */
numb2but(int inumb,int maxnumb)297 int numb2but(int inumb, int maxnumb)
298 { return n2b[maxnumb][inumb]; }
299
300 /* Pushing and Popping menus */
301
302 char stack[64][32];
303 int stsize = 0;
304
305 /* Push a menu to be visible */
push_menu(const char * nnn)306 void push_menu(const char* nnn) {
307 int n,i,bn;
308 int men = find_menu(nnn);
309 if (men < 0) return;
310 n = menus[men].numb;
311 for (i=0; i<9; i++) but[i]->hide();
312 for (i=0; i<n; i++)
313 {
314 bn = numb2but(i,n-1);
315 but[bn]->show();
316 but[bn]->label(menus[men].iname[i]);
317 if (menus[men].icommand[i][0] != '@') but[bn]->tooltip(menus[men].icommand[i]);
318 else but[bn]->tooltip(0);
319 }
320 if (stack[stsize]!=nnn)
321 strcpy(stack[stsize],nnn);
322 stsize++;
323 }
324
325 /* Pop a menu */
pop_menu()326 void pop_menu() {
327 if (stsize<=1) return;
328 stsize -= 2;
329 push_menu(stack[stsize]);
330 }
331
332 /* The callback Routines */
333
334 /* Handle a button push */
dobut(Fl_Widget *,long arg)335 void dobut(Fl_Widget *, long arg) {
336 int men = find_menu(stack[stsize-1]);
337 int n = menus[men].numb;
338 int bn = but2numb( (int) arg, n-1);
339
340 // menu ?
341
342 if (menus[men].icommand[bn][0] == '@') {
343 push_menu(menus[men].icommand[bn]);
344 return;
345 }
346
347 // not a menu: run test/demo/fluid executable
348 // find and separate "command" and "params"
349
350 // skip leading spaces in command
351 char *start_command = menus[men].icommand[bn];
352 while (*start_command == ' ') ++start_command;
353
354 strcpy(cmdbuf, start_command); // here still full command w/params
355
356 // find the space between the command and parameters if one exists
357 char *start_params = strchr(cmdbuf, ' ');
358 if (start_params) {
359 *start_params = '\0'; // terminate command
360 start_params++; // skip space
361 strcpy(params, start_params); // copy parameters
362 } else {
363 params[0] = '\0'; // empty string
364 }
365
366 // select application path: either app_path or fluid_path
367
368 const char *path = app_path;
369 if (!strncmp(cmdbuf, "fluid", 5))
370 path = fluid_path;
371
372 // format commandline with optional parameters
373
374 #if defined(__APPLE__) // macOS
375
376 if (params[0]) {
377 // we assume that we have only one argument which is a filename in 'data_path'
378 sprintf(command, "open '%s/%s%s' --args '%s/%s'", path, cmdbuf, suffix, data_path, params);
379 } else {
380 sprintf(command, "open '%s/%s%s'", path, cmdbuf, suffix);
381 }
382
383 #else // other platforms
384
385 if (params[0])
386 sprintf(command, "%s/%s%s %s", path, cmdbuf, suffix, params);
387 else
388 sprintf(command, "%s/%s%s", path, cmdbuf, suffix);
389
390 #endif
391
392 // finally, execute program (the system specific part)
393
394 #ifdef _WIN32
395
396 STARTUPINFO suInfo; // Process startup information
397 PROCESS_INFORMATION prInfo; // Process information
398
399 memset(&suInfo, 0, sizeof(suInfo));
400 suInfo.cb = sizeof(suInfo);
401
402 debug_var("Command", command);
403
404 BOOL stat = CreateProcess(NULL, command, NULL, NULL, FALSE,
405 NORMAL_PRIORITY_CLASS, NULL,
406 NULL, &suInfo, &prInfo);
407 if (!stat) {
408 DWORD err = GetLastError();
409 fl_alert("Error starting process, error #%lu\n'%s'", err, command);
410 }
411
412 #elif defined __APPLE__
413
414 debug_var("Command", command);
415
416 system(command);
417
418 #else // other platforms (Unix, Linux)
419
420 strcat(command, " &"); // run in background
421
422 debug_var("Command", command);
423
424 if (system(command) == -1) {
425 fl_alert("Could not start program, errno = %d\n'%s'", errno, command);
426 }
427
428 #endif // _WIN32
429
430 }
431
doback(Fl_Widget *,void *)432 void doback(Fl_Widget *, void *) {pop_menu();}
433
doexit(Fl_Widget *,void *)434 void doexit(Fl_Widget *, void *) {exit(0);}
435
436 /*
437 Load the menu file. Returns whether successful.
438 */
load_the_menu(const char * const menu)439 int load_the_menu(const char * const menu) {
440 FILE *fin = 0;
441 char line[256], mname[64],iname[64],cname[64];
442 int i, j;
443
444 fin = fl_fopen(menu, "r");
445
446 if (fin == NULL)
447 return 0;
448
449 for (;;) {
450 if (fgets(line,256,fin) == NULL) break;
451 // remove all carriage returns that Cygwin may have inserted
452 char *s = line, *d = line;
453 for (;;++d) {
454 while (*s=='\r') s++;
455 *d = *s++;
456 if (!*d) break;
457 }
458 // interpret the line
459 j = 0; i = 0;
460 while (line[i] == ' ' || line[i] == '\t') i++;
461 if (line[i] == '\n') continue;
462 if (line[i] == '#') continue;
463 while (line[i] != ':' && line[i] != '\n') mname[j++] = line[i++];
464 mname[j] = '\0';
465 if (line[i] == ':') i++;
466 j = 0;
467 while (line[i] != ':' && line[i] != '\n') {
468 if (line[i] == '\\') {
469 i++;
470 if (line[i] == 'n') iname[j++] = '\n';
471 else iname[j++] = line[i];
472 i++;
473 } else
474 iname[j++] = line[i++];
475 }
476 iname[j] = '\0';
477 if (line[i] == ':') i++;
478 j = 0;
479 while (line[i] != ':' && line[i] != '\n') cname[j++] = line[i++];
480 cname[j] = '\0';
481 addto_menu(mname,iname,cname);
482 }
483 fclose(fin);
484 return 1;
485 }
486
487 // Fix '\' in Windows paths (convert to '/') and cut off filename (optional, default)
fix_path(char * path,int strip_filename=1)488 void fix_path(char *path, int strip_filename = 1) {
489 if (!path[0])
490 return;
491 #ifdef _WIN32 // convert '\' to '/'
492 char *p = path;
493 while (*p) {
494 if (*p == '\\')
495 *p = '/';
496 p++;
497 }
498 #endif // _WIN32
499 if (strip_filename) {
500 char *pos = strrchr(path, '/');
501 if (pos)
502 *pos = 0;
503 }
504 }
505
main(int argc,char ** argv)506 int main(int argc, char **argv) {
507
508 putenv((char *)"FLTK_DOCDIR=../documentation/html"); // used by fluid
509
510 char menu[FL_PATH_MAX];
511
512 // construct app_path for all executable files
513
514 fl_filename_absolute(app_path, sizeof(app_path), argv[0]);
515 #ifdef __APPLE__
516 char *q = strstr(app_path, "/Contents/MacOS/");
517 if (q) *q = 0;
518 #endif
519 fix_path(app_path);
520
521 // fluid's path is relative to app_path:
522 // - "../fluid" for autoconf/make builds
523 // - ".." (parent directory) for single configuration CMake builds
524 // - "../../$CMAKE_INTDIR" for multi-config (Visual Studio or Xcode) CMake builds
525
526 strcpy(fluid_path, app_path);
527
528 if (cmake_intdir)
529 fix_path(fluid_path); // remove intermediate (build type) folder, e.g. "/Debug"
530
531 fix_path(fluid_path); // remove folder name ("test")
532
533 #if !defined(GENERATED_BY_CMAKE)
534 strcat(fluid_path, "/fluid");
535 #else
536 // CMake: potentially Visual Studio or Xcode (multi config)
537 if (cmake_intdir)
538 strcat(fluid_path, cmake_intdir); // append e.g. "/Debug"
539
540 #endif // GENERATED_BY_CMAKE
541
542 // construct data_path for the menu file and all resources (data files)
543 // CMake: replace "/bin/test/*" with "/data"
544 // autotools: use app_path directly
545
546 strcpy(data_path, app_path);
547
548 #if defined(GENERATED_BY_CMAKE)
549 {
550 char *pos = strstr(data_path, "/bin/test");
551 if (pos)
552 strcpy(pos, "/data");
553 }
554 #endif // GENERATED_BY_CMAKE
555
556 // Construct the menu file name, optionally overridden by command args.
557 // Use data_path and append "/<exe-file-name>.menu"
558
559 const char *fn = fl_filename_name(argv[0]);
560 strcpy(menu, data_path);
561 strcat(menu, "/");
562 strcat(menu, fn);
563 #if DEBUG_EXE_WITH_D
564 char* dexe_found = strstr(menu, "d.exe");
565 if (dexe_found) strcpy(dexe_found, ".exe"); // strip trailing 'd'
566 #endif
567 fl_filename_setext(menu, sizeof(menu), ".menu");
568
569 // parse commandline
570
571 int i = 0;
572 if (!Fl::args(argc, argv, i) || i < argc-1)
573 Fl::fatal("Usage: %s <switches> <menufile>\n%s", argv[0], Fl::help);
574 if (i < argc) {
575 // override menu file *and* data path !
576 fl_filename_absolute(menu, sizeof(menu), (const char *)argv[i]);
577 strcpy(data_path, menu);
578 fix_path(data_path);
579 }
580
581 // set current work directory to 'data_path'
582
583 if (chdir(data_path) == -1) { /* ignore */ }
584
585 // Create forms first
586 create_the_forms();
587
588 {
589 char cwd[1024];
590 fl_getcwd(cwd, sizeof(cwd));
591 fix_path(cwd, 0);
592
593 debug_var("app_path", app_path);
594 debug_var("fluid_path", fluid_path);
595 debug_var("data_path", data_path);
596 debug_var("menu file", menu);
597 debug_var("cwd", cwd);
598 printf("\n");
599 fflush(stdout);
600 }
601
602 if (!load_the_menu(menu))
603 Fl::fatal("Can't open menu file '%s'", menu);
604
605 push_menu("@main");
606 form->show(argc, argv);
607 Fl::run();
608 return 0;
609 }
610