summaryrefslogtreecommitdiff
path: root/fluid
diff options
context:
space:
mode:
authorGreg Ercolano <erco@seriss.com>2016-07-18 21:12:25 +0000
committerGreg Ercolano <erco@seriss.com>2016-07-18 21:12:25 +0000
commit8850c5c822ce0878b4d808c46b25463136a69231 (patch)
tree07a74f064beca50b2e7ac8f48a22e244eb4a0437 /fluid
parentbcb75b518f47583a1265edfcd1984f6a675cbb60 (diff)
Adds external editor capability to fluid for all platforms.
Solves STR#3213. [CORRECTED] git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3-porting@11818 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
Diffstat (limited to 'fluid')
-rw-r--r--fluid/CMakeLists.txt2
-rw-r--r--fluid/ExternalCodeEditor_UNIX.cxx436
-rw-r--r--fluid/ExternalCodeEditor_UNIX.h51
-rw-r--r--fluid/ExternalCodeEditor_WIN32.cxx548
-rw-r--r--fluid/ExternalCodeEditor_WIN32.h63
-rw-r--r--fluid/Fl_Function_Type.cxx33
-rw-r--r--fluid/Fl_Type.cxx1
-rw-r--r--fluid/Fl_Type.h50
-rw-r--r--fluid/Makefile2
-rw-r--r--fluid/about_panel.cxx7
-rw-r--r--fluid/alignment_panel.cxx49
-rw-r--r--fluid/alignment_panel.fl41
-rw-r--r--fluid/alignment_panel.h4
-rw-r--r--fluid/fluid.cxx55
-rw-r--r--fluid/print_panel.cxx18
15 files changed, 1343 insertions, 17 deletions
diff --git a/fluid/CMakeLists.txt b/fluid/CMakeLists.txt
index 2041ccc96..846ae4d5a 100644
--- a/fluid/CMakeLists.txt
+++ b/fluid/CMakeLists.txt
@@ -22,6 +22,8 @@ set(CPPFILES
Fl_Group_Type.cxx
Fl_Menu_Type.cxx
Fl_Type.cxx
+ ExternalCodeEditor_UNIX.cxx
+ ExternalCodeEditor_WIN32.cxx
Fl_Widget_Type.cxx
Fl_Window_Type.cxx
Fluid_Image.cxx
diff --git a/fluid/ExternalCodeEditor_UNIX.cxx b/fluid/ExternalCodeEditor_UNIX.cxx
new file mode 100644
index 000000000..e0d230d65
--- /dev/null
+++ b/fluid/ExternalCodeEditor_UNIX.cxx
@@ -0,0 +1,436 @@
+//
+// "$Id: ExternalCodeEditor_UNIX.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+// External code editor management class for Unix
+//
+#ifndef WIN32 /* This entire file unix only */
+
+#include <errno.h> /* errno */
+#include <string.h> /* strerror() */
+#include <sys/types.h> /* stat().. */
+#include <sys/stat.h>
+#include <sys/wait.h> /* waitpid().. */
+#include <fcntl.h> /* open().. */
+#include <signal.h> /* kill().. */
+#include <unistd.h>
+#include <stdlib.h> /* free().. */
+#include <stdio.h> /* snprintf().. */
+
+#include <FL/Fl.H> /* Fl_Timeout_Handler.. */
+#include <FL/fl_ask.H> /* fl_alert() */
+
+#include "ExternalCodeEditor_UNIX.h"
+
+extern int G_debug; // defined in fluid.cxx
+
+// Static local data
+static int L_editors_open = 0; // keep track of #editors open
+static Fl_Timeout_Handler L_update_timer_cb = 0; // app's update timer callback
+
+// [Static/Local] See if file exists
+static int is_file(const char *filename) {
+ struct stat buf;
+ if ( stat(filename, &buf) < 0 ) return(0);
+ return(S_ISREG(buf.st_mode) ? 1 : 0); // regular file?
+}
+
+// [Static/Local] See if dir exists
+static int is_dir(const char *dirname) {
+ struct stat buf;
+ if ( stat(dirname, &buf) < 0 ) return(0);
+ return(S_ISDIR(buf.st_mode) ? 1 : 0); // a dir?
+}
+
+// CTOR
+ExternalCodeEditor::ExternalCodeEditor() {
+ pid_ = -1;
+ filename_ = 0;
+ file_mtime_ = 0;
+ file_size_ = 0;
+}
+
+// DTOR
+ExternalCodeEditor::~ExternalCodeEditor() {
+ if ( G_debug )
+ printf("ExternalCodeEditor() DTOR CALLED (this=%p, pid=%ld)\n",
+ (void*)this, (long)pid_);
+ kill_editor(); // Kill open editor, deletes tmp file
+ set_filename(0); // free()s filename
+}
+
+// [Protected] Set the filename. Handles memory allocation/free
+// If set to NULL, frees memory.
+//
+void ExternalCodeEditor::set_filename(const char *val) {
+ if ( filename_ ) free((void*)filename_);
+ filename_ = val ? strdup(val) : 0;
+}
+
+// [Public] Is editor running?
+int ExternalCodeEditor::is_editing() {
+ return( (pid_ != -1) ? 1 : 0 );
+}
+
+// [Protected] Kill the running editor (if any)
+// Kills the editor, reaps the process, and removes the tmp file.
+// The dtor calls this to ensure no editors remain running when fluid exits.
+//
+void ExternalCodeEditor::kill_editor() {
+ if ( G_debug ) printf("kill_editor() called: pid=%ld\n", (long)pid_);
+ if ( !is_editing() ) return; // editor not running? return..
+ kill(pid_, SIGTERM); // kill editor
+ int wcount = 0;
+ while ( pid_ != -1 ) { // and wait for it to finish..
+ usleep(100000); // 1/10th sec delay gives editor time to close itself
+ switch (reap_editor()) {
+ case -1: // error
+ fl_alert("Can't seem to close editor of file: %s\n"
+ "waitpid() returned: %s\n"
+ "Please close editor and hit OK",
+ filename(), strerror(errno));
+ continue;
+ case 0: // process still running
+ if ( ++wcount > 3 ) { // retry 3x with 1/10th delay before showing dialog
+ fl_alert("Can't seem to close editor of file: %s\n"
+ "Please close editor and hit OK", filename());
+ }
+ continue;
+ default: // process reaped
+ if ( G_debug )
+ printf("*** REAPED KILLED EXTERNAL EDITOR: PID %ld\n", (long)pid_);
+ pid_ = -1;
+ break;
+ }
+ }
+ return;
+}
+
+// [Public] Handle if file changed since last check, and update records if so.
+// Load new data into 'code', which caller must free().
+// If 'force' set, forces reload even if file size/time didn't change.
+//
+// Returns:
+// 0 -- file unchanged or not editing
+// 1 -- file changed, internal records updated, 'code' has new content
+// -1 -- error getting file info (strerror() has reason)
+//
+int ExternalCodeEditor::handle_changes(const char **code, int force) {
+ code[0] = 0;
+ if ( !is_editing() ) return 0;
+ // Get current time/size info, see if file changed
+ int changed = 0;
+ {
+ struct stat sbuf;
+ if ( stat(filename(), &sbuf) < 0 ) return(-1); // TODO: show fl_alert(), do this in win32 too, adjust func call docs above
+ time_t now_mtime = sbuf.st_mtime;
+ size_t now_size = sbuf.st_size;
+ // OK, now see if file changed; update records if so
+ if ( now_mtime != file_mtime_ ) { changed = 1; file_mtime_ = now_mtime; }
+ if ( now_size != file_size_ ) { changed = 1; file_size_ = now_size; }
+ }
+ // No changes? done
+ if ( !changed && !force ) return 0;
+ // Changes? Load file, and fallthru to close()
+ int fd = open(filename(), O_RDONLY);
+ if ( fd < 0 ) {
+ fl_alert("ERROR: can't open '%s': %s", filename(), strerror(errno));
+ return -1;
+ }
+ int ret = 0;
+ char *buf = (char*)malloc(file_size_ + 1);
+ ssize_t count = read(fd, buf, file_size_);
+ if ( count == -1 ) {
+ fl_alert("ERROR: read() %s: %s", filename(), strerror(errno));
+ free((void*)buf);
+ ret = -1;
+ } else if ( (long)count != (long)file_size_ ) {
+ fl_alert("ERROR: read() failed for %s:\n"
+ "expected %ld bytes, only got %ld",
+ filename(), long(file_size_), long(count));
+ ret = -1;
+ } else {
+ // Success -- file loaded OK
+ buf[count] = '\0';
+ code[0] = buf; // return pointer to allocated buffer
+ ret = 1;
+ }
+ close(fd);
+ return ret;
+}
+
+// [Public] Remove the tmp file (if it exists), and zero out filename/mtime/size
+// Returns:
+// -1 -- on error (dialog is posted as to why)
+// 0 -- no file to remove
+// 1 -- file was removed
+//
+int ExternalCodeEditor::remove_tmpfile() {
+ const char *tmpfile = filename();
+ if ( !tmpfile ) return 0;
+ // Filename set? remove (if exists) and zero filename/mtime/size
+ if ( is_file(tmpfile) ) {
+ if ( G_debug ) printf("Removing tmpfile '%s'\n", tmpfile);
+ if ( remove(tmpfile) < 0 ) {
+ fl_alert("WARNING: Can't remove() '%s': %s", tmpfile, strerror(errno));
+ return -1;
+ }
+ }
+ set_filename(0);
+ file_mtime_ = 0;
+ file_size_ = 0;
+ return 1;
+}
+
+// [Static/Public] Return tmpdir name for this fluid instance.
+// Returns pointer to static memory.
+//
+const char* ExternalCodeEditor::tmpdir_name() {
+ static char dirname[100];
+ snprintf(dirname, sizeof(dirname), "/tmp/.fluid-%ld", (long)getpid());
+ return dirname;
+}
+
+// [Static/Public] Clear the external editor's tempdir
+// Static so that the main program can call it on exit to clean up.
+//
+void ExternalCodeEditor::tmpdir_clear() {
+ const char *tmpdir = tmpdir_name();
+ if ( is_dir(tmpdir) ) {
+ if ( G_debug ) printf("Removing tmpdir '%s'\n", tmpdir);
+ if ( rmdir(tmpdir) < 0 ) {
+ fl_alert("WARNING: Can't rmdir() '%s': %s", tmpdir, strerror(errno));
+ }
+ }
+}
+
+// [Protected] Creates temp dir (if doesn't exist) and returns the dirname
+// as a static string. Returns NULL on error, dialog shows reason.
+//
+const char* ExternalCodeEditor::create_tmpdir() {
+ const char *dirname = tmpdir_name();
+ if ( ! is_dir(dirname) ) {
+ if ( mkdir(dirname, 0777) < 0 ) {
+ fl_alert("can't create directory '%s': %s",
+ dirname, strerror(errno));
+ return NULL;
+ }
+ }
+ return dirname;
+}
+
+// [Protected] Returns temp filename in static buffer.
+// Returns NULL if can't, posts dialog explaining why.
+//
+const char* ExternalCodeEditor::tmp_filename() {
+ static char path[512];
+ const char *tmpdir = create_tmpdir();
+ if ( !tmpdir ) return 0;
+ extern const char *code_file_name; // fluid's global
+ const char *ext = code_file_name; // e.g. ".cxx"
+ snprintf(path, sizeof(path), "%s/%p%s", tmpdir, (void*)this, ext);
+ path[sizeof(path)-1] = 0;
+ return path;
+}
+
+// [Static/Local] Save string 'code' to 'filename', returning file's mtime/size
+// 'code' can be NULL -- writes an empty file if so.
+// Returns:
+// 0 on success
+// -1 on error (posts dialog with reason)
+//
+static int save_file(const char *filename, const char *code) {
+ int fd = open(filename, O_WRONLY|O_CREAT, 0666);
+ if ( fd == -1 ) {
+ fl_alert("ERROR: open() '%s': %s", filename, strerror(errno));
+ return -1;
+ }
+ ssize_t clen = strlen(code);
+ ssize_t count = write(fd, code, clen);
+ int ret = 0;
+ if ( count == -1 ) {
+ fl_alert("ERROR: write() '%s': %s", filename, strerror(errno));
+ ret = -1; // fallthru to close()
+ } else if ( count != clen ) {
+ fl_alert("ERROR: write() '%s': wrote only %lu bytes, expected %lu",
+ filename, (unsigned long)count, (unsigned long)clen);
+ ret = -1; // fallthru to close()
+ }
+ close(fd);
+ return(ret);
+}
+
+// [Static/Local] Convert string 's' to array of argv[], useful for execve()
+// o 's' will be modified (words will be NULL separated)
+// o argv[] will end up pointing to the words of 's'
+// o Caller must free argv with: free(argv);
+//
+static int make_args(char *s, // string containing words (gets trashed!)
+ int *aargc, // pointer to argc
+ char ***aargv) { // pointer to argv
+ char *ss, **argv;
+ if ((argv=(char**)malloc(sizeof(char*) * (strlen(s)/2)))==NULL) {
+ return -1;
+ }
+ int t;
+ for(t=0; (t==0)?(ss=strtok(s," \t")):(ss=strtok(0," \t")); t++) {
+ argv[t] = ss;
+ }
+ argv[t] = 0;
+ aargv[0] = argv;
+ aargc[0] = t;
+ return(t);
+}
+
+// [Protected] Start editor in background (fork/exec)
+// Returns:
+// > 0 on success, leaves editor child process running as 'pid_'
+// > -1 on error, posts dialog with reason (child exits)
+//
+int ExternalCodeEditor::start_editor(const char *editor_cmd,
+ const char *filename) {
+ if ( G_debug ) printf("start_editor() cmd='%s', filename='%s'\n",
+ editor_cmd, filename);
+ char cmd[1024];
+ snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
+ // Fork editor to background..
+ switch ( pid_ = fork() ) {
+ case -1: // error
+ fl_alert("couldn't fork(): %s", strerror(errno));
+ return -1;
+ case 0: { // child
+ // NOTE: OSX wants minimal code between fork/exec, see Apple TN2083
+ int nargs;
+ char **args = 0;
+ make_args(cmd, &nargs, &args);
+ execvp(args[0], args); // run command - doesn't return if succeeds
+ fl_alert("couldn't exec() '%s': %s", cmd, strerror(errno));
+ exit(1);
+ }
+ default: // parent
+ if ( L_editors_open++ == 0 ) // first editor? start timers
+ { start_update_timer(); }
+ if ( G_debug )
+ printf("--- EDITOR STARTED: pid_=%ld #open=%d\n", (long)pid_, L_editors_open);
+ break;
+ }
+ return 0;
+}
+
+// [Public] Try to reap external editor process
+// Returns:
+// -2 -- editor not open
+// -1 -- waitpid() failed (errno has reason)
+// 0 -- process still running
+// >0 -- process finished + reaped (value is pid)
+// Handles removing tmpfile/zeroing file_mtime/file_size
+//
+pid_t ExternalCodeEditor::reap_editor() {
+ if ( !is_editing() ) return -2;
+ int status = 0;
+ pid_t wpid;
+ switch (wpid = waitpid(pid_, &status, WNOHANG)) {
+ case -1: // waitpid() failed
+ return -1;
+ case 0: // process didn't reap, still running
+ return 0;
+ default: // process reaped
+ remove_tmpfile(); // also zeroes mtime/size
+ pid_ = -1;
+ if ( --L_editors_open <= 0 )
+ { stop_update_timer(); }
+ break;
+ }
+ if ( G_debug )
+ printf("*** EDITOR REAPED: pid=%ld #open=%d\n", long(wpid), L_editors_open);
+ return wpid;
+}
+
+// [Public] Open external editor using 'editor_cmd' to edit 'code'
+// 'code' contains multiline code to be edited as a temp file.
+//
+// Returns:
+// 0 if succeeds
+// -1 if can't open editor (already open, etc),
+// errors were shown to user in a dialog
+//
+int ExternalCodeEditor::open_editor(const char *editor_cmd,
+ const char *code) {
+ // Make sure a temp filename exists
+ if ( !filename() ) {
+ set_filename(tmp_filename());
+ if ( !filename() ) return -1;
+ }
+ // See if tmpfile already exists or editor already open
+ if ( is_file(filename()) ) {
+ if ( is_editing() ) {
+ // See if editor recently closed but not reaped; try to reap
+ pid_t wpid = reap_editor();
+ switch (wpid) {
+ case -1: // waitpid() failed
+ fl_alert("ERROR: waitpid() failed: %s\nfile='%s', pid=%ld",
+ strerror(errno), filename(), (long)pid_);
+ return -1;
+ case 0: // process still running
+ fl_alert("Editor Already Open\n file='%s'\n pid=%ld",
+ filename(), (long)pid_);
+ return 0;
+ default: // process reaped, wpid is pid reaped
+ if ( G_debug )
+ printf("*** REAPED EXTERNAL EDITOR: PID %ld\n", (long)wpid);
+ break; // fall thru to open new editor instance
+ }
+ // Reinstate tmp filename (reap_editor() clears it)
+ set_filename(tmp_filename());
+ }
+ }
+ if ( save_file(filename(), code) < 0 ) {
+ return -1; // errors were shown in dialog
+ }
+ // Update mtime/size from closed file
+ struct stat sbuf;
+ if ( stat(filename(), &sbuf) < 0 ) {
+ fl_alert("ERROR: can't stat('%s'): %s", filename(), strerror(errno));
+ return -1;
+ }
+ file_mtime_ = sbuf.st_mtime;
+ file_size_ = sbuf.st_size;
+ if ( start_editor(editor_cmd, filename()) < 0 ) { // open file in external editor
+ if ( G_debug ) printf("Editor failed to start\n");
+ return -1; // errors were shown in dialog
+ }
+ return 0;
+}
+
+// [Public/Static] Start update timer
+void ExternalCodeEditor::start_update_timer() {
+ if ( !L_update_timer_cb ) return;
+ if ( G_debug ) printf("--- TIMER: STARTING UPDATES\n");
+ Fl::add_timeout(2.0, L_update_timer_cb);
+}
+
+// [Public/Static] Stop update timer
+void ExternalCodeEditor::stop_update_timer() {
+ if ( !L_update_timer_cb ) return;
+ if ( G_debug ) printf("--- TIMER: STOPPING UPDATES\n");
+ Fl::remove_timeout(L_update_timer_cb);
+}
+
+// [Public/Static] Set app's external editor update timer callback
+// This is the app's callback callback we start while editors are open,
+// and stop when all editors are closed.
+//
+void ExternalCodeEditor::set_update_timer_callback(Fl_Timeout_Handler cb) {
+ L_update_timer_cb = cb;
+}
+
+// [Static/Public] See if any external editors are open.
+// App's timer cb can see if any editors need checking..
+//
+int ExternalCodeEditor::editors_open() {
+ return L_editors_open;
+}
+
+#endif /* !WIN32 */
+//
+// End of "$Id: ExternalCodeEditor_UNIX.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
diff --git a/fluid/ExternalCodeEditor_UNIX.h b/fluid/ExternalCodeEditor_UNIX.h
new file mode 100644
index 000000000..07d8e3762
--- /dev/null
+++ b/fluid/ExternalCodeEditor_UNIX.h
@@ -0,0 +1,51 @@
+//
+// "$Id: ExternalCodeEditor_UNIX.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+// External code editor management class for Unix
+//
+// Handles starting and keeping track of an external text editor,
+// including process start, temp file creation/removal, bookkeeping, killing..
+//
+#ifndef _EXTCODEEDITOR_H
+#define _EXTCODEEDITOR_H
+
+#include <errno.h> /* errno */
+#include <string.h> /* strerror() */
+
+#include <sys/types.h> /* stat().. */
+#include <sys/stat.h>
+#include <unistd.h>
+
+class ExternalCodeEditor {
+ int pid_;
+ time_t file_mtime_; // last modify time of the file (used to determine if file changed)
+ size_t file_size_; // last file size (used to determine if changed)
+ const char *filename_;
+protected:
+ void kill_editor();
+ const char *create_tmpdir();
+ const char *tmp_filename();
+ int start_editor(const char *cmd, const char *filename);
+ void set_filename(const char *val);
+public:
+ ExternalCodeEditor();
+ ~ExternalCodeEditor();
+ int is_editing();
+ pid_t reap_editor();
+ const char *filename() { return filename_; }
+ int open_editor(const char *editor_cmd, const char *code);
+ int handle_changes(const char **code, int force=0);
+ int remove_tmpfile();
+ // Public static methods
+ static void start_update_timer();
+ static void stop_update_timer();
+ static const char* tmpdir_name();
+ static void tmpdir_clear();
+ static int editors_open();
+ static void set_update_timer_callback(Fl_Timeout_Handler);
+};
+
+#endif /*_EXTCODEEDITOR_H */
+//
+// End of "$Id: ExternalCodeEditor_UNIX.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
diff --git a/fluid/ExternalCodeEditor_WIN32.cxx b/fluid/ExternalCodeEditor_WIN32.cxx
new file mode 100644
index 000000000..767fd81f7
--- /dev/null
+++ b/fluid/ExternalCodeEditor_WIN32.cxx
@@ -0,0 +1,548 @@
+//
+// "$Id: ExternalCodeEditor_WIN32.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+// External code editor management class for Windows
+//
+
+#ifdef WIN32 /* This entire file windows only */
+
+#include <stdio.h> // snprintf()
+
+#include <FL/Fl.H> // Fl_Timeout_Handler..
+#include <FL/fl_ask.H> // fl_alert()
+
+#include "ExternalCodeEditor_WIN32.h"
+
+extern int G_debug; // defined in fluid.cxx
+
+// Static local data
+static int L_editors_open = 0; // keep track of #editors open
+static Fl_Timeout_Handler L_update_timer_cb = 0; // app's update timer callback
+
+// [Static/Local] Get error message string for last failed WIN32 function.
+// Returns a string pointing to static memory.
+//
+// TODO: Is more code needed here to convert returned string to utf8? -erco
+//
+static const char *get_ms_errmsg() {
+ static char emsg[1024];
+ DWORD lastErr = GetLastError();
+ DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_FROM_SYSTEM;
+ LPSTR mbuf = 0;
+ DWORD size = FormatMessageA(flags, 0, lastErr, MAKELANGID(LANG_NEUTRAL,
+ SUBLANG_DEFAULT), (LPSTR)&mbuf, 0, NULL);
+ if ( size == 0 ) {
+ _snprintf(emsg, sizeof(emsg), "Error Code %ld", long(lastErr));
+ } else {
+ // Copy mbuf -> emsg (with '\r's removed -- they screw up fl_alert())
+ for ( char *src=mbuf, *dst=emsg; 1; src++ ) {
+ if ( *src == '\0' ) { *dst = '\0'; break; }
+ if ( *src != '\r' ) { *dst++ = *src; }
+ }
+ LocalFree(mbuf); // Free the buffer allocated by the system
+ }
+ return emsg;
+}
+
+// [Static/Local] See if file exists
+static int is_file(const char *filename) {
+ DWORD att = GetFileAttributesA(filename);
+ if (att == INVALID_FILE_ATTRIBUTES) return 0;
+ if ( (att & FILE_ATTRIBUTE_DIRECTORY) == 0 ) return 1; // not a dir == file
+ return 0;
+}
+
+// [Static/Local] See if dir exists
+static int is_dir(const char *dirname) {
+ DWORD att = GetFileAttributesA(dirname);
+ if (att == INVALID_FILE_ATTRIBUTES) return 0;
+ if (att & FILE_ATTRIBUTE_DIRECTORY) return 1;
+ return 0;
+}
+
+// CTOR
+ExternalCodeEditor::ExternalCodeEditor() {
+ memset(&pinfo_, 0, sizeof(pinfo_));
+ memset(&file_mtime_, 0, sizeof(file_mtime_));
+ memset(&file_size_, 0, sizeof(file_size_));
+ filename_ = 0;
+}
+
+// DTOR
+ExternalCodeEditor::~ExternalCodeEditor() {
+ kill_editor(); // Kill any open editors, deletes tmp file
+ set_filename(0); // free()s filename
+}
+
+// [Protected] Set the filename. Handles memory allocation/free
+// If set to NULL, frees memory.
+//
+void ExternalCodeEditor::set_filename(const char *val) {
+ if ( filename_ ) free((void*)filename_);
+ filename_ = val ? strdup(val) : 0;
+}
+
+// [Public] Is editor running?
+int ExternalCodeEditor::is_editing() {
+ return( (pinfo_.dwProcessId != 0) ? 1 : 0 );
+}
+
+// [Static/Local] Terminate_app()'s callback to send WM_CLOSE to a single window.
+static BOOL CALLBACK terminate_app_enum(HWND hwnd, LPARAM lParam) {
+ DWORD dwID;
+ GetWindowThreadProcessId(hwnd, &dwID);
+ if (dwID == (DWORD)lParam) {
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ if ( G_debug )
+ printf("terminate_app_enum() sends WIN_CLOSE to hwnd=%p\n", (void*)hwnd);
+ }
+ return TRUE;
+}
+
+// [Static/Local] Handle sending WIN_CLOSE to /all/ windows matching specified pid.
+// Wait up to msecTimeout for process to close, and if it doesn't, use TerminateProcess().
+//
+static int terminate_app(DWORD pid, DWORD msecTimeout) {
+ HANDLE hProc = OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid);
+ if ( !hProc ) return -1;
+ // terminate_app_enum() posts WM_CLOSE to all windows matching pid
+ EnumWindows((WNDENUMPROC)terminate_app_enum, (LPARAM) pid);
+ // Wait on handle. If it closes, great. If it times out, use TerminateProcess()
+ int ret = 0;
+ if ( WaitForSingleObject(hProc, msecTimeout) != WAIT_OBJECT_0 ) {
+ if ( G_debug ) {
+ printf("WARNING: sent WIN_CLOSE, but timeout after %ld msecs.."
+ "trying TerminateProcess\n", msecTimeout);
+ }
+ if ( TerminateProcess(hProc, 0) == 0 ) {
+ if ( G_debug ) {
+ printf("ERROR: TerminateProcess() for pid=%ld failed: %s\n",
+ long(pid), get_ms_errmsg());
+ }
+ ret = -1;
+ } else {
+ ret = 0; // TerminateProcess succeeded
+ }
+ } else {
+ ret = 0; // WaitForSingleObject() confirmed WIN_CLOSE succeeded
+ }
+ CloseHandle(hProc);
+ return ret;
+}
+
+// [Protected] Kill the running editor (if any) and cleanup
+// Kills the editor, reaps the process, and removes the tmp file.
+// The dtor calls this to ensure no editors remain running when fluid exits.
+//
+void ExternalCodeEditor::kill_editor() {
+ if ( G_debug )
+ printf("kill_editor() called: pid=%ld\n", (long)pinfo_.dwProcessId);
+ if ( !is_editing() ) return;
+ switch ( terminate_app(pinfo_.dwProcessId, 500) ) { // kill editor, wait up to 1/2 sec to die
+ case -1: { // error
+ fl_alert("Can't seem to close editor of file: %s\n"
+ "Please close editor and hit OK", filename());
+ break;
+ }
+ case 0: { // success -- process reaped
+ DWORD pid = pinfo_.dwProcessId; // save pid
+ reap_cleanup();
+ if ( G_debug )
+ printf("*** kill_editor() REAP pid=%ld #open=%ld\n",
+ long(pid), long(L_editors_open));
+ break;
+ }
+ }
+ return;
+}
+
+// [Public] Handle if file changed since last check, and update records if so.
+// Load new data into 'code', which caller must free().
+// If 'force' set, forces reload even if file size/time didn't change.
+//
+// Returns:
+// 0 -- file unchanged or not editing
+// 1 -- file changed, internal records updated, 'code' has new content
+// -1 -- error getting file info (get_ms_errmsg() has reason)
+//
+// OPTIONAL TODO:
+// Ignore changes made within the last 2 seconds,
+// to give editor time to fully write out the file.
+//
+int ExternalCodeEditor::handle_changes(const char **code, int force) {
+ code[0] = 0;
+ if ( !is_editing() ) return 0;
+ // Sigh, have to open file to get file time/size :/
+ HANDLE fh = CreateFile(filename(), // file to read
+ GENERIC_READ, // reading only
+ FILE_SHARE_READ, // sharing -- allow read share; just getting file size
+ NULL, // security
+ OPEN_EXISTING, // create flags -- must exist
+ 0, // misc flags
+ NULL); // templates
+ if ( fh == INVALID_HANDLE_VALUE ) return -1;
+ LARGE_INTEGER fsize;
+ // Get file size
+ if ( GetFileSizeEx(fh, &fsize) == 0 ) {
+ DWORD err = GetLastError();
+ CloseHandle(fh);
+ SetLastError(err); // return error from GetFileSizeEx(), not CloseHandle()
+ return -1;
+ }
+ // Get file time
+ FILETIME ftCreate, ftAccess, ftWrite;
+ if ( GetFileTime(fh, &ftCreate, &ftAccess, &ftWrite) == 0 ) {
+ DWORD err = GetLastError();
+ CloseHandle(fh);
+ SetLastError(err); // return error from GetFileTime(), not CloseHandle()
+ return -1;
+ }
+ // OK, now see if file changed; update records if so
+ int changed = 0;
+ if ( fsize.QuadPart != file_size_.QuadPart )
+ { changed = 1; file_size_ = fsize; }
+ if ( CompareFileTime(&ftWrite, &file_mtime_) != 0 )
+ { changed = 1; file_mtime_ = ftWrite; }
+ // Changes? Load file. Be sure to fallthru to CloseHandle()
+ int ret = 0;
+ if ( changed || force ) {
+ char *buf = (char*)malloc(fsize.QuadPart + 1);
+ DWORD count;
+ if ( ReadFile(fh, buf, fsize.QuadPart, &count, 0) == 0 ) {
+ fl_alert("ERROR: ReadFile() failed for %s: %s",
+ filename(), get_ms_errmsg());
+ free((void*)buf); buf = 0;
+ ret = -1; // fallthru to CloseHandle()
+ } else if ( count != fsize.QuadPart ) {
+ fl_alert("ERROR: ReadFile() failed for %s:\n"
+ "expected %ld bytes, got %ld",
+ filename(), long(fsize.QuadPart), long(count));
+ free((void*)buf); buf = 0;
+ ret = -1; // fallthru to CloseHandle()
+ } else {
+ // Successfully read changed file
+ buf[count] = '\0';
+ code[0] = buf; // return pointer to allocated buffer
+ ret = 1; // fallthru to CloseHandle()
+ }
+ }
+ CloseHandle(fh);
+ return ret;
+}
+
+// [Public] Remove the tmp file (if it exists), and zero out filename/mtime/size
+// Returns:
+// -1 -- on error (dialog is posted as to why)
+// 0 -- no file to remove
+// 1 -- file was removed
+//
+int ExternalCodeEditor::remove_tmpfile() {
+ const char *tmpfile = filename();
+ if ( G_debug ) printf("remove_tmpfile() '%s'\n", tmpfile ? tmpfile : "(empty)");
+ if ( !tmpfile ) return 0;
+ // Filename set? remove (if exists) and zero filename/mtime/size
+ if ( is_file(tmpfile) ) {
+ if ( G_debug ) printf("Removing tmpfile '%s'\n", tmpfile);
+ if ( DeleteFile(tmpfile) == 0 ) {
+ fl_alert("WARNING: Can't DeleteFile() '%s': %s", tmpfile, get_ms_errmsg());
+ return -1;
+ }
+ } else {
+ if ( G_debug ) printf("remove_tmpfile(): is_file(%s) failed\n", tmpfile);
+ }
+ set_filename(0);
+ memset(&file_mtime_, 0, sizeof(file_mtime_));
+ memset(&file_size_, 0, sizeof(file_size_));
+ return 1;
+}
+
+// [Static/Public] Return tmpdir name for this fluid instance.
+// Returns pointer to static memory.
+//
+const char* ExternalCodeEditor::tmpdir_name() {
+ char tempdir[100];
+ if (GetTempPath(sizeof(tempdir), tempdir) == 0 ) {
+ strcpy(tempdir, "c:\\windows\\temp"); // fallback
+ }
+ static char dirname[100];
+ _snprintf(dirname, sizeof(dirname), "%s.fluid-%ld",
+ tempdir, (long)GetCurrentProcessId());
+ if ( G_debug ) printf("tmpdir_name(): '%s'\n", dirname);
+ return dirname;
+}
+
+// [Static/Public] Clear the external editor's tempdir
+// Static so that the main program can call it on exit to clean up.
+//
+void ExternalCodeEditor::tmpdir_clear() {
+ const char *tmpdir = tmpdir_name();
+ if ( is_dir(tmpdir) ) {
+ if ( G_debug ) printf("Removing tmpdir '%s'\n", tmpdir);
+ if ( RemoveDirectory(tmpdir) == 0 ) {
+ fl_alert("WARNING: Can't RemoveDirectory() '%s': %s",
+ tmpdir, get_ms_errmsg());
+ }
+ }
+}
+
+// [Protected] Creates temp dir (if doesn't exist) and returns the dirname
+// as a static string. Returns NULL on error, dialog shows reason.
+//
+const char* ExternalCodeEditor::create_tmpdir() {
+ const char *dirname = tmpdir_name();
+ if ( ! is_dir(dirname) ) {
+ if ( CreateDirectory(dirname,0) == 0 ) {
+ fl_alert("can't create directory '%s': %s",
+ dirname, get_ms_errmsg());
+ return NULL;
+ }
+ }
+ return dirname;
+}
+
+// [Protected] Returns temp filename in static buffer.
+// Returns NULL if can't, posts dialog explaining why.
+//
+const char* ExternalCodeEditor::tmp_filename() {
+ static char path[512];
+ const char *tmpdir = create_tmpdir();
+ if ( !tmpdir ) return 0;
+ extern const char *code_file_name; // fluid's global
+ const char *ext = code_file_name; // e.g. ".cxx"
+ _snprintf(path, sizeof(path), "%s\\%p%s", tmpdir, (void*)this, ext);
+ path[sizeof(path)-1] = 0;
+ return path;
+}
+
+// [Static/Local] Save string 'code' to 'filename', returning file's mtime/size
+// 'code' can be NULL -- writes an empty file if so.
+// Returns:
+// 0 on success
+// -1 on error (posts dialog with reason)
+//
+static int save_file(const char *filename,
+ const char *code,
+ FILETIME &file_mtime, // return these since in win32 it's..
+ LARGE_INTEGER &file_size) { // ..efficient to get while file open
+ if ( code == 0 ) code = ""; // NULL? write an empty file
+ memset(&file_mtime, 0, sizeof(file_mtime));
+ memset(&file_size, 0, sizeof(file_size));
+ HANDLE fh = CreateFile(filename, // filename
+ GENERIC_WRITE, // write only
+ 0, // sharing -- no share during write
+ NULL, // security
+ CREATE_ALWAYS, // create flags -- recreate
+ FILE_ATTRIBUTE_NORMAL, // misc flags
+ NULL); // templates
+ if ( fh == INVALID_HANDLE_VALUE ) {
+ fl_alert("ERROR: couldn't create file '%s': %s",
+ filename, get_ms_errmsg());
+ return(-1);
+ }
+ // Write the file, being careful to CloseHandle() even on errs
+ DWORD clen = strlen(code);
+ DWORD count = 0;
+ int ret = 0;
+ if ( WriteFile(fh, code, clen, &count, NULL) == 0 ) {
+ fl_alert("ERROR: WriteFile() '%s': %s", filename, get_ms_errmsg());
+ ret = -1; // fallthru to CloseHandle()
+ } else if ( count != clen ) {
+ fl_alert("ERROR: WriteFile() '%s': wrote only %lu bytes, expected %lu",
+ filename, (unsigned long)count, (unsigned long)clen);
+ ret = -1; // fallthru to CloseHandle()
+ }
+ // Get mtime/size before closing
+ {
+ FILETIME ftCreate, ftAccess, ftWrite;
+ if ( GetFileSizeEx(fh, &file_size) == 0 ) {
+ fl_alert("ERROR: save_file(%s): GetFileSizeEx() failed: %s\n",
+ filename, get_ms_errmsg());
+ }
+ if ( GetFileTime(fh, &ftCreate, &ftAccess, &ftWrite) == 0 ) {
+ fl_alert("ERROR: save_file(%s): GetFileTime() failed: %s\n",
+ filename, get_ms_errmsg());
+ }
+ file_mtime = ftWrite;
+ }
+ // Close, done
+ CloseHandle(fh);
+ return(ret);
+}
+
+// [Protected] Start editor
+// Returns:
+// > 0 on success, leaves editor child process running as 'pinfo_'
+// > -1 on error, posts dialog with reason (child exits)
+//
+int ExternalCodeEditor::start_editor(const char *editor_cmd,
+ const char *filename) {
+ if ( G_debug ) printf("start_editor() cmd='%s', filename='%s'\n",
+ editor_cmd, filename);
+ // Startup info
+ STARTUPINFO sinfo;
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.cb = sizeof(sinfo);
+ sinfo.dwFlags = 0;
+ sinfo.wShowWindow = 0;
+ // Process info
+ memset(&pinfo_, 0, sizeof(pinfo_));
+ // Command
+ char cmd[1024];
+ _snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
+ // Start editor process
+ if (CreateProcess(NULL, // app name
+ (char*)cmd, // command to exec
+ NULL, // secure attribs
+ NULL, // thread secure attribs
+ FALSE, // handle inheritance
+ 0, // creation flags
+ NULL, // environ block
+ NULL, // current dir
+ &sinfo, // startup info
+ &pinfo_) == 0 ) { // process info
+ fl_alert("CreateProcess() failed to start '%s': %s",
+ cmd, get_ms_errmsg());
+ return(-1);
+ }
+ if ( L_editors_open++ == 0 ) // first editor? start timers
+ { start_update_timer(); }
+ if ( G_debug )
+ printf("--- EDITOR STARTED: pid_=%ld #open=%d\n",
+ (long)pinfo_.dwProcessId, L_editors_open);
+ return 0;
+}
+
+// [Protected] Cleanup after editor reaped:
+// > Remove tmpfile, zeroes mtime/size/filename
+// > Close process handles
+// > Zero out process info
+// > Decrease editor count
+//
+void ExternalCodeEditor::reap_cleanup() {
+ remove_tmpfile(); // also zeroes mtime/size/filename
+ CloseHandle(pinfo_.hProcess); // close process handle
+ CloseHandle(pinfo_.hThread); // close thread handle
+ memset(&pinfo_, 0, sizeof(pinfo_)); // clear pinfo_
+ if ( --L_editors_open <= 0 )
+ { stop_update_timer(); }
+}
+
+// [Public] Try to reap external editor process
+// Returns:
+// -2 -- editor not open
+// -1 -- WaitForSingleObject() failed (get_ms_errmsg() has reason)
+// 0 -- process still running
+// >0 -- process finished + reaped (value is pid)
+// Handles removing tmpfile/zeroing file_mtime/file_size/filename
+//
+DWORD ExternalCodeEditor::reap_editor() {
+ if ( pinfo_.dwProcessId == 0 ) return -2;
+ int err;
+ DWORD msecs_wait = 50; // .05 sec
+ switch ( err = WaitForSingleObject(pinfo_.hProcess, msecs_wait) ) {
+ case WAIT_TIMEOUT: { // process didn't reap, still running
+ return 0;
+ }
+ case WAIT_OBJECT_0: { // reaped
+ DWORD pid = pinfo_.dwProcessId; // save pid
+ reap_cleanup();
+ if ( G_debug ) printf("*** EDITOR REAPED: pid=%ld #open=%d\n",
+ long(pid), L_editors_open);
+ return pid;
+ }
+ case WAIT_FAILED: { // failed
+ return -1;
+ }
+ }
+ return -1; // any other return unexpected
+}
+
+// [Public] Open external editor using 'editor_cmd' to edit 'code'
+// 'code' contains multiline code to be edited as a temp file.
+//
+// Returns:
+// 0 if succeeds
+// -1 if can't open editor (already open, etc),
+// errors were shown to user in a dialog
+//
+int ExternalCodeEditor::open_editor(const char *editor_cmd,
+ const char *code) {
+ // Make sure a temp filename exists
+ if ( !filename() ) {
+ set_filename(tmp_filename());
+ if ( !filename() ) return -1;
+ }
+ // See if tmpfile already exists or editor already open
+ if ( is_file(filename()) ) {
+ if ( is_editing() ) {
+ // See if editor recently closed but not reaped; try to reap
+ DWORD wpid = reap_editor();
+ switch (wpid) {
+ case -1: // wait failed
+ fl_alert("ERROR: WaitForSingleObject() failed: %s\nfile='%s', pid=%ld",
+ get_ms_errmsg(), filename(), long(pinfo_.dwProcessId));
+ return -1;
+ case 0: // process still running
+ fl_alert("Editor Already Open\n file='%s'\n pid=%ld",
+ filename(), long(pinfo_.dwProcessId));
+ return 0;
+ default: // process reaped, wpid is pid reaped
+ if ( G_debug )
+ printf("*** REAPED EXTERNAL EDITOR: PID %ld\n", long(wpid));
+ break; // fall thru to open new editor instance
+ }
+ // Reinstate tmp filename (reap_editor() clears it)
+ set_filename(tmp_filename());
+ }
+ }
+ // Save code to tmpfile, getting mtime/size
+ if ( save_file(filename(), code, file_mtime_, file_size_) < 0 ) {
+ return -1; // errors were shown in dialog
+ }
+ if ( start_editor(editor_cmd, filename()) < 0 ) { // open file in external editor
+ if ( G_debug ) printf("Editor failed to start\n");
+ return -1; // errors were shown in dialog
+ }
+ // New editor opened -- start update timer (if not already)
+ if ( L_update_timer_cb && !Fl::has_timeout(L_update_timer_cb) ) {
+ if ( G_debug ) printf("--- Editor opened: STARTING UPDATE TIMER\n");
+ Fl::add_timeout(2.0, L_update_timer_cb);
+ }
+ return 0;
+}
+
+// [Public/Static] Start update timer
+void ExternalCodeEditor::start_update_timer() {
+ if ( !L_update_timer_cb ) return;
+ if ( G_debug ) printf("--- TIMER: STARTING UPDATES\n");
+ Fl::add_timeout(2.0, L_update_timer_cb);
+}
+
+// [Public/Static] Stop update timer
+void ExternalCodeEditor::stop_update_timer() {
+ if ( !L_update_timer_cb ) return;
+ if ( G_debug ) printf("--- TIMER: STOPPING UPDATES\n");
+ Fl::remove_timeout(L_update_timer_cb);
+}
+
+// [Public/Static] Set app's external editor update timer callback
+// This is the app's callback callback we start while editors are open,
+// and stop when all editors are closed.
+//
+void ExternalCodeEditor::set_update_timer_callback(Fl_Timeout_Handler cb) {
+ L_update_timer_cb = cb;
+}
+
+// [Static/Public] See if any external editors are open.
+// App's timer cb can see if any editors need checking..
+//
+int ExternalCodeEditor::editors_open() {
+ return L_editors_open;
+}
+
+#endif /* WIN32 */
+//
+// End of "$Id: ExternalCodeEditor_WIN32.cxx 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
diff --git a/fluid/ExternalCodeEditor_WIN32.h b/fluid/ExternalCodeEditor_WIN32.h
new file mode 100644
index 000000000..43492e632
--- /dev/null
+++ b/fluid/ExternalCodeEditor_WIN32.h
@@ -0,0 +1,63 @@
+//
+// "$Id: ExternalCodeEditor_WIN32.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
+// External code editor management class for Windows
+//
+// Handles starting and keeping track of an external text editor,
+// including process start, temp file creation/removal, bookkeeping, killing..
+//
+#ifndef _EXTCODEEDITOR_H
+#define _EXTCODEEDITOR_H
+
+/* We require at least Windows 2000 (WINVER == 0x0500) for GetFileSizeEx(). */
+/* This must be defined before #include <windows.h> - MinGW doesn't do that. */
+#if !defined(WINVER) || (WINVER < 0x0500)
+# ifdef WINVER
+# undef WINVER
+# endif
+# define WINVER 0x0500
+#endif
+#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0500)
+# ifdef _WIN32_WINNT
+# undef _WIN32_WINNT
+# endif
+# define _WIN32_WINNT 0x0500
+#endif
+
+#include <windows.h> /* CreateFile().. */
+#include <string.h> /* sprintf().. */
+
+class ExternalCodeEditor {
+ PROCESS_INFORMATION pinfo_; // CreateProcess() handle to running process
+ FILETIME file_mtime_; // last modify time of the file (used to determine if file changed)
+ LARGE_INTEGER file_size_; // last file size (used to determine if changed)
+ const char * filename_; // tmpfilename editor uses
+protected:
+ void kill_editor();
+ void reap_cleanup();
+ const char *create_tmpdir();
+ const char *tmp_filename();
+ int start_editor(const char *cmd, const char *filename);
+ void set_filename(const char *val);
+public:
+ ExternalCodeEditor();
+ ~ExternalCodeEditor();
+ int is_editing();
+ DWORD reap_editor();
+ const char *filename() { return filename_; }
+ int open_editor(const char *editor_cmd, const char *code);
+ int handle_changes(const char **code, int force=0);
+ int remove_tmpfile();
+ // Public static methods
+ static void start_update_timer();
+ static void stop_update_timer();
+ static const char* tmpdir_name();
+ static void tmpdir_clear();
+ static int editors_open();
+ static void set_update_timer_callback(Fl_Timeout_Handler);
+};
+
+#endif /*_EXTCODEEDITOR_H */
+//
+// End of "$Id: ExternalCodeEditor_WIN32.h 10800 2016-07-10 00:00:00Z greg.ercolano $".
+//
diff --git a/fluid/Fl_Function_Type.cxx b/fluid/Fl_Function_Type.cxx
index 5ca6c192c..b9234f3f1 100644
--- a/fluid/Fl_Function_Type.cxx
+++ b/fluid/Fl_Function_Type.cxx
@@ -15,16 +15,24 @@
//
// http://www.fltk.org/str.php
//
-
#include <FL/Fl.H>
+#include <FL/Fl_Window.H>
#include <FL/Fl_Preferences.H>
#include <FL/Fl_File_Chooser.H>
#include "Fl_Type.h"
#include <FL/fl_show_input.H>
#include <FL/Fl_File_Chooser.H>
+#include "alignment_panel.h"
#include "../src/flstring.h"
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+ #include "ExternalCodeEditor_WIN32.h"
+#else
+ #include "ExternalCodeEditor_UNIX.h"
+#endif
extern int i18n_type;
extern const char* i18n_include;
@@ -38,6 +46,7 @@ extern int batch_mode;
extern void redraw_browser();
extern void goto_source_dir();
extern void leave_source_dir();
+extern Fl_Window *main_window;
////////////////////////////////////////////////////////////////
// quick check of any C code for legality, returns an error message
@@ -434,6 +443,14 @@ Fl_Type *Fl_Code_Type::make() {
}
void Fl_Code_Type::open() {
+ // Using an external code editor? Open it..
+ if ( G_use_external_editor && G_external_editor_command[0] ) {
+ const char *cmd = G_external_editor_command;
+ const char *code = name();
+ if ( editor_.open_editor(cmd, code) == 0 )
+ return; // return if editor opened ok, fallthru to built-in if not
+ }
+ // Use built-in code editor..
if (!code_panel) make_code_panel();
const char *text = name();
code_input->buffer()->text( text ? text : "" );
@@ -459,9 +476,23 @@ BREAK2:
Fl_Code_Type Fl_Code_type;
+void Fl_Code_Type::write() {
+ // External editor changes? If so, load changes into ram, update mtime/size
+ if ( handle_editor_changes() == 1 ) {
+ main_window->redraw(); // tell fluid to redraw; edits may affect tree's contents
+ }
+ Fl_Type::write();
+}
+
void Fl_Code_Type::write_code1() {
+ // External editor changes? If so, load changes into ram, update mtime/size
+ if ( handle_editor_changes() == 1 ) {
+ main_window->redraw(); // tell fluid to redraw; edits may affect tree's contents
+ }
+
const char* c = name();
if (!c) return;
+
const char *pch;
const char *ind = indent();
while( (pch=strchr(c,'\n')) )
diff --git a/fluid/Fl_Type.cxx b/fluid/Fl_Type.cxx
index bbeb779af..a95fd72c9 100644
--- a/fluid/Fl_Type.cxx
+++ b/fluid/Fl_Type.cxx
@@ -756,6 +756,7 @@ int Fl_Type::is_menu_item() const {return 0;}
int Fl_Type::is_menu_button() const {return 0;}
int Fl_Type::is_group() const {return 0;}
int Fl_Type::is_window() const {return 0;}
+int Fl_Type::is_code() const {return 0;}
int Fl_Type::is_code_block() const {return 0;}
int Fl_Type::is_decl_block() const {return 0;}
int Fl_Type::is_comment() const {return 0;}
diff --git a/fluid/Fl_Type.h b/fluid/Fl_Type.h
index 2a66c5f05..e56fa02e1 100644
--- a/fluid/Fl_Type.h
+++ b/fluid/Fl_Type.h
@@ -32,6 +32,12 @@
#include <FL/fl_draw.H>
#include <stdarg.h>
+#ifdef WIN32
+ #include "ExternalCodeEditor_WIN32.h"
+#else
+ #include "ExternalCodeEditor_UNIX.h"
+#endif
+
void set_modflag(int mf);
class Fl_Type {
@@ -109,7 +115,7 @@ public:
virtual void open(); // what happens when you double-click
// read and write data to a saved file:
- void write();
+ virtual void write();
virtual void write_properties();
virtual void read_property(const char *);
virtual int read_fdesign(const char*, const char*);
@@ -143,6 +149,7 @@ public:
virtual int is_menu_button() const;
virtual int is_group() const;
virtual int is_window() const;
+ virtual int is_code() const;
virtual int is_code_block() const;
virtual int is_decl_block() const;
virtual int is_comment() const;
@@ -185,15 +192,56 @@ public:
};
class Fl_Code_Type : public Fl_Type {
+ ExternalCodeEditor editor_;
public:
Fl_Type *make();
+ void write();
void write_code1();
void write_code2();
void open();
virtual const char *type_name() {return "code";}
int is_code_block() const {return 0;}
+ int is_code() const {return 1;}
int pixmapID() { return 8; }
virtual int is_public() const;
+ // See if external editor is open
+ int is_editing() {
+ return editor_.is_editing();
+ }
+ // Reap the editor's pid
+ // Returns:
+ // -2 -- editor not open
+ // -1 -- wait failed
+ // 0 -- process still running
+ // >0 -- process finished + reaped (returns pid)
+ //
+ int reap_editor() {
+ return editor_.reap_editor();
+ }
+ // Handle external editor file modifications
+ // If changed, record keeping is updated and file's contents is loaded into ram
+ //
+ // Returns:
+ // 0 -- file unchanged or not editing
+ // 1 -- file changed, internal records updated, 'code' has new content
+ // -1 -- error getting file info (get_ms_errmsg() has reason)
+ //
+ // TODO: Figure out how saving a fluid file can be intercepted to grab
+ // current contents of editor file..
+ //
+ int handle_editor_changes() {
+ const char *newcode = 0;
+ switch ( editor_.handle_changes(&newcode) ) {
+ case 1: { // (1)=changed
+ name(newcode); // update value in ram
+ free((void*)newcode);
+ return 1;
+ }
+ case -1: return -1; // (-1)=error -- couldn't read file (dialog showed reason)
+ default: break; // (0)=no change
+ }
+ return 0;
+ }
};
class Fl_CodeBlock_Type : public Fl_Type {
diff --git a/fluid/Makefile b/fluid/Makefile
index f1602587f..4423c6934 100644
--- a/fluid/Makefile
+++ b/fluid/Makefile
@@ -18,6 +18,8 @@
CPPFILES = \
CodeEditor.cxx \
+ ExternalCodeEditor_UNIX.cxx \
+ ExternalCodeEditor_WIN32.cxx \
Fl_Function_Type.cxx \
Fl_Group_Type.cxx \
Fl_Menu_Type.cxx \
diff --git a/fluid/about_panel.cxx b/fluid/about_panel.cxx
index 9d96ab082..92e2c4c7a 100644
--- a/fluid/about_panel.cxx
+++ b/fluid/about_panel.cxx
@@ -251,7 +251,10 @@ static const char *idata_fluid[] = {
" ........................................................................\
.............. "
};
-static Fl_Pixmap image_fluid(idata_fluid);
+static Fl_Image *image_fluid() {
+ static Fl_Image *image = new Fl_Pixmap(idata_fluid);
+ return image;
+}
static void cb_View(Fl_Button*, void*) {
show_help("license.html");
@@ -267,7 +270,7 @@ Fl_Double_Window* make_about_panel() {
about_panel->selection_color(FL_DARK1);
about_panel->hotspot(about_panel);
{ Fl_Box* o = new Fl_Box(10, 10, 115, 120);
- o->image(image_fluid);
+ o->image( image_fluid() );
} // Fl_Box* o
{ Fl_Box* o = new Fl_Box(135, 10, 205, 75, "FLTK User\nInterface Designer\nVersion x.x.x");
o->color((Fl_Color)12);
diff --git a/fluid/alignment_panel.cxx b/fluid/alignment_panel.cxx
index 84d38e46b..8f3636233 100644
--- a/fluid/alignment_panel.cxx
+++ b/fluid/alignment_panel.cxx
@@ -197,12 +197,29 @@ static void cb_recent_spinner(Fl_Spinner*, void*) {
load_history();
}
+Fl_Check_Button *use_external_editor_button=(Fl_Check_Button *)0;
+
+static void cb_use_external_editor_button(Fl_Check_Button*, void*) {
+ G_use_external_editor = use_external_editor_button->value();
+fluid_prefs.set("use_external_editor", G_use_external_editor);
+redraw_browser();
+}
+
+Fl_Input *editor_command_input=(Fl_Input *)0;
+
+static void cb_editor_command_input(Fl_Input*, void*) {
+ strncpy(G_external_editor_command, editor_command_input->value(), sizeof(G_external_editor_command)-1);
+G_external_editor_command[sizeof(G_external_editor_command)-1] = 0;
+fluid_prefs.set("external_editor_command", G_external_editor_command);
+redraw_browser();
+}
+
static void cb_Close1(Fl_Button*, void*) {
settings_window->hide();
}
Fl_Double_Window* make_settings_window() {
- { settings_window = new Fl_Double_Window(349, 241, "GUI Settings");
+ { Fl_Double_Window* o = settings_window = new Fl_Double_Window(360, 355, "GUI Settings");
{ scheme_choice = new Fl_Choice(140, 10, 115, 25, "Scheme: ");
scheme_choice->down_box(FL_BORDER_BOX);
scheme_choice->labelfont(1);
@@ -213,7 +230,7 @@ Fl_Double_Window* make_settings_window() {
scheme_choice->value(s);
scheme_cb(0, 0);
} // Fl_Choice* scheme_choice
- { Fl_Group* o = new Fl_Group(116, 43, 220, 126);
+ { Fl_Group* o = new Fl_Group(20, 43, 330, 161);
o->labelfont(1);
o->align(Fl_Align(FL_ALIGN_CENTER));
{ Fl_Box* o = new Fl_Box(140, 43, 1, 25, "Options: ");
@@ -271,10 +288,36 @@ Fl_Double_Window* make_settings_window() {
recent_spinner->maximum(10);
recent_spinner->value(c);
} // Fl_Spinner* recent_spinner
- { Fl_Button* o = new Fl_Button(276, 205, 64, 25, "Close");
+ { Fl_Group* o = new Fl_Group(10, 210, 337, 95);
+ o->box(FL_THIN_UP_BOX);
+ o->color(FL_DARK1);
+ { use_external_editor_button = new Fl_Check_Button(25, 218, 209, 22, "Use external editor?");
+ use_external_editor_button->down_box(FL_DOWN_BOX);
+ use_external_editor_button->labelsize(12);
+ use_external_editor_button->callback((Fl_Callback*)cb_use_external_editor_button);
+ fluid_prefs.get("use_external_editor", G_use_external_editor, 0);
+ use_external_editor_button->value(G_use_external_editor);
+ } // Fl_Check_Button* use_external_editor_button
+ { editor_command_input = new Fl_Input(25, 264, 305, 21, "Editor Command");
+ editor_command_input->tooltip("The editor command to open your external text editor.\nInclude any necessary \
+flags to ensure your editor does not background itself.\nExamples:\n gvim -\
+f\n gedit\n emacs");
+ editor_command_input->labelsize(12);
+ editor_command_input->textsize(12);
+ editor_command_input->callback((Fl_Callback*)cb_editor_command_input);
+ editor_command_input->align(Fl_Align(FL_ALIGN_TOP_LEFT));
+ editor_command_input->when(FL_WHEN_CHANGED);
+ fluid_prefs.get("external_editor_command", G_external_editor_command, "", sizeof(G_external_editor_command)-1);
+ editor_command_input->value(G_external_editor_command);
+ } // Fl_Input* editor_command_input
+ o->end();
+ Fl_Group::current()->resizable(o);
+ } // Fl_Group* o
+ { Fl_Button* o = new Fl_Button(285, 320, 64, 25, "Close");
o->tooltip("Close this dialog.");
o->callback((Fl_Callback*)cb_Close1);
} // Fl_Button* o
+ o->size_range(o->w(), o->h());
settings_window->set_non_modal();
settings_window->end();
} // Fl_Double_Window* settings_window
diff --git a/fluid/alignment_panel.fl b/fluid/alignment_panel.fl
index e597e79bd..17b4159f5 100644
--- a/fluid/alignment_panel.fl
+++ b/fluid/alignment_panel.fl
@@ -46,6 +46,12 @@ decl {extern void redraw_browser();} {public local
decl {extern int show_comments;} {public local
}
+decl {extern int G_use_external_editor;} {public local
+}
+
+decl {extern char G_external_editor_command[512];} {public local
+}
+
decl {extern int show_coredevmenus;} {public local
}
@@ -161,7 +167,8 @@ decl {void scheme_cb(Fl_Choice *, void *);} {public local
Function {make_settings_window()} {} {
Fl_Window settings_window {
label {GUI Settings} open
- xywh {393 191 349 241} type Double hide non_modal
+ xywh {355 85 360 355} type Double resizable
+ code0 {o->size_range(o->w(), o->h());} non_modal visible
} {
Fl_Choice scheme_choice {
label {Scheme: }
@@ -194,7 +201,7 @@ Function {make_settings_window()} {} {
}
}
Fl_Group {} {open
- xywh {116 43 220 126} labelfont 1 align 0
+ xywh {20 43 330 161} labelfont 1 align 0
} {
Fl_Box {} {
label {Options: }
@@ -254,10 +261,38 @@ load_history();}
code2 {recent_spinner->maximum(10);}
code3 {recent_spinner->value(c);}
}
+ Fl_Group {} {open
+ xywh {10 210 337 95} box THIN_UP_BOX color 47 resizable
+ } {
+ Fl_Check_Button use_external_editor_button {
+ label {Use external editor?}
+ callback {G_use_external_editor = use_external_editor_button->value();
+fluid_prefs.set("use_external_editor", G_use_external_editor);
+redraw_browser();}
+ xywh {25 218 209 22} down_box DOWN_BOX labelsize 12
+ code1 {fluid_prefs.get("use_external_editor", G_use_external_editor, 0);}
+ code2 {use_external_editor_button->value(G_use_external_editor);}
+ }
+ Fl_Input editor_command_input {
+ label {Editor Command}
+ callback {strncpy(G_external_editor_command, editor_command_input->value(), sizeof(G_external_editor_command)-1);
+G_external_editor_command[sizeof(G_external_editor_command)-1] = 0;
+fluid_prefs.set("external_editor_command", G_external_editor_command);
+redraw_browser();} selected
+ tooltip {The editor command to open your external text editor.
+Include any necessary flags to ensure your editor does not background itself.
+Examples:
+ gvim -f
+ gedit
+ emacs} xywh {25 264 305 21} labelsize 12 align 5 when 1 textsize 12
+ code1 {fluid_prefs.get("external_editor_command", G_external_editor_command, "", sizeof(G_external_editor_command)-1);}
+ code2 {editor_command_input->value(G_external_editor_command);}
+ }
+ }
Fl_Button {} {
label Close
callback {settings_window->hide();}
- tooltip {Close this dialog.} xywh {276 205 64 25}
+ tooltip {Close this dialog.} xywh {285 320 64 25}
}
}
}
diff --git a/fluid/alignment_panel.h b/fluid/alignment_panel.h
index c914adec2..18ca01dac 100644
--- a/fluid/alignment_panel.h
+++ b/fluid/alignment_panel.h
@@ -27,6 +27,8 @@
extern void load_history();
extern void redraw_browser();
extern int show_comments;
+extern int G_use_external_editor;
+extern char G_external_editor_command[512];
extern int show_coredevmenus;
extern struct Fl_Menu_Item *dbmanager_item;
extern Fl_Preferences fluid_prefs;
@@ -73,6 +75,8 @@ extern Fl_Check_Button *prevpos_button;
extern Fl_Check_Button *show_comments_button;
#include <FL/Fl_Spinner.H>
extern Fl_Spinner *recent_spinner;
+extern Fl_Check_Button *use_external_editor_button;
+extern Fl_Input *editor_command_input;
Fl_Double_Window* make_settings_window();
extern Fl_Menu_Item menu_scheme_choice[];
extern Fl_Double_Window *shell_window;
diff --git a/fluid/fluid.cxx b/fluid/fluid.cxx
index 86eef5c12..404a1bfa3 100644
--- a/fluid/fluid.cxx
+++ b/fluid/fluid.cxx
@@ -84,6 +84,9 @@ extern "C"
#endif // HAVE_LIBPNG && HAVE_LIBZ
}
+//
+// Globals..
+//
static Fl_Help_Dialog *help_dialog = 0;
Fl_Preferences fluid_prefs(Fl_Preferences::USER, "fltk.org", "fluid");
@@ -92,6 +95,9 @@ int gridy = 5;
int snap = 1;
int show_guides = 1;
int show_comments = 1;
+int G_use_external_editor = 0;
+int G_debug = 0;
+char G_external_editor_command[512];
int show_coredevmenus = 1;
// File history info...
@@ -186,6 +192,41 @@ static char* cutfname(int which = 0) {
return name[which];
}
+// Timer to watch for external editor modifications
+// If one or more external editors open, check if their files were modified.
+// If so: reload to ram, update size/mtime records, and change fluid's 'modified' state.
+//
+static void external_editor_timer(void*) {
+ int editors_open = ExternalCodeEditor::editors_open();
+ if ( G_debug ) printf("--- TIMER --- External editors open=%d\n", editors_open);
+ if ( editors_open > 0 ) {
+ // Walk tree looking for files modified by external editors.
+ int modified = 0;
+ for (Fl_Type *p = Fl_Type::first; p; p = p->next) {
+ if ( p->is_code() ) {
+ Fl_Code_Type *code = (Fl_Code_Type*)p;
+ // Code changed by external editor?
+ if ( code->handle_editor_changes() ) { // updates ram, file size/mtime
+ modified++;
+ }
+ if ( code->is_editing() ) { // editor open?
+ code->reap_editor(); // Try to reap; maybe it recently closed
+ }
+ }
+ }
+ if ( modified ) set_modflag(1);
+ }
+ // Repeat timeout if editors still open
+ // The ExternalCodeEditor class handles start/stopping timer, we just
+ // repeat_timeout() if it's already on. NOTE: above code may have reaped
+ // only open editor, which would disable further timeouts. So *recheck*
+ // if editors still open, to ensure we don't accidentally re-enable them.
+ //
+ if ( ExternalCodeEditor::editors_open() ) {
+ Fl::repeat_timeout(2.0, external_editor_timer);
+ }
+}
+
void save_cb(Fl_Widget *, void *v) {
Fl_Native_File_Chooser fnfc;
const char *c = filename;
@@ -368,6 +409,10 @@ void revert_cb(Fl_Widget *,void *) {
}
void exit_cb(Fl_Widget *,void *) {
+
+ // Stop any external editor update timers
+ ExternalCodeEditor::stop_update_timer();
+
if (modflag)
switch (fl_choice("Do you want to save changes to this user\n"
"interface before exiting?", "Cancel",
@@ -401,6 +446,12 @@ void exit_cb(Fl_Widget *,void *) {
undo_clear();
+ // Destroy tree
+ // Doing so causes dtors to automatically close all external editors
+ // and cleans up editor tmp files. Then remove fluid tmpdir /last/.
+ delete_all();
+ ExternalCodeEditor::tmpdir_clear();
+
exit(0);
}
@@ -1697,7 +1748,6 @@ static void sigint(SIGARG) {
}
#endif
-
int main(int argc,char **argv) {
int i = 1;
@@ -1786,6 +1836,9 @@ int main(int argc,char **argv) {
signal(SIGINT,sigint);
#endif
+ // Set (but do not start) timer callback for external editor updates
+ ExternalCodeEditor::set_update_timer_callback(external_editor_timer);
+
grid_cb(horizontal_input, 0); // Makes sure that windows get snap params...
#ifdef WIN32
diff --git a/fluid/print_panel.cxx b/fluid/print_panel.cxx
index f9be6533e..c82857c80 100644
--- a/fluid/print_panel.cxx
+++ b/fluid/print_panel.cxx
@@ -158,7 +158,10 @@ static const char *idata_print_color[] = {
" %%%%%%%%\'\'******** ",
" %%%%%% ****** "
};
-static Fl_Pixmap image_print_color(idata_print_color);
+static Fl_Image *image_print_color() {
+ static Fl_Image *image = new Fl_Pixmap(idata_print_color);
+ return image;
+}
static const char *idata_print_gray[] = {
"24 24 17 1",
@@ -204,7 +207,10 @@ static const char *idata_print_gray[] = {
" %%%%%%%%\'\'******** ",
" %%%%%% ****** "
};
-static Fl_Pixmap image_print_gray(idata_print_gray);
+static Fl_Image *image_print_gray() {
+ static Fl_Image *image = new Fl_Pixmap(idata_print_gray);
+ return image;
+}
Fl_Button *print_output_mode[4]={(Fl_Button *)0};
@@ -457,7 +463,7 @@ Fl_Double_Window* make_print_panel() {
print_output_mode[0]->value(1);
print_output_mode[0]->color(FL_BACKGROUND2_COLOR);
print_output_mode[0]->selection_color(FL_FOREGROUND_COLOR);
- print_output_mode[0]->image(image_print_color);
+ print_output_mode[0]->image( image_print_color() );
} // Fl_Button* print_output_mode[0]
{ print_output_mode[1] = new Fl_Button(150, 50, 40, 30);
print_output_mode[1]->type(102);
@@ -465,7 +471,7 @@ Fl_Double_Window* make_print_panel() {
print_output_mode[1]->down_box(FL_BORDER_BOX);
print_output_mode[1]->color(FL_BACKGROUND2_COLOR);
print_output_mode[1]->selection_color(FL_FOREGROUND_COLOR);
- print_output_mode[1]->image(image_print_color);
+ print_output_mode[1]->image( image_print_color() );
} // Fl_Button* print_output_mode[1]
{ print_output_mode[2] = new Fl_Button(200, 45, 30, 40);
print_output_mode[2]->type(102);
@@ -473,7 +479,7 @@ Fl_Double_Window* make_print_panel() {
print_output_mode[2]->down_box(FL_BORDER_BOX);
print_output_mode[2]->color(FL_BACKGROUND2_COLOR);
print_output_mode[2]->selection_color(FL_FOREGROUND_COLOR);
- print_output_mode[2]->image(image_print_gray);
+ print_output_mode[2]->image( image_print_gray() );
} // Fl_Button* print_output_mode[2]
{ print_output_mode[3] = new Fl_Button(240, 50, 40, 30);
print_output_mode[3]->type(102);
@@ -481,7 +487,7 @@ Fl_Double_Window* make_print_panel() {
print_output_mode[3]->down_box(FL_BORDER_BOX);
print_output_mode[3]->color(FL_BACKGROUND2_COLOR);
print_output_mode[3]->selection_color(FL_FOREGROUND_COLOR);
- print_output_mode[3]->image(image_print_gray);
+ print_output_mode[3]->image( image_print_gray() );
} // Fl_Button* print_output_mode[3]
o->end();
} // Fl_Group* o