diff options
| author | Matthias Melcher <github@matthiasm.com> | 2025-03-07 16:34:35 +0100 |
|---|---|---|
| committer | Matthias Melcher <github@matthiasm.com> | 2025-03-07 16:34:48 +0100 |
| commit | 1985aefc0e502048f92b91beef87c0dfbe669fed (patch) | |
| tree | af62874def4590e437a47784b4428d975ceb262f /fluid/ExternalCodeEditor_WIN32.cxx | |
| parent | 42a04c064d4b31c3a85210311f3ada163c406a25 (diff) | |
Restructuring Fluid source files.
Diffstat (limited to 'fluid/ExternalCodeEditor_WIN32.cxx')
| -rw-r--r-- | fluid/ExternalCodeEditor_WIN32.cxx | 632 |
1 files changed, 0 insertions, 632 deletions
diff --git a/fluid/ExternalCodeEditor_WIN32.cxx b/fluid/ExternalCodeEditor_WIN32.cxx deleted file mode 100644 index bb7d5791a..000000000 --- a/fluid/ExternalCodeEditor_WIN32.cxx +++ /dev/null @@ -1,632 +0,0 @@ -// -// External code editor management class for Windows -// -// Copyright 1998-2023 by Bill Spitzak and others. -// -// This library is free software. Distribution and use rights are outlined in -// the file "COPYING" which should have been included with this file. If this -// file is missing or damaged, see the license at: -// -// https://www.fltk.org/COPYING.php -// -// Please see the following page on how to report bugs and issues: -// -// https://www.fltk.org/bugs.php -// - -// Note: This entire file Windows only. - -#include <FL/Fl.H> // Fl_Timeout_Handler.. -#include <FL/fl_ask.H> // fl_alert() -#include <FL/fl_utf8.h> // fl_utf8fromwc() -#include <FL/fl_string_functions.h> // fl_strdup() - -#include "ExternalCodeEditor_WIN32.h" -#include "fluid.h" - -#include <stdio.h> // snprintf() -#include <stdlib.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 wchar_t *wbuf = NULL; -static char *abuf = NULL; - -static wchar_t *utf8_to_wchar(const char *utf8, wchar_t *&wbuf, int lg = -1) { - unsigned len = (lg >= 0) ? (unsigned)lg : (unsigned)strlen(utf8); - unsigned wn = fl_utf8toUtf16(utf8, len, NULL, 0) + 1; // Query length - wbuf = (wchar_t *)realloc(wbuf, sizeof(wchar_t) * wn); - wn = fl_utf8toUtf16(utf8, len, (unsigned short *)wbuf, wn); // Convert string - wbuf[wn] = 0; - return wbuf; -} - -static char *wchar_to_utf8(const wchar_t *wstr, char *&utf8) { - unsigned len = (unsigned)wcslen(wstr); - unsigned wn = fl_utf8fromwc(NULL, 0, wstr, len) + 1; // query length - utf8 = (char *)realloc(utf8, wn); - wn = fl_utf8fromwc(utf8, wn, wstr, len); // convert string - utf8[wn] = 0; - return utf8; -} - -// [Static/Local] Get error message string for last failed WIN32 function. -// Returns a string pointing to static memory. -// -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; - DWORD langid = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); - LPWSTR mbuf = 0; - DWORD msize = 0; - - // Get error message from Windows - msize = FormatMessageW(flags, 0, lastErr, langid, (LPWSTR)&mbuf, 0, NULL); - if ( msize == 0 ) { - _snprintf(emsg, sizeof(emsg), "Error #%ld", (unsigned long)lastErr); - } else { - // Convert message to UTF-8 - fl_utf8fromwc(emsg, sizeof(emsg), mbuf, msize); - // Remove '\r's -- they screw up fl_alert() - char *src = emsg, *dst = emsg; - for ( ; 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) { - utf8_to_wchar(filename, wbuf); - DWORD att = GetFileAttributesW(wbuf); - 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) { - utf8_to_wchar(dirname, wbuf); - DWORD att = GetFileAttributesW(wbuf); - 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() { - close_editor(); // close editor, delete 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 ? fl_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] Wait for editor to close -void ExternalCodeEditor::close_editor() { - if ( G_debug ) printf("close_editor() called: pid=%ld\n", long(pinfo_.dwProcessId)); - // Wait until editor is closed + reaped - while ( is_editing() ) { - switch ( reap_editor() ) { - case -2: // no editor running (unlikely to happen) - return; - case -1: // error - fl_alert("Error reaping external editor\npid=%ld file=%s\nOS error message=%s", - long(pinfo_.dwProcessId), filename(), get_ms_errmsg()); - break; - case 0: // process still running - switch ( fl_choice("Please close external editor\npid=%ld file=%s", - "Force Close", // button 0 - "Closed", // button 1 - 0, // button 2 - long(pinfo_.dwProcessId), filename() ) ) { - case 0: // Force Close - kill_editor(); - continue; - case 1: // Closed? try to reap - continue; - } - break; - case 1: // process reaped - return; - } - } -} - -// [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(); // clears pinfo_ - 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 :/ - utf8_to_wchar(filename(), wbuf); - HANDLE fh = CreateFileW(wbuf, // 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 ) { - DWORD buflen = (DWORD)fsize.QuadPart; - char *buf = (char*)malloc((size_t)buflen + 1); - DWORD count; - if ( ReadFile(fh, buf, buflen, &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 != buflen ) { - fl_alert("ERROR: ReadFile() failed for %s:\n" - "expected %ld bytes, got %ld", - filename(), long(buflen), 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); - utf8_to_wchar(tmpfile, wbuf); - if (DeleteFileW(wbuf) == 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() { - wchar_t tempdirW[FL_PATH_MAX+1]; - char tempdir[FL_PATH_MAX+1]; - if (GetTempPathW(FL_PATH_MAX, tempdirW) == 0) { - strcpy(tempdir, "c:\\windows\\temp"); // fallback - } else { - strcpy(tempdir, wchar_to_utf8(tempdirW, abuf)); - } - 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); - utf8_to_wchar(tmpdir, wbuf); - if ( RemoveDirectoryW(wbuf) == 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) ) { - utf8_to_wchar(dirname, wbuf); - if (CreateDirectoryW(wbuf, 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; - const char *ext = g_project.code_file_name.c_str(); // 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)); - utf8_to_wchar(filename, wbuf); - HANDLE fh = CreateFileW(wbuf, // 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 = (DWORD)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 - STARTUPINFOW 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); - utf8_to_wchar(cmd, wbuf); - // Start editor process - if (CreateProcessW(NULL, // app name - wbuf, // 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 -// > Zeroes out pinfo_ -// > 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 -// If 'pid_reaped' not NULL, returns PID of reaped editor. -// Returns: -// -2 -- editor not open -// -1 -- WaitForSingleObject() failed (get_ms_errmsg() has reason) -// 0 -- process still running -// 1 -- process finished + reaped ('pid_reaped' has pid), pinfo_ set to 0. -// Handles removing tmpfile/zeroing file_mtime/file_size/filename -// -// If return value <=0, 'pid_reaped' is set to zero. -// -int ExternalCodeEditor::reap_editor(DWORD *pid_reaped) { - if ( pid_reaped ) *pid_reaped = 0; - if ( !is_editing() ) return -2; - DWORD msecs_wait = 50; // .05 sec - switch ( WaitForSingleObject(pinfo_.hProcess, msecs_wait) ) { - case WAIT_TIMEOUT: { // process didn't reap, still running - return 0; - } - case WAIT_OBJECT_0: { // reaped - DWORD wpid = pinfo_.dwProcessId; // save pid - reap_cleanup(); // clears pinfo_ - if ( pid_reaped ) *pid_reaped = wpid; // return pid to caller - if ( G_debug ) printf("*** EDITOR REAPED: pid=%ld #open=%d\n", - long(wpid), L_editors_open); - return 1; - } - 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. -// 'code' can be NULL -- edits an empty file if so. -// -// 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; - switch ( reap_editor(&wpid) ) { - case -2: // no editor running (unlikely to happen) - break; - 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; - case 1: // 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; -} |
