diff options
| author | Greg Ercolano <erco@seriss.com> | 2016-07-18 21:12:25 +0000 |
|---|---|---|
| committer | Greg Ercolano <erco@seriss.com> | 2016-07-18 21:12:25 +0000 |
| commit | 8850c5c822ce0878b4d808c46b25463136a69231 (patch) | |
| tree | 07a74f064beca50b2e7ac8f48a22e244eb4a0437 | |
| parent | bcb75b518f47583a1265edfcd1984f6a675cbb60 (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
| -rw-r--r-- | fluid/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | fluid/ExternalCodeEditor_UNIX.cxx | 436 | ||||
| -rw-r--r-- | fluid/ExternalCodeEditor_UNIX.h | 51 | ||||
| -rw-r--r-- | fluid/ExternalCodeEditor_WIN32.cxx | 548 | ||||
| -rw-r--r-- | fluid/ExternalCodeEditor_WIN32.h | 63 | ||||
| -rw-r--r-- | fluid/Fl_Function_Type.cxx | 33 | ||||
| -rw-r--r-- | fluid/Fl_Type.cxx | 1 | ||||
| -rw-r--r-- | fluid/Fl_Type.h | 50 | ||||
| -rw-r--r-- | fluid/Makefile | 2 | ||||
| -rw-r--r-- | fluid/about_panel.cxx | 7 | ||||
| -rw-r--r-- | fluid/alignment_panel.cxx | 49 | ||||
| -rw-r--r-- | fluid/alignment_panel.fl | 41 | ||||
| -rw-r--r-- | fluid/alignment_panel.h | 4 | ||||
| -rw-r--r-- | fluid/fluid.cxx | 55 | ||||
| -rw-r--r-- | fluid/print_panel.cxx | 18 |
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 |
