OSDN Git Service

Start implementing JANPA integration with Psi4
[molby/Molby.git] / wxSources / MyApp.cpp
index 3f9ef5c..8043438 100755 (executable)
@@ -52,6 +52,7 @@
 #include "ProgressFrame.h"
 #include "GlobalParameterFrame.h"
 #include "GlobalParameterFilesFrame.h"
+#include "RubyDialogFrame.h"
 #include "MyMBConv.h"
 
 #if defined(__WXMSW__)
@@ -81,23 +82,28 @@ MyFrame *frame = (MyFrame *) NULL;
 
 bool gInitCompleted = false;
 
+int gSuppressConsole = 0;  //  Non-zero if console output should be suppressed in non-GUI mode
+int gUseGUI = 1;
+
 IMPLEMENT_APP(MyApp)
 
 //IMPLEMENT_CLASS(MyApp, wxApp)
 
 BEGIN_EVENT_TABLE(MyApp, wxApp)
        EVT_COMMAND(MyDocumentEvent_scriptMenuModified, MyDocumentEvent, MyApp::OnScriptMenuModified)
-       EVT_COMMAND(MyDocumentEvent_openFilesByIPC, MyDocumentEvent, MyApp::OnOpenFilesByIPC)
+       EVT_COMMAND(MyDocumentEvent_openFilesByEvent, MyDocumentEvent, MyApp::OnOpenFilesByEvent)
        EVT_UPDATE_UI_RANGE(myMenuID_MyFirstMenuItem, myMenuID_MyLastMenuItem, MyApp::OnUpdateUI)
        EVT_MENU(myMenuID_ExecuteScript, MyApp::OnExecuteScript)
        EVT_MENU(myMenuID_OpenConsoleWindow, MyApp::OnOpenConsoleWindow)
        EVT_MENU(myMenuID_EmptyConsoleWindow, MyApp::OnEmptyConsoleWindow)
        EVT_MENU(myMenuID_ViewGlobalParameters, MyApp::OnViewGlobalParameters)
        EVT_MENU(myMenuID_ViewParameterFilesList, MyApp::OnViewParameterFilesList)
+       EVT_MENU(myMenuID_BringAllWindowsToFront, MyApp::OnBringAllWindowsToFront)
+    EVT_MENU(wxID_HELP, MyApp::OnHelp)
 #if defined(__WXMAC__)
        EVT_ACTIVATE(MyApp::OnActivate)
 #endif
-       EVT_END_PROCESS(-1, MyApp::OnEndProcess)
+//     EVT_END_PROCESS(-1, MyApp::OnEndProcess)
        EVT_TIMER(-1, MyApp::TimerInvoked)
        EVT_COMMAND(myMenuID_Internal_CheckIfAllWindowsAreGone, MyDocumentEvent, MyApp::CheckIfAllWindowsAreGoneHandler)
 END_EVENT_TABLE()
@@ -107,24 +113,26 @@ END_EVENT_TABLE()
 //  Windows: the directory in which the application executable is located.
 //  UNIX: ?
 wxString
-MyApp::FindResourcePath()
+MyApp::InitResourcePath(int& argc, wxChar **argv)
 {
 #if defined(__WXMAC__)
        CFBundleRef mainBundle = CFBundleGetMainBundle();
        CFURLRef ref = CFBundleCopyResourcesDirectoryURL(mainBundle);
        if (ref != NULL) {
-               UInt8 buffer[256];
+               UInt8 buffer[4096];
                if (CFURLGetFileSystemRepresentation(ref, true, buffer, sizeof buffer)) {
                        wxString dirname((const char *)buffer, WX_DEFAULT_CONV);
                        CFRelease(ref);
+            m_resourcePath = dirname;
                        return dirname;
                }
                CFRelease(ref);
        }
+    m_resourcePath = wxEmptyString;
        return wxEmptyString;
 #elif defined(__WXMSW__)
     wxString str;
-       wxString argv0 = wxTheApp->argv[0];
+       wxString argv0 = argv[0];
        //  Fix dosish path (when invoked from MSYS console, the path may be unix-like)
        //  Note: absolute paths like /c/Molby/... (== c:\Molby\...) is not supported
        {
@@ -135,35 +143,47 @@ MyApp::FindResourcePath()
        }
        //  Is it an absolute path?
     if (wxIsAbsolutePath(argv0)) {
-        return wxPathOnly(argv0);
+        m_resourcePath = wxPathOnly(argv0);
+        return m_resourcePath;
     } else {
         //  Is it a relative path?
         wxString currentDir = wxGetCwd();
         if (currentDir.Last() != wxFILE_SEP_PATH)
             currentDir += wxFILE_SEP_PATH;             
         str = currentDir + argv0;
-        if (wxFileExists(str))
-            return wxPathOnly(str);
+        if (wxFileExists(str)) {
+            m_resourcePath = wxPathOnly(str);
+            return m_resourcePath;
+        }
     }
        //  Search PATH
     wxPathList pathList;
     pathList.AddEnvList(wxT("PATH"));
     str = pathList.FindAbsoluteValidPath(argv0);
-    if (!str.IsEmpty())
-        return wxPathOnly(str);
-    return wxEmptyString;
+    if (!str.IsEmpty()) {
+        m_resourcePath = wxPathOnly(str);
+        return m_resourcePath;
+    }
+    m_resourcePath = wxEmptyString;
+    return m_resourcePath;
 #else
 #error "FindResourcePath is not defined for UNIXes."
 #endif
 }
 
+wxString
+MyApp::FindResourcePath()
+{
+    return wxGetApp().m_resourcePath;
+}
+
 MyApp::MyApp(void)
 {
     m_docManager = NULL;
-       m_progressFrame = NULL;
+       m_progressDialog = NULL;
        m_process = NULL;
-       m_processTerminated = false;
-       m_processExitCode = 0;
+//     m_processTerminated = false;
+//     m_processExitCode = 0;
        countScriptMenu = 0;
        scriptMenuTitles = NULL;
        scriptMenuPositions = NULL;
@@ -186,12 +206,87 @@ MyApp::MyApp(void)
 #endif
 }
 
+//  We override Initialize() instead of OnInit, because wxAppBase::Initialize() calls OnInitGui(), which
+//  makes the program run as a GUI application.
+//  So, we intercept here, and go directly to the execution in the batch mode.
+//  Otherwise, we call the inherited version of Initialize() and the program will run as a normal application.
+bool MyApp::Initialize(int& argc, wxChar **argv)
+{
+    //  Called with a batch mode?
+    if (argc > 1 && wcscmp(argv[1], L"-b") == 0) {
+
+        //  Disable any wxLog functionality (otherwise ::exit() may crash)
+        wxLog::EnableLogging(false);
+
+        gUseGUI = 0;
+        gSuppressConsole = 1;
+        
+        if (argc > 2 && wcscmp(argv[2], L"-v") == 0)
+            gSuppressConsole = 0;
+
+        //  We need these parameters in FindResourcePath(), so we assign them here
+        //this->argc = argc;
+        //this->argv = argv;
+
+        //  Initialize the internal m_resourcePath member
+        InitResourcePath(argc, argv);
+        
+        static const char fname[] = "startup.rb";
+        wxString dirname = FindResourcePath();
+        
+        dirname += wxFILE_SEP_PATH;
+        dirname += wxT("Scripts");
+        wxString cwd = wxGetCwd();
+        wxSetWorkingDirectory(dirname);
+        
+        wxString fnamestr(fname, wxConvFile);
+        Molby_startup(wxFileExists(fnamestr) ? fname : NULL, (const char *)dirname.mb_str(wxConvFile));
+        
+        wxSetWorkingDirectory(cwd);
+        
+        //  Build ARGV
+        int c = (gSuppressConsole ? 2 : 3);
+        int i = 1;
+        int status;
+        if (c >= argc) {
+            if (gSuppressConsole) {
+                fprintf(stderr, "The script is not given\n");
+                exit(1);
+            } else exit(0);  //  Show startup message and exit
+        }
+        wxString argv_script;
+        while (i + c < argc) {
+            wxString arg(argv[i + c]);
+            arg.Replace(wxT("\'"), wxT("\\\'"));
+            argv_script += wxString::Format(wxT("ARGV[%d] = \'"), i - 1);
+            argv_script += arg;
+            argv_script += wxT("\'\n");
+            i++;
+        }
+        gSuppressConsole = 0;  //  Console output is no longer suppressed (startup is done)
+        status = Molby_loadScript(argv_script.mb_str(wxConvFile), 0);
+        if (status == 0) {
+            wxString arg2(argv[c]);
+            status = Molby_loadScript(arg2.mb_str(wxConvFile), 1);
+        }
+        if (status != 0) {
+            Ruby_showError(status);
+        }
+        //  Force exit
+        ::exit(status);
+    } else {
+        //  Call the inherited version
+        return wxApp::Initialize(argc, argv);
+    }
+}
+
 bool MyApp::OnInit(void)
 {
        //  Set defaults
-#ifdef __WXMAC__
+#if defined(__WXMAC__) || defined(__WXOSX__)
        wxSystemOptions::SetOption(wxT("mac.listctrl.always_use_generic"), 1);
        wxSystemOptions::SetOption(wxT("osx.openfiledialog.always-show-types"), 1);
+    wxSystemOptions::SetOption(wxOSX_FILEDIALOG_ALWAYS_SHOW_TYPES, 1);
 #endif
 
 #if __WXMSW__
@@ -254,10 +349,14 @@ bool MyApp::OnInit(void)
        new wxDocTemplate(m_docManager, _T("Z Matrix"), _T("*.zmat"), _T(""), _T("zmat"), _T("Molecule Doc"), _T("Molecule View"), CLASSINFO(MyDocument), CLASSINFO(MoleculeView));
        new wxDocTemplate(m_docManager, _T("Any Molecule"), _T("*.*"), _T(""), _T(""), _T("Molecule Doc"), _T("Molecule View"), CLASSINFO(MyDocument), CLASSINFO(MoleculeView));
 
+    // Init image handlers
+    MyAppCallback_initImageHandlers();
+
        // Create the main frame window
        frame = new MyFrame((wxDocManager *) m_docManager, (wxFrame *) NULL,
-                      _T("Molby"), wxPoint(0, 0), wxSize(800, 600),
+                      _T("Molby"), wxDefaultPosition, wxDefaultSize,
                       wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE);
+    frame->SetClientSize(FromFrameDIP(frame, wxSize(800, 600)));
 
        // Give it an icon (this is ignored in MDI mode: uses resources)
 #ifdef __WXMSW__
@@ -299,6 +398,7 @@ bool MyApp::OnInit(void)
        {
                extern int gRevisionNumber;
                static const char fname[] = "startup.rb";
+        InitResourcePath(argc, argv);
                wxString dirname = FindResourcePath();
        
                dirname += wxFILE_SEP_PATH;
@@ -340,7 +440,7 @@ bool MyApp::OnInit(void)
                        files.append(argv[i]);
                        files.append(wxT("\n"));
                }
-               OnOpenFiles(files);
+               RequestOpenFilesByEvent(files);
        }
        
        gInitCompleted = true;
@@ -352,17 +452,51 @@ bool MyApp::OnInit(void)
 //  kind == 0: main menu
 //  kind == 1: molecule window
 //  kind == 2: console window
+//  kind == 3: Ruby dialog (non-modal)
 wxMenuBar *
 MyApp::CreateMenuBar(int kind, wxMenu **out_file_history_menu, wxMenu **out_edit_menu)
 {
+
+#if __WXMSW__
+       if (kind == 3) {
+
+               //  Simplified menu
+               wxMenu *file_menu = new wxMenu;
+               file_menu->Append(wxID_CLOSE, _T("&Close\tCtrl-W"));
+
+               wxMenu *edit_menu = new wxMenu;
+               edit_menu->Append(wxID_UNDO, _T("&Undo\tCtrl-Z"));
+               edit_menu->Append(wxID_REDO, _T("&Redo"));
+               edit_menu->AppendSeparator();
+               edit_menu->Append(wxID_CUT, _T("Cut\tCtrl-X"));
+               edit_menu->Append(wxID_COPY, _T("Copy\tCtrl-C"));
+               edit_menu->Append(wxID_PASTE, _T("Paste\tCtrl-V"));
+               edit_menu->Append(wxID_CLEAR, _T("Clear"));
+               edit_menu->AppendSeparator();
+               edit_menu->Append(wxID_SELECTALL, _T("Select All\tCtrl-A"));
+               
+               wxMenu *help_menu = new wxMenu;
+               help_menu->Append(wxID_ABOUT, _T("&About...\tF1"));
+        help_menu->Append(wxID_HELP, _T("&Molby Help"));
+
+               wxMenuBar *menu_bar = new wxMenuBar;
+               
+               menu_bar->Append(file_menu, _T("&File"));
+               menu_bar->Append(edit_menu, _T("&Edit"));
+               menu_bar->Append(help_menu, _T("&Help"));
+               
+               return menu_bar;
+       }
+#endif
        
-       //// Make a menubar
        wxMenu *file_menu = new wxMenu;
+       wxMenu *file_history_menu = NULL;
 
        file_menu->Append(wxID_NEW, _T("&New...\tCtrl-N"));
        file_menu->Append(wxID_OPEN, _T("&Open...\tCtrl-O"));
        if (out_file_history_menu != NULL) {
-               *out_file_history_menu = new wxMenu;
+               file_history_menu = new wxMenu;
+               *out_file_history_menu = file_history_menu;
                file_menu->AppendSubMenu(*out_file_history_menu, _T("Open Recent"));
                m_docManager->FileHistoryAddFilesToMenu(*out_file_history_menu);
                m_docManager->FileHistoryUseMenu(*out_file_history_menu);  //  Should be removed when menu is discarded
@@ -467,7 +601,8 @@ MyApp::CreateMenuBar(int kind, wxMenu **out_file_history_menu, wxMenu **out_edit
 
        wxMenu *help_menu = new wxMenu;
        help_menu->Append(wxID_ABOUT, _T("&About...\tF1"));
-       
+    help_menu->Append(wxID_HELP, _T("&Molby Help"));
+
        wxMenuBar *menu_bar = new wxMenuBar;
        
        menu_bar->Append(file_menu, _T("&File"));
@@ -475,6 +610,14 @@ MyApp::CreateMenuBar(int kind, wxMenu **out_file_history_menu, wxMenu **out_edit
        menu_bar->Append(view_menu, _T("View"));
        menu_bar->Append(md_menu, _T("MM/MD"));
        menu_bar->Append(script_menu, _T("&Script"));
+       
+#if defined(__WXMAC__)
+       wxMenu *window_menu = new wxMenu;
+       window_menu->Append(myMenuID_BringAllWindowsToFront, _T("Bring All to Front"));
+       window_menu->AppendSeparator();
+       menu_bar->Append(window_menu, _T("Window"));
+#endif
+       
        menu_bar->Append(help_menu, _T("&Help"));
        
        UpdateScriptMenu(menu_bar);
@@ -484,70 +627,40 @@ MyApp::CreateMenuBar(int kind, wxMenu **out_file_history_menu, wxMenu **out_edit
        return menu_bar;
 }
 
-#if __WXMAC__
+#if defined(__WXMAC__) || defined(__WXOSX__)
 /*  When the application is launched without any documents, an empty document is opened.
     This should be implemented by overriding this special method; parsing argc/argv does
     not work, because the list of files is passed through an Apple Event.  */
 void
 MyApp::MacNewFile()
 {
+       if (m_docManager == NULL)
+               return;  //  Initialization is not yet complete
        m_docManager->CreateDocument(_T(""), wxDOC_NEW);
 }
 
 void
 MyApp::MacOpenFile(const wxString &fileName)
 {
-       wxString files(fileName);
-       OnOpenFiles(files);
+    wxString file(fileName);
+       RequestOpenFilesByEvent(file);
 }
 
-/*  Open given files: instead of calling MacOpenFile() for each entry, build a file list
-    and call MyApp::OnOpenFiles()  */
-short
-MyApp::MacHandleAEODoc(const WXEVENTREF event, WXEVENTREF WXUNUSED(reply))
-{
-    AEDescList docList;
-    AEKeyword keywd;
-    DescType returnedType;
-    Size actualSize;
-    long itemsInList;
-    OSErr err;
-    short i;
-       
-       return noErr;  /*  TODO: handle open Apple event  */
-       
-    err = AEGetParamDesc((AppleEvent *)event, keyDirectObject, typeAEList, &docList);
-    if (err != noErr)
-        return err;
-       
-    err = AECountItems(&docList, &itemsInList);
-    if (err != noErr)
-        return err;
-       
-    ProcessSerialNumber PSN ;
-    PSN.highLongOfPSN = 0 ;
-    PSN.lowLongOfPSN = kCurrentProcess ;
-    SetFrontProcess( &PSN ) ;
-       
-    wxString fName, fNameList;
-    FSRef theRef ;
-       
-    for (i = 1; i <= itemsInList; i++)
-    {
-        AEGetNthPtr(
-                                       &docList, i, typeFSRef, &keywd, &returnedType,
-                                       (Ptr)&theRef, sizeof(theRef), &actualSize);
-    //    fName = wxMacFSRefToPath( &theRef ) ;
-               fNameList.append(fName);
-               fNameList.append(wxT("\n"));
+void
+MyApp::MacOpenFiles(const wxArrayString &fileNames)
+{
+    wxString fnames;
+    int i, n;
+    n = fileNames.GetCount();
+    for (i = 0; i < n; i++) {
+        fnames = fnames + fileNames[i];
+        if (i < n - 1)
+            fnames = fnames + wxT("\n");
     }
-       
-       OnOpenFiles(fNameList);
-       
-    return noErr;
+    OnOpenFiles(fnames);
 }
 
-#endif
+#endif  // WXMAC
 
 int
 MyApp::OnExit(void)
@@ -623,8 +736,11 @@ sModifyMenuForFilterMode(wxMenuBar *mbar)
 void
 MyApp::ShowProgressPanel(const char *mes)
 {
-       wxString string((mes ? mes : ""), WX_DEFAULT_CONV);
-       if (m_progressFrame == NULL) {
+    wxString string((mes ? mes : ""), WX_DEFAULT_CONV);
+    if (m_progressDialog == NULL) {
+        m_progressDialog = new wxProgressDialog(wxT("Progress"), mes, 100, NULL, wxPD_APP_MODAL | wxPD_CAN_ABORT);
+    }
+/*
 #if __WXMAC__
                {
                        wxMenuBar *mbar = ((wxFrame *)GetTopWindow())->GetMenuBar();
@@ -637,13 +753,19 @@ MyApp::ShowProgressPanel(const char *mes)
                m_progressFrame = new ProgressFrame(_T("Progress"), string);
                m_progressCanceled = false;
                m_progressValue = -1;
-       }
+*/
 }
 
 void
 MyApp::HideProgressPanel()
 {
-       if (m_progressFrame != NULL) {
+    if (m_progressDialog != NULL) {
+        m_progressDialog->Hide();
+        m_progressDialog->Destroy();
+        m_progressDialog = NULL;
+    }
+/*
+    if (m_progressFrame != NULL) {
                m_progressFrame->Hide();
                m_progressFrame->Destroy();
                m_progressFrame = NULL;
@@ -657,35 +779,39 @@ MyApp::HideProgressPanel()
                }
 #endif
        }
+*/
 }
 
 void
 MyApp::SetProgressValue(double dval)
 {
-       if (m_progressFrame != NULL) {
-               m_progressFrame->SetProgressValue(dval);
-       }
+    if (m_progressDialog != NULL) {
+        if (dval >= 0)
+            m_progressDialog->Update((int)(dval * 100));
+        else
+            m_progressDialog->Pulse();
+    }
 }
 
 void
 MyApp::SetProgressMessage(const char *mes)
 {
-       if (m_progressFrame != NULL) {
+       if (m_progressDialog != NULL) {
                wxString string((mes ? mes : ""), WX_DEFAULT_CONV);
-               m_progressFrame->SetProgressMessage(string);
+               m_progressDialog->Update(0, string);
        }
 }
 
 int
 MyApp::IsInterrupted()
 {
-       if (m_progressFrame != NULL)
-               return m_progressFrame->CheckInterrupt();
-       else {
-               if (::wxGetKeyState(WXK_ESCAPE))
-                       return 1;
-               else return 0;          
-       }
+    if (m_progressDialog != NULL)
+        return m_progressDialog->WasCancelled();
+    else {
+        if (::wxGetKeyState(WXK_ESCAPE))
+            return 1;
+        else return 0;
+    }
 }
 
 void
@@ -723,6 +849,25 @@ MyApp::OnViewParameterFilesList(wxCommandEvent &event)
        parameterFilesFrame->Raise();
 }
 
+void
+MyApp::OnBringAllWindowsToFront(wxCommandEvent &event)
+{
+       int size = 0, n;
+       wxWindowList::iterator iter;
+       wxTopLevelWindow **wins;
+       size = wxTopLevelWindows.size();
+       if (size > 0) {
+               wins = (wxTopLevelWindow **)calloc(sizeof(wxTopLevelWindow *), size);
+               for (iter = wxTopLevelWindows.begin(), n = 0; iter != wxTopLevelWindows.end(); ++iter, ++n) {
+                       wins[n] = (wxTopLevelWindow *)(*iter);
+               }
+               for (n = 0; n < size; n++) {
+                       if (wins[n]->IsShown())
+                               wins[n]->Raise();
+               }
+       }
+}
+
 int
 MyApp::LookupScriptMenu(const char *title)
 {
@@ -1003,6 +1148,18 @@ MyApp::OnUpdateUI(wxUpdateUIEvent& event)
        MainView *mview = MainViewCallback_activeView();
        if (uid >= myMenuID_CustomScript && uid < myMenuID_CustomScript + countScriptMenu) {
                //  Check the script menu
+               //  If the frontmost window is RubyDialogFrame, then disable any script menu command
+               wxWindow *w;
+#if defined(__WXMAC__)
+               void *MacGetActiveWindow(void);
+               w = wxDynamicCast(wxNonOwnedWindow::GetFromWXWindow((WXWindow)MacGetActiveWindow()), wxWindow);
+#else
+               w = wxGetActiveWindow();
+#endif
+               if (wxDynamicCast(w, RubyDialogFrame) != NULL) {
+                       event.Enable(false);
+                       return;
+               }
                Molecule *mol;
                int enabled, checked;
                char *title;
@@ -1013,7 +1170,11 @@ MyApp::OnUpdateUI(wxUpdateUIEvent& event)
                checked = -1;
                title = NULL;
                enabled = Ruby_UpdateUI(index, mol, &checked, &title);
-               if (checked >= 0)
+               if (checked >= 0
+#if wxCHECK_VERSION(3,2,0)
+            && event.IsCheckable()
+#endif
+            )
                        event.Check(checked != 0);
                if (title != NULL) {
                        wxString wtext(title, WX_DEFAULT_CONV);
@@ -1023,6 +1184,10 @@ MyApp::OnUpdateUI(wxUpdateUIEvent& event)
                event.Enable(enabled != 0);
        } else if (uid >= myMenuID_PredefinedFragment && uid <= myMenuID_PredefinedFragment + m_CountNamedFragments) {
                event.Enable(true);
+#if defined(__WXMAC__)
+       } else if (uid >= wxID_OSX_MENU_FIRST && uid <= wxID_OSX_MENU_LAST) {
+               event.Enable(true);
+#endif
        } else {
                switch (uid) {
                        case myMenuID_ExecuteScript:
@@ -1065,7 +1230,7 @@ MyApp::OnExecuteScript(wxCommandEvent &event)
                if (retval == (RubyValue)6 && status == -1)
                        MyAppCallback_errorMessageBox("Cannot open Ruby script %s", (const char *)path.mb_str(wxConvFile));
                else if (status != 0)
-                       Molby_showError(status);
+                       Ruby_showError(status);
        }
        dialog->Destroy();
 }
@@ -1082,21 +1247,34 @@ MyApp::OnActivate(wxActivateEvent &event)
 }
 
 void
-MyApp::RequestOpenFilesByIPC(wxString& files)
+MyApp::RequestOpenFilesByEvent(wxString& files)
 {
+    /*  We do not respond to "open file" event (either via IPC [MSW] or Apple Event [Mac])
+        while we are running something else  */
+    if (m_progressDialog != NULL || gMolbyIsCheckingInterrupt || gMolbyRunLevel > 0)
+        return;
+
        if (m_pendingFilesToOpen != NULL)
                m_pendingFilesToOpen->Append(files);
        else
                m_pendingFilesToOpen = new wxString(files);
-       wxCommandEvent myEvent(MyDocumentEvent, MyDocumentEvent_openFilesByIPC);
+    if (!m_pendingFilesToOpen->EndsWith(wxT("\n")))
+        m_pendingFilesToOpen->Append(wxT("\n"));
+       wxCommandEvent myEvent(MyDocumentEvent, MyDocumentEvent_openFilesByEvent);
        wxPostEvent(this, myEvent);
 }
 
 void
-MyApp::OnOpenFilesByIPC(wxCommandEvent& event)
+MyApp::OnOpenFilesByEvent(wxCommandEvent& event)
 {
        if (m_pendingFilesToOpen == NULL)
                return;
+    if (!gInitCompleted) {
+        //  Repost this event and try again later
+        wxCommandEvent myEvent(MyDocumentEvent, MyDocumentEvent_openFilesByEvent);
+        wxPostEvent(this, myEvent);
+        return;
+    }
        OnOpenFiles(*m_pendingFilesToOpen);
        delete m_pendingFilesToOpen;
        m_pendingFilesToOpen = NULL;
@@ -1122,7 +1300,7 @@ MyApp::OnOpenFiles(const wxString &files)
                                if (retval == (RubyValue)6 && status == -1)
                                        MyAppCallback_errorMessageBox("Cannot open Ruby script: %s", (const char *)file.mb_str(wxConvFile));
                                else
-                                       Molby_showError(status);
+                                       Ruby_showError(status);
                                return false;
                        }
                } else {
@@ -1218,6 +1396,7 @@ MyApp::GetGlobalParameterListCtrl()
 static FILE *fplog;
 #endif
 
+#if 0
 void
 MyApp::OnEndProcess(wxProcessEvent &event)
 {
@@ -1227,22 +1406,21 @@ MyApp::OnEndProcess(wxProcessEvent &event)
        if (fplog != NULL)
                fprintf(fplog, "OnEndProcess called\n");
 #endif
-       delete m_process;
-       m_process = NULL;
+//     delete m_process;
+//     m_process = NULL;
 }
+#endif
 
 int
-MyApp::CallSubProcess(const char *cmdline, const char *procname, int (*callback)(void *), void *callback_data, FILE *fpout, FILE *fperr)
+MyApp::CallSubProcess(const char *cmdline, const char *procname, int (*callback)(void *), void *callback_data, FILE *fpout, FILE *fperr, int *exitstatus_p, int *pid_p)
 {
        int status = 0;
        int callback_result = 0;
-       int count = 0;
        bool progress_panel = false;
        char buf[256];
-       size_t len, len_total;
        wxString cmdstr(cmdline, WX_DEFAULT_CONV);
-       wxLongLong startTime;
-       
+    wxLongLong startTime, lastTime, presentTime;
+
        if (m_process != NULL)
                return -1;  //  Another process is already running (CallSubProcess() allows only one subprocess)
        
@@ -1252,35 +1430,35 @@ MyApp::CallSubProcess(const char *cmdline, const char *procname, int (*callback)
        //  Show progress panel
        if (procname != NULL) {
                snprintf(buf, sizeof buf, "Running %s...", procname);
-               ShowProgressPanel(buf);
+        ShowProgressPanel(buf);
                progress_panel = true;
        }
-       startTime = wxGetUTCTimeMillis();
+       startTime = lastTime = wxGetUTCTimeMillis();
        
        //  Create log file in the document home directory
 #if LOG_SUBPROCESS
+    wxDateTime dateTime;
+    dateTime.SetToCurrent();
        int nn = 0;
        {
                char *dochome = MyAppCallback_getDocumentHomeDir();
-               snprintf(buf, sizeof buf, "%s/%s.log", dochome, (procname ? procname : "subprocess"));
+               snprintf(buf, sizeof buf, "%s/molby_subprocess.log", dochome);
                free(dochome);
-               fplog = fopen(buf, "w");
+               fplog = fopen(buf, "a");
                if (fplog == NULL)
                        return -1;
        }
 #endif
 
        //  Create proc object and call subprocess
-       m_process = new wxProcess(this, -1);
+       m_process = new wxBetterProcess(this, -1);
        m_process->Redirect();
        int flag = wxEXEC_ASYNC;
        flag |= wxEXEC_MAKE_GROUP_LEADER;
-       m_processTerminated = false;
-       m_processExitCode = 0;
        long pid = ::wxExecute(cmdstr, flag, m_process);
        if (pid == 0) {
-               if (procname != NULL)
-                       HideProgressPanel();
+        if (progress_panel)
+            HideProgressPanel();
                delete m_process;
 #if LOG_SUBPROCESS
                fprintf(fplog, "Cannot start '%s'\n", cmdline);
@@ -1288,205 +1466,155 @@ MyApp::CallSubProcess(const char *cmdline, const char *procname, int (*callback)
 #endif
                return -1;
        }
+       if (pid_p != NULL)
+               *pid_p = pid;
 #if LOG_SUBPROCESS
-       fprintf(fplog, "[DEBUG]pid = %ld\n", pid);
+       fprintf(fplog, "%s[DEBUG]pid = %ld\n", (const char *)(dateTime.FormatISOCombined(' ')), pid);
        fflush(fplog);
 #endif
        
        //  Wait until process ends or user interrupts
-       wxInputStream *in;
-       wxInputStream *err;
-       len_total = 0;
-       char *membuf = NULL;
-       int memsize = 0;
-       bool processShouldTerminate = false;
-       while (1) {
-               while (m_process != NULL && (m_process->IsInputAvailable())) {
-                       in = m_process->GetInputStream();
-                       in->Read(buf, sizeof buf - 1);
-                       if ((len = in->LastRead()) > 0) {
-                               buf[len] = 0;
-                               len_total += len;
-#if LOG_SUBPROCESS
-                               fprintf(fplog, "%s", buf);
-                               fflush(fplog);
-#endif
-                               if (callback == DUMMY_CALLBACK) {
-                                       if (memsize < len_total + 1) {
-                                               char *p = (char *)realloc(membuf, len_total + 1);
-                                               if (p != NULL) {
-                                                       membuf = p;
-                                                       memmove(membuf + len_total - len, buf, len + 1);
-                                                       memsize = len_total + 1;
-                                               }
-                                       }
-                               } else if (fpout != NULL && fpout != (FILE *)1) {
-                                       fputs(buf, fpout);
-                               } else if (fpout == (FILE *)1) {
-                                       MyAppCallback_setConsoleColor(0);
-                                       MyAppCallback_showScriptMessage("%s", buf);
-                               }
-                       }
-               }
-               while (m_process != NULL && (m_process->IsErrorAvailable())) {
-                       err = m_process->GetErrorStream();
-                       err->Read(buf, sizeof buf - 1);
-                       if ((len = err->LastRead()) > 0) {
-                               buf[len] = 0;
-                               len_total += len;
+    wxMemoryBuffer memBuffer;  //  Buffer to store standard output
+    bool interrupted = false;
+    wxString bufstr;
+    wxString buferrstr;
+    while (1) {
+        int len1, len2;
+        lastTime = wxGetUTCTimeMillis();
+        while ((len1 = m_process->GetLine(bufstr)) > 0) {
 #if LOG_SUBPROCESS
-                               fprintf(fplog, "%s", buf);
-                               fflush(fplog);
-#endif
-                               if (fperr != NULL && fperr != (FILE *)1) {
-                                       fputs(buf, fperr);
-                               } else if (fpout == (FILE *)1) {
-                                       MyAppCallback_setConsoleColor(1);
-                                       MyAppCallback_showScriptMessage("\n%s", buf);
-                                       MyAppCallback_setConsoleColor(0); 
-                               }
-                       }
-               }
-               if (progress_panel == false) {
-                       ::wxSafeYield(NULL);  //  This seems necessary to get OnEndProcess called
-                       if (++count == 40) {
-                               ShowProgressPanel("Running subprocess...");
-                               progress_panel = true;
-                       }
-               }
-               if (m_processTerminated) {
-                       //  OnEndProcess has been called
-                       if (m_processExitCode != 0) {
-                               /*  Error from subprocess  */
-                               status = (m_processExitCode & 255);
-                       } else status = 0;
-                       break;
-               }
-       
-               /*  In some cases, wxProcess cannot detect the termination of the subprocess. */
-               /*  So here are the platform-dependent examination   */
-               /*  2014.3.23. This part of code is removed, in the hope that as of 3.0.0
-                   wxWidgets now reports the termination of the subprocess correctly. (T.Nagata) */
-#if __WXMSW__
-               if (0) {
-                // get the process handle to operate on
-                       HANDLE hProcess = ::OpenProcess(SYNCHRONIZE |
-                                                                               PROCESS_TERMINATE |
-                                                                               PROCESS_QUERY_INFORMATION,
-                                                                               false, // not inheritable
-                                                                               (DWORD)pid);
-                       if (hProcess == NULL) {
-                               if (::GetLastError() != ERROR_ACCESS_DENIED) {
-                                       processShouldTerminate = true;
-                                       status = 255;
-                               }
-                       } else {
-                               DWORD exitCode;
-                               if (::GetExitCodeProcess(hProcess, &exitCode) && exitCode != STILL_ACTIVE) {
-                                       processShouldTerminate = true;
-                                       status = exitCode & 255;
-                               }
-                               ::CloseHandle(hProcess);
-                       }
-               }
-#else
-               if (0 && waitpid(pid, &status, WNOHANG) != 0) {
-                       processShouldTerminate = true;
-                       //proc->Detach();
-                       status = WEXITSTATUS(status);
-                       //break;
-               }
+            dateTime.SetToCurrent();
+            fprintf(fplog, "%s[STDOUT]%s", (const char *)(dateTime.FormatISOCombined(' ')), (const char *)bufstr);
+            fflush(fplog);
 #endif
-               if (processShouldTerminate) {
-                       int count1;
+            if (callback == DUMMY_CALLBACK) {
+                const char *p = (const char *)bufstr;
+                memBuffer.AppendData(p, strlen(bufstr));
+            } else if (fpout != NULL && fpout != (FILE *)1) {
+                fputs((const char *)bufstr, fpout);
+            } else if (fpout == (FILE *)1) {
+                MyAppCallback_setConsoleColor(0);
+                MyAppCallback_showScriptMessage("%s", (const char *)bufstr);
+            }
+            presentTime = wxGetUTCTimeMillis();
+            if (presentTime > lastTime + 25) {
+                presentTime = lastTime;
+                break;
+            }
+        }
+        while ((len2 = m_process->GetErrorLine(buferrstr)) > 0) {
 #if LOG_SUBPROCESS
-                       if (fplog) {
-                               fprintf(fplog, "OnEndProcess *NOT* called\n");
-                               fflush(fplog);
-                       }
+            dateTime.SetToCurrent();
+            fprintf(fplog, "%s[STDERR]%s", (const char *)(dateTime.FormatISOCombined(' ')), buf);
+            fflush(fplog);
 #endif
-                       for (count1 = 100; count1 > 0; count1--) {
-                               if (!wxProcess::Exists(pid))
-                                       break;
-                               ::wxMilliSleep(10);
-                       }
-                       if (count1 == 0)
-                               m_process->Detach();
-                       {
-                               char *dochome = MyAppCallback_getDocumentHomeDir();
-                               FILE *fp1;
-                               snprintf(buf, sizeof buf, "%s/%s.log", dochome, (procname ? procname : "subprocess"));
-                               free(dochome);
-                               fp1 = fopen(buf, "w");
-                               if (fp1 != NULL) {
-                                       fprintf(fp1, "OnEndProcess *NOT* called: count1 = %d\n", count1);
-                                       ::wxMilliSleep(500);
-                                       if (m_processTerminated)
-                                               fprintf(fp1, "It looks like OnEndProcess is called after our checking.\n");
-                                       fclose(fp1);
-                               }
-                       }
-                       
-                       break;
-               }
-               
+            if (fperr != NULL && fperr != (FILE *)1) {
+                fputs((const char *)buferrstr, fperr);
+            } else if (fpout == (FILE *)1) {
+                MyAppCallback_setConsoleColor(1);
+                MyAppCallback_showScriptMessage("\n%s", (const char *)buferrstr);
+                MyAppCallback_setConsoleColor(0);
+            }
+            presentTime = wxGetUTCTimeMillis();
+            if (presentTime > lastTime + 25) {
+                presentTime = lastTime;
+                break;
+            }
+        }
+        if (len1 < 0 && len2 < 0) {
+            //  The standard/error outputs are exhausted; the process should have terminated
+            //  (Normally, this should be detected by wxBetterProcess::OnTerminate())
+            interrupted = true;
+        }
+        ::wxMilliSleep(25);
+        if (callback != NULL && callback != DUMMY_CALLBACK) {
+            callback_result = (*callback)(callback_data);
+            if (callback_result != 0)
+                interrupted = true;
+        }
+        ::wxSafeYield();  //  This allows updating console and wxProcess status
+        if (progress_panel) {
+            SetProgressValue(-1);
+            if (IsInterrupted())
+                interrupted = true;
+        }
+
 #if LOG_SUBPROCESS
-               if (++nn >= 10) {
-                       fprintf(fplog, "[DEBUG]pid %ld exists\n", pid);
-                       fflush(fplog);
-                       nn = 0;
-               }
+        if (++nn >= 10) {
+            dateTime.SetToCurrent();
+            fprintf(fplog, "%s[DEBUG]pid %ld exists\n", (const char *)(dateTime.FormatISOCombined(' ')), pid);
+            fflush(fplog);
+            nn = 0;
+        }
 #endif
-               ::wxMilliSleep(25);
-               if (wxGetApp().IsInterrupted() || (callback != NULL && callback != DUMMY_CALLBACK && (callback_result = (*callback)(callback_data)) != 0)) {
-                       /*  User interrupt  */
-                       int kflag = wxKILL_CHILDREN;
-                       wxKillError rc;
-                       if (
+        if (m_process->IsTerminated() || !wxProcess::Exists(pid)) {
+            /*  The subprocess has terminated  */
+            status = m_process->GetStatus();
+            break;
+        } else if (interrupted) {
+            /*  User interrupt  */
+            int kflag = wxKILL_CHILDREN;
+            wxKillError rc;
+            if (
 #if __WXMSW__
-                               myKillAllChildren(pid, wxSIGKILL, &rc) != 0
+                myKillAllChildren(pid, wxSIGKILL, &rc) != 0
 #else
-                               ::wxKill(pid, wxSIGTERM, &rc, kflag) != 0
-#endif
-                               ) {
-                               switch (rc) {
-                                       case wxKILL_BAD_SIGNAL: status = -3; break; /* No such signal */
-                                       case wxKILL_ACCESS_DENIED: status = -4; break; /*  Permission denied  */
-                                       case wxKILL_NO_PROCESS: status = -5; break; /*  No such process  */
-                                       default: status = -6; break;  /*  unknown error  */
-                               }
-                       } else {
-                               if (callback_result != 0)
-                                       status = -3;  /*  Interrupt from callback  */
-                               else
-                                       status = -2;  /*  User interrupt  */
-                       }
-                       m_process->Detach();
-                       break;
-               }
-       }
-#if LOG_SUBPROCESS
-       fclose(fplog);
+                ::wxKill(pid, wxSIGTERM, &rc, kflag) != 0
 #endif
+                ) {
+                switch (rc) {
+                    case wxKILL_BAD_SIGNAL: status = -3; break; /* No such signal */
+                    case wxKILL_ACCESS_DENIED: status = -4; break; /*  Permission denied  */
+                    case wxKILL_NO_PROCESS: status = -5; break; /*  No such process  */
+                    default: status = -6; break;  /*  unknown error  */
+                }
+            } else {
+                if (callback_result != 0)
+                    status = -3;  /*  Interrupt from callback  */
+                else
+                    status = -2;  /*  User interrupt  */
+            }
+            m_process->Detach();
+            m_process = NULL;
+            break;
+        }
+    }
+    
+    if (exitstatus_p != NULL)
+        *exitstatus_p = status;
 
-       if (progress_panel)
-               HideProgressPanel();
+    if (progress_panel) {
+        HideProgressPanel();
+    }
+    
+       if (m_process != NULL) {
+        m_process->Detach();
+               m_process = NULL;
+       }
 
        if (callback == DUMMY_CALLBACK) {
+        char *membuf = NULL;
+        size_t memsize = 0;
+        memBuffer.AppendByte(0);
+        memsize = memBuffer.GetDataLen();
+        membuf = (char *)malloc(memsize);
+        if (membuf != NULL) {
+            memmove(membuf, memBuffer.GetData(), memsize);
 #if __WXMSW__
-               if (membuf != NULL) {
-                       /*  Convert "\r\n" to "\n"  */
-                       char *p, *pend;
-                       p = pend = membuf + strlen(membuf);
-                       while (--p >= membuf) {
-                               if (*p == '\r') {
-                                       memmove(p, p + 1, pend - p);
-                                       pend--;
-                               }
-                       }
-               }
+            {
+                /*  Convert "\r\n" to "\n"  */
+                char *p, *pend;
+                p = pend = membuf + strlen(membuf) + 1;
+                while (--p >= membuf) {
+                    if (*p == '\r') {
+                        memmove(p, p + 1, pend - p);
+                        pend--;
+                    }
+                }
+            }
 #endif
-               *((char **)callback_data) = membuf;
+            *((char **)callback_data) = membuf;
+        }
        }
 
        return status;
@@ -1587,6 +1715,130 @@ MyApp::CheckIfAllWindowsAreGone(wxTopLevelWindow *frame)
        this->AddPendingEvent(myEvent);
 }
 
+void
+MyApp::OnHelp(wxCommandEvent& WXUNUSED(event) )
+{
+    static wxString url;
+    if (url.IsEmpty()) {
+        url = FindResourcePath();
+#if defined(__WXMSW__)
+        if (url.SubString(0, 1) == wxT("\\\\")) {
+            //  Network drive: convert to the drive letter
+            wxBetterProcess *process = new wxBetterProcess(this, -1);
+            process->Redirect();
+            long pid = ::wxExecute("net use", wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, process);
+            if (pid != 0) {
+                wxRegEx re(wxT("^[[:space:]]+([A-Za-z]:)[[:space:]]+(.*)$"));
+                wxString bufstr;
+                while (process->GetLine(bufstr) >= 0) {
+                    bufstr = bufstr.Trim();
+                    if (!bufstr.IsEmpty() && re.Matches(bufstr)) {
+                        wxString dr = re.GetMatch(bufstr, 1);
+                        wxString path = re.GetMatch(bufstr, 2);
+                        if (url.Left(path.Length()) == path) {
+                            url = dr + url.Mid(path.Length());
+                            break;
+                        }
+                    }
+                }
+                process->Detach();
+            }
+        }
+#endif
+        url.Replace(wxFILE_SEP_PATH, wxT("/"));
+        url += "/MolbyDoc/ja/index.html";
+    }
+    wxLaunchDefaultBrowser(wxT("file:///") + url);
+}
+
+int
+MyApp::FilterEvent(wxEvent &event)
+{
+#if 0
+    static FILE *fp_eventlog = NULL;
+    if (fp_eventlog == NULL) {
+        char buf[32];
+        int i = 0;
+        while (1) {
+            snprintf(buf, sizeof buf, "Molby_eventlog_%d.log", i);
+            fp_eventlog = fopen(buf, "r");
+            if (fp_eventlog == NULL) {
+                fp_eventlog = fopen(buf, "wt");
+                break;
+            } else {
+                fclose(fp_eventlog);
+                i++;
+            }
+        }
+    }
+    if (fp_eventlog != NULL) {
+        fprintf(fp_eventlog, "%d %d\n", event.GetEventType(), event.GetId());
+        }
+        fflush(fp_eventlog);
+    }
+#endif
+    return -1;
+}
+
+#pragma mark ====== AboutBox ======
+
+IMPLEMENT_CLASS(AboutDialog, wxDialog)
+BEGIN_EVENT_TABLE(AboutDialog, wxDialog)
+END_EVENT_TABLE()
+
+AboutDialog::AboutDialog():
+    wxDialog(NULL, -1, wxT("About Molby"))
+{
+    //  vsizer1 --> hsizer1 --> Molby icon
+    //          |           |-> vsizer2 --> "Molby"
+    //          |                       |-> version strings
+    //          |
+    //          |-> copyright messages
+
+    const char *s1;
+    char *s2, *s3;
+    s1 = "Molby";
+    Molby_getDescription(&s2, &s3);
+    wxString str1(s1, WX_DEFAULT_CONV);
+    wxString str2(s2, WX_DEFAULT_CONV);
+    wxString str3(s3, WX_DEFAULT_CONV);
+    free(s2);
+    free(s3);
+#if defined(__WXMSW__)
+    wxFont *textFont0 = new wxFont(FromFrameDIP(this, 12), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
+    wxFont *textFont1 = new wxFont(FromFrameDIP(this, 10), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
+    wxFont *textFont2 = new wxFont(FromFrameDIP(this, 9), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
+#else
+    wxFont *textFont0 = new wxFont(FromFrameDIP(this, 14), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD);
+    wxFont *textFont1 = new wxFont(FromFrameDIP(this, 12), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
+    wxFont *textFont2 = new wxFont(FromFrameDIP(this, 11), wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
+#endif
+    wxBoxSizer *vsizer1 = new wxBoxSizer(wxVERTICAL);
+    wxBoxSizer *vsizer2 = new wxBoxSizer(wxVERTICAL);
+    wxBoxSizer *hsizer1 = new wxBoxSizer(wxHORIZONTAL);
+    wxString tifname = wxGetApp().FindResourcePath() + wxFILE_SEP_PATH + wxT("bitmaps/molby_icon64.png");
+    wxBitmap *molbyBitmap = new wxBitmap(tifname, wxBITMAP_TYPE_PNG);
+    wxStaticText *stext1 = new wxStaticText(this, -1, wxT("Molby"));
+    stext1->SetFont(*textFont0);
+    wxStaticText *stext2 = new wxStaticText(this, -1, str2);
+    stext2->SetFont(*textFont1);
+    wxStaticBitmap *staticBitmap = new wxStaticBitmap(this, -1, *molbyBitmap);
+    vsizer2->Add(stext1, 0, wxALL | wxEXPAND, FromFrameDIP(this, 2));
+    vsizer2->Add(stext2, 0, wxALL | wxEXPAND, FromFrameDIP(this, 2));
+    hsizer1->AddSpacer(FromFrameDIP(this, 20));
+    hsizer1->Add(staticBitmap, 0, 0, FromFrameDIP(this, 10));
+    hsizer1->AddSpacer(FromFrameDIP(this, 20));
+    hsizer1->Add(vsizer2, 0, wxALL | wxEXPAND, FromFrameDIP(this, 5));
+    wxStaticText *stext3 = new wxStaticText(this, -1, str3);
+    stext3->SetFont(*textFont2);
+    vsizer1->Add(hsizer1, 0, wxALL | wxEXPAND, FromFrameDIP(this, 5));
+    vsizer1->Add(stext3, 0, wxALL | wxEXPAND, FromFrameDIP(this, 5));
+    vsizer1->Add(this->CreateButtonSizer(wxOK), 0, wxALL | wxEXPAND, FromFrameDIP(this, 10));
+    vsizer1->Layout();
+    this->SetSizerAndFit(vsizer1);
+    this->Centre();
+}
+
 #pragma mark ====== MyFrame (top-level window) ======
 
 /*
@@ -1615,11 +1867,9 @@ MyFrame::MyFrame(wxDocManager *manager, wxFrame *frame, const wxString& title,
 
 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event) )
 {
-       char *s;
-       s = Molby_getDescription();
-       wxString str(s, WX_DEFAULT_CONV);
-    (void)wxMessageBox(str, _T("Molby"));
-       free(s);
+    AboutDialog *d = new AboutDialog();
+    if ( d->ShowModal() == wxID_OK )
+    d->Destroy();
 }
 
 MyFrame *GetMainFrame(void)
@@ -1627,6 +1877,120 @@ MyFrame *GetMainFrame(void)
        return frame;
 }
 
+#if 0
+#pragma mark ====== Better wxProcess ======
+#endif
+
+void
+wxBetterProcess::OnTerminate(int pid, int status)
+{
+    m_terminated = true;
+    m_status = status;
+}
+wxKillError
+wxBetterProcess::KillProcess(wxSignal sig, int flags)
+{
+    wxKillError retval = wxProcess::Kill(this->GetPid(), sig, flags);
+    if (retval == wxKILL_OK)
+        m_killSignal = sig;
+    return retval;
+}
+
+int
+wxBetterProcess::GetLineSub(wxString &outStr, wxInputStream *stream, wxMemoryBuffer &mbuf)
+{
+    int err = wxSTREAM_NO_ERROR;
+    int trial = 0;
+    char *p, *pp;
+    long len;
+    char buf[1024];
+    if (stream == NULL)
+        return -3;  //  No stderr stream
+    while (1) {
+        p = (char *)mbuf.GetData();
+        len = mbuf.GetDataLen();
+        if (len > 0) {
+            pp = (char *)memchr(p, '\n', len);
+            if (pp == NULL)
+                pp = (char *)memchr(p, '\r', len);
+            if (pp == NULL && stream->GetLastError() == wxSTREAM_EOF) {
+                //  If EOF, then return all remaining data (without '\n')
+                pp = p + mbuf.GetDataLen() - 1;  //  Point to the last char
+            }
+            if (pp != NULL) {
+                //  Return one line and string length
+                outStr = wxString(p, wxConvUTF8, pp - p + 1);
+                memmove(p, pp + 1, len - (pp - p + 1));
+                m_stdout.SetDataLen(len - (pp - p + 1));
+                return pp - p + 1;
+            }
+        }
+        if (trial > 0) {
+            //  stream->Read() is called only once
+            outStr = _T("");
+            if (err == wxSTREAM_EOF)
+                return -1;  //  EOF and no data left
+            return 0;  //  Not EOF, but no data is available at present
+        }
+        len = 0;
+        if (stream->CanRead()) {
+            //  We need to read by one character because wxInputStream has
+            //  no way to give the available number of bytes
+            stream->Read(buf, sizeof buf);
+            err = stream->GetLastError();
+            if (err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF)
+                return -2;  //  Some read error
+            len = stream->LastRead();
+        } else err = stream->GetLastError();
+        if (len > 0)
+            mbuf.AppendData(buf, len);
+        trial++;
+    }
+}
+
+int
+wxBetterProcess::GetLine(wxString &outStr)
+{
+    return GetLineSub(outStr, this->GetInputStream(), m_stdout);
+}
+
+int
+wxBetterProcess::GetErrorLine(wxString &outStr)
+{
+    return GetLineSub(outStr, this->GetErrorStream(), m_stderr);
+}
+
+int
+wxBetterProcess::PutLine(wxString str)
+{
+    wxOutputStream *stream = this->GetOutputStream();
+    if (stream == NULL)
+        return -3;  //  No stdin stream
+    const char *p = str.utf8_str();
+    long len = strlen(p);
+    if (len > 0)
+        m_stdin.AppendData(p, len);
+    char *pp = (char *)m_stdin.GetData();
+    len = m_stdin.GetDataLen();
+    if (len == 0)
+        return 0;
+    stream->Write(pp, len);
+    long len2 = stream->LastWrite();
+    if (len2 > 0) {
+        memmove(pp, pp + len2, len - len2);
+        m_stdin.SetDataLen(len - len2);
+    }
+    return len2;
+}
+
+void
+wxBetterProcess::CloseOutput()
+{
+    //  We must flush the data in the internal buffer before closing the output
+    while (PutLine("") > 0) {}
+    wxProcess::CloseOutput();  //  Call the original version
+}
+
 #pragma mark ====== Plain-C interface ======
 
 char *
@@ -1636,18 +2000,22 @@ MyAppCallback_getGUIDescriptionString(void)
        if (desc == NULL) {
                asprintf(&desc,
                        "AmberTools 1.3, http://ambermd.org/\n"
-                       "  Copyright (C) Junmei Wang, Ross C. Walker, \n"
-                       "  Michael F. Crowley, Scott Brozell and David A. Case\n"
+                       "  Copyright (C) Junmei Wang, Ross C. Walker, "
+                         "Michael F. Crowley, Scott Brozell and David A. Case\n"
                        "ORTEP-III, http://web.ornl.gov/sci/ortep/\n"
-                       "  Michael N. Burnett and Carroll K. Johnson, \n"
-                       "  Oak Ridge National Laboratory Report ORNL-6895,\n"
-                       "  1996.\n"
+                       "  Michael N. Burnett and Carroll K. Johnson, "
+                         "Oak Ridge National Laboratory Report ORNL-6895, "
+                         "1996.\n"
                        "wxWidgets %d.%d.%d, http://www.wxwidgets.org/\n"
-                   "  Copyright (C) 1992-2013 Julian Smart, Vadim\n"
-                       "  Zeitlin, Stefan Csomor, Robert Roebling,\n"
-                       "  and other members of the wxWidgets team\n"
-                       "  Portions (C) 1996 Artificial Intelligence \n"
-                       "  Applications Institute\n",
+#if wxCHECK_VERSION(3,2,0)
+            "  Copyright (C) 1992-2022 Julian Smart, Vadim "
+#else
+            "  Copyright (C) 1992-2013 Julian Smart, Vadim "
+#endif
+            "Zeitlin, Stefan Csomor, Robert Roebling,\n"
+            "  and other members of the wxWidgets team\n"
+            "  Portions (C) 1996 Artificial Intelligence "
+                         "Applications Institute\n",
                        wxMAJOR_VERSION, wxMINOR_VERSION, wxRELEASE_NUMBER);
        }
        return desc;
@@ -1656,12 +2024,16 @@ MyAppCallback_getGUIDescriptionString(void)
 void
 MyAppCallback_loadGlobalSettings(void)
 {
+    if (!gUseGUI)
+        return;
        wxGetApp().LoadDefaultSettings();
 }
 
 void
 MyAppCallback_saveGlobalSettings(void)
 {
+    if (!gUseGUI)
+        return;
        wxGetApp().SaveDefaultSettings();
 }
 
@@ -1675,14 +2047,18 @@ MyAppCallback_saveGlobalSettings(void)
 char *
 MyAppCallback_getGlobalSettings(const char *key)
 {
-       wxString wxkey(key, WX_DEFAULT_CONV);
-       wxString wxvalue = wxGetApp().GetDefaultSetting(wxkey);
-       return strdup(wxvalue.mb_str(WX_DEFAULT_CONV));
+    if (!gUseGUI)
+        return NULL;
+    wxString wxkey(key, WX_DEFAULT_CONV);
+    wxString wxvalue = wxGetApp().GetDefaultSetting(wxkey);
+    return strdup(wxvalue.mb_str(WX_DEFAULT_CONV));
 }
 
 void
 MyAppCallback_setGlobalSettings(const char *key, const char *value)
 {
+    if (!gUseGUI)
+        return;
        wxString wxkey(key, WX_DEFAULT_CONV);
        wxString wxvalue(value, WX_DEFAULT_CONV);
        wxGetApp().SetDefaultSetting(wxkey, wxvalue);
@@ -1691,12 +2067,15 @@ MyAppCallback_setGlobalSettings(const char *key, const char *value)
 int
 MyAppCallback_getGlobalSettingsWithType(const char *key, int type, void *ptr)
 {
-       int retval;
+    int retval, temp;
        char *s = MyAppCallback_getGlobalSettings(key);
        char desc[] = SCRIPT_ACTION("s; ");
        desc[sizeof(desc) - 2] = type;
+    temp = gMolActionNoErrorDialog;
+    gMolActionNoErrorDialog = 1;
        retval = MolActionCreateAndPerform(NULL, desc, "eval", s, ptr);
        free(s);
+    gMolActionNoErrorDialog = temp;
        return retval;
 }
 
@@ -1705,11 +2084,11 @@ MyAppCallback_setGlobalSettingsWithType(const char *key, int type, const void *p
 {
        const char *cmd = "set_global_settings";
        switch (type) {
-               case 'i': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("i"), cmd, *((const Int *)ptr));
-               case 'd': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("d"), cmd, *((const Double *)ptr));
-               case 's': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("s"), cmd, (const char *)ptr);
-               case 'v': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("v"), cmd, (const Vector *)ptr);
-               case 't': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("t"), cmd, (const Transform *)ptr);
+               case 'i': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("si"), cmd, key, *((const Int *)ptr));
+               case 'd': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("sd"), cmd, key, *((const Double *)ptr));
+               case 's': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("ss"), cmd, key, (const char *)ptr);
+               case 'v': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("sv"), cmd, key, (const Vector *)ptr);
+               case 't': return MolActionCreateAndPerform(NULL, SCRIPT_ACTION("st"), cmd, key, (const Transform *)ptr);
                default:
                        MyAppCallback_errorMessageBox("Internal error: unsupported format '%c' at line %d, file %s", type, __LINE__, __FILE__);
                        return -2;
@@ -1719,36 +2098,50 @@ MyAppCallback_setGlobalSettingsWithType(const char *key, int type, const void *p
 int
 MyAppCallback_checkInterrupt(void)
 {
-       return wxGetApp().IsInterrupted();
+    if (!gUseGUI)
+        return 0;
+    return wxGetApp().IsInterrupted();
 }
 
 void
 MyAppCallback_showProgressPanel(const char *msg)
 {
-       wxGetApp().ShowProgressPanel(msg);
+    if (!gUseGUI)
+        return;
+    wxGetApp().ShowProgressPanel(msg);
 }
 
 void
 MyAppCallback_hideProgressPanel(void)
 {
-       wxGetApp().HideProgressPanel();
+    if (!gUseGUI)
+        return;
+    wxGetApp().HideProgressPanel();
 }
 
 void
 MyAppCallback_setProgressValue(double dval)
 {
-       wxGetApp().SetProgressValue(dval);
+    if (!gUseGUI)
+        return;
+    wxGetApp().SetProgressValue(dval);
 }
 
 void
 MyAppCallback_setProgressMessage(const char *msg)
 {
-       wxGetApp().SetProgressMessage(msg);
+    if (!gUseGUI)
+        return;
+    wxGetApp().SetProgressMessage(msg);
 }
 
 int
 MyAppCallback_getTextWithPrompt(const char *prompt, char *buf, int bufsize)
 {
+    if (!gUseGUI) {
+        buf[0] = 0;
+        return 0;
+    }
        wxDialog *dialog = new wxDialog(NULL, -1, _T("Input request"), wxDefaultPosition);
        wxStaticText *stext;
        wxTextCtrl *tctrl;
@@ -1758,15 +2151,16 @@ MyAppCallback_getTextWithPrompt(const char *prompt, char *buf, int bufsize)
        {       //  Vertical sizer containing [prompt, textbox, buttons]
                wxBoxSizer *sizer1;
                sizer1 = new wxBoxSizer(wxVERTICAL);
-               stext = new wxStaticText(dialog, -1, pstr, wxDefaultPosition, wxSize(200, 22));
-               sizer1->Add(stext, 0, wxEXPAND | wxALL, 6);
-               tctrl = new wxTextCtrl(dialog, -1, defstr, wxDefaultPosition, wxSize(200, 22));
-               sizer1->Add(tctrl, 0, wxEXPAND | wxALL, 6);
+               stext = new wxStaticText(dialog, -1, pstr, wxDefaultPosition, FromFrameDIP(dialog, wxSize(200, 22)));
+               sizer1->Add(stext, 0, wxEXPAND | wxALL, FromFrameDIP(dialog, 6));
+               tctrl = new wxTextCtrl(dialog, -1, defstr, wxDefaultPosition, FromFrameDIP(dialog, wxSize(200, 22)));
+               sizer1->Add(tctrl, 0, wxEXPAND | wxALL, FromFrameDIP(dialog, 6));
                wxSizer *bsizer = dialog->CreateButtonSizer(wxOK | wxCANCEL);
-               sizer1->Add(bsizer, 0, wxEXPAND | wxALL, 6);
+               sizer1->Add(bsizer, 0, wxEXPAND | wxALL, FromFrameDIP(dialog, 6));
                sizer1->Layout();
                dialog->SetSizerAndFit(sizer1);
                dialog->Centre(wxBOTH);
+        tctrl->SelectAll();
                tctrl->SetFocus();
        }
        if (dialog->ShowModal() == wxID_OK) {
@@ -1785,6 +2179,11 @@ MyAppCallback_getTextWithPrompt(const char *prompt, char *buf, int bufsize)
 int
 MyAppCallback_messageBox(const char *message, const char *title, int flags, int icon)
 {
+    if (!gUseGUI) {
+        printf("%s\n%s\n", title, message);
+        return 1;
+    }
+    
        int wxflags, wxicon, retval;
        if (!wxGetApp().IsMainLoopRunning()) {
                MyAppCallback_setConsoleColor(1);
@@ -1809,6 +2208,12 @@ MyAppCallback_messageBox(const char *message, const char *title, int flags, int
 void
 MyAppCallback_errorMessageBox(const char *fmt, ...)
 {
+    if (!gUseGUI) {
+        va_list ap;
+        va_start(ap, fmt);
+        vfprintf(stderr, fmt, ap);
+        return;
+    }
        char *s;
        int need_free = 0;
        va_list ap;
@@ -1839,16 +2244,24 @@ MyAppCallback_getHomeDir(void)
        return (s == NULL ? NULL : strdup(s));
 }
 
+#if __WXMSW__
+#include <Shlobj.h>
+#endif
+
 char *
 MyAppCallback_getDocumentHomeDir(void)
 {
-       char *s;
 #if __WXMSW__
-       char *ss;
-       s = getenv("USERPROFILE");
-       asprintf(&ss, "%s\\My Documents", s);
-       return ss;
+       char appData[MAX_PATH * 2];
+       HRESULT hResult;
+       hResult = SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, appData);
+       if (hResult == S_OK) {
+               return strdup(appData);
+       } else {
+               return MyAppCallback_getHomeDir();
+       }
 #else
+       char *s;
        s = getenv("HOME");
        return (s == NULL ? NULL : strdup(s));
 #endif
@@ -1857,18 +2270,26 @@ MyAppCallback_getDocumentHomeDir(void)
 int
 MyAppCallback_registerScriptMenu(const char *title)
 {
+    if (!gUseGUI)
+        return -1;
        return wxGetApp().RegisterScriptMenu(title);
 }
 
 int
 MyAppCallback_lookupScriptMenu(const char *title)
 {
+    if (!gUseGUI)
+        return 0;
        return wxGetApp().LookupScriptMenu(title);
 }
 
 RubyValue
 MyAppCallback_executeScriptFromFile(const char *cpath, int *status)
 {
+    if (!gUseGUI) {
+        return 0;
+    }
+    
        RubyValue retval;
        wxString cwd = wxFileName::GetCwd();
        wxString path(cpath, wxConvFile);
@@ -1955,6 +2376,8 @@ MyAppCallback_executeScriptFromFile(const char *cpath, int *status)
 
 void MyAppCallback_beginUndoGrouping(void)
 {
+    if (!gUseGUI)
+        return;
        wxList &doclist = wxGetApp().DocManager()->GetDocuments();
        wxList::iterator iter;
        for (iter = doclist.begin(); iter != doclist.end(); ++iter) {
@@ -1964,6 +2387,8 @@ void MyAppCallback_beginUndoGrouping(void)
 
 void MyAppCallback_endUndoGrouping(void)
 {
+    if (!gUseGUI)
+        return;
        wxList &doclist = wxGetApp().DocManager()->GetDocuments();
        wxList::iterator iter;
        for (iter = doclist.begin(); iter != doclist.end(); ++iter) {
@@ -1971,13 +2396,17 @@ void MyAppCallback_endUndoGrouping(void)
        }
 }
 
-int MyAppCallback_callSubProcess(const char *cmdline, const char *procname, int (*callback)(void *), void *callback_data, FILE *output, FILE *errout)
+int MyAppCallback_callSubProcess(const char *cmdline, const char *procname, int (*callback)(void *), void *callback_data, FILE *output, FILE *errout, int *exitstatus_p, int *pid_p)
 {
-       return wxGetApp().CallSubProcess(cmdline, procname, callback, callback_data, output, errout);
+    if (!gUseGUI)
+        return system(cmdline);
+       return wxGetApp().CallSubProcess(cmdline, procname, callback, callback_data, output, errout, exitstatus_p, pid_p);
 }
 
 void MyAppCallback_showConsoleWindow(void)
 {
+    if (!gUseGUI)
+        return;
        ConsoleFrame *frame = wxGetApp().GetConsoleFrame();
        frame->Show(true);
        frame->Raise();
@@ -1985,17 +2414,23 @@ void MyAppCallback_showConsoleWindow(void)
 
 void MyAppCallback_hideConsoleWindow(void)
 {
+    if (!gUseGUI)
+        return;
        ConsoleFrame *frame = wxGetApp().GetConsoleFrame();
        frame->Hide();
 }
 
 void MyAppCallback_bell(void)
 {
-       wxBell();
+    if (!gUseGUI)
+        return;
+    wxBell();
 }
 
 int MyAppCallback_playSound(const char *filename, int flag)
 {
+    if (!gUseGUI)
+        return 0;
        unsigned uflag = wxSOUND_SYNC;
        if (flag == 1)
                uflag = wxSOUND_ASYNC;
@@ -2008,6 +2443,8 @@ int MyAppCallback_playSound(const char *filename, int flag)
 
 void MyAppCallback_stopSound(void)
 {
+    if (!gUseGUI)
+        return;
        wxSound::Stop();
 }