"Fossies" - the Fresh Open Source Software Archive

Member "filezilla-3.48.1/src/interface/edithandler.cpp" (16 May 2020, 52270 Bytes) of package /linux/misc/FileZilla_3.48.1_src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "edithandler.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.48.0_vs_3.48.1.

    1 #include <filezilla.h>
    2 #include "conditionaldialog.h"
    3 #include "dialogex.h"
    4 #include "edithandler.h"
    5 #include "filezillaapp.h"
    6 #include "file_utils.h"
    7 #include "Options.h"
    8 #include "queue.h"
    9 #include "textctrlex.h"
   10 #include "window_state_manager.h"
   11 
   12 #include <libfilezilla/file.hpp>
   13 #include <libfilezilla/local_filesys.hpp>
   14 #include <libfilezilla/process.hpp>
   15 
   16 #include <wx/filedlg.h>
   17 #include <wx/hyperlink.h>
   18 #include <wx/statline.h>
   19 
   20 //-------------
   21 
   22 DECLARE_EVENT_TYPE(fzEDIT_CHANGEDFILE, -1)
   23 DEFINE_EVENT_TYPE(fzEDIT_CHANGEDFILE)
   24 
   25 BEGIN_EVENT_TABLE(CEditHandler, wxEvtHandler)
   26 EVT_TIMER(wxID_ANY, CEditHandler::OnTimerEvent)
   27 EVT_COMMAND(wxID_ANY, fzEDIT_CHANGEDFILE, CEditHandler::OnChangedFileEvent)
   28 END_EVENT_TABLE()
   29 
   30 Associations LoadAssociations()
   31 {
   32     Associations ret;
   33 
   34     std::wstring const raw_assocs = COptions::Get()->GetOption(OPTION_EDIT_CUSTOMASSOCIATIONS);
   35     auto assocs = fz::strtok_view(raw_assocs, L"\r\n", true);
   36 
   37     for (std::wstring_view assoc : assocs) {
   38         std::optional<std::wstring> ext = UnquoteFirst(assoc);
   39         if (!ext || ext->empty()) {
   40             continue;
   41         }
   42 
   43         auto unquoted = UnquoteCommand(assoc);
   44         if (!unquoted.empty() && !unquoted[0].empty()) {
   45             ret.emplace(std::move(*ext), std::move(unquoted));
   46         }
   47     }
   48 
   49     return ret;
   50 
   51 }
   52 
   53 void SaveAssociations(Associations const& assocs)
   54 {
   55     std::wstring quoted;
   56     for (auto const& assoc : assocs) {
   57         if (!quoted.empty()) {
   58             quoted += '\n';
   59         }
   60 
   61         if (assoc.first.find_first_of(L" \t'\"") != std::wstring::npos) {
   62             quoted += '"';
   63             quoted += fz::replaced_substrings(assoc.first, L"\"", L"\"\"");
   64             quoted += '"';
   65         }
   66         else {
   67             quoted += assoc.first;
   68         }
   69         quoted += ' ';
   70         quoted += QuoteCommand(assoc.second);
   71     }
   72     COptions::Get()->SetOption(OPTION_EDIT_CUSTOMASSOCIATIONS, quoted);
   73 }
   74 
   75 CEditHandler* CEditHandler::m_pEditHandler = 0;
   76 
   77 CEditHandler::CEditHandler()
   78 {
   79     m_pQueue = 0;
   80 
   81     m_timer.SetOwner(this);
   82     m_busyTimer.SetOwner(this);
   83 
   84 #ifdef __WXMSW__
   85     m_lockfile_handle = INVALID_HANDLE_VALUE;
   86 #else
   87     m_lockfile_descriptor = -1;
   88 #endif
   89 }
   90 
   91 CEditHandler* CEditHandler::Create()
   92 {
   93     if (!m_pEditHandler) {
   94         m_pEditHandler = new CEditHandler();
   95     }
   96 
   97     return m_pEditHandler;
   98 }
   99 
  100 CEditHandler* CEditHandler::Get()
  101 {
  102     return m_pEditHandler;
  103 }
  104 
  105 void CEditHandler::RemoveTemporaryFiles(std::wstring const& temp)
  106 {
  107     wxDir dir(temp);
  108     if (!dir.IsOpened()) {
  109         return;
  110     }
  111 
  112     wxString file;
  113     if (!dir.GetFirst(&file, _T("fz3temp-*"), wxDIR_DIRS)) {
  114         return;
  115     }
  116 
  117     wxChar const& sep = wxFileName::GetPathSeparator();
  118     do {
  119         if (!m_localDir.empty() && temp + file + sep == m_localDir) {
  120             // Don't delete own working directory
  121             continue;
  122         }
  123 
  124         RemoveTemporaryFilesInSpecificDir((temp + file + sep).ToStdWstring());
  125     } while (dir.GetNext(&file));
  126 }
  127 
  128 void CEditHandler::RemoveTemporaryFilesInSpecificDir(std::wstring const& temp)
  129 {
  130     std::wstring const lockfile = temp + L"fz3temp-lockfile";
  131     if (wxFileName::FileExists(lockfile)) {
  132 #ifndef __WXMSW__
  133         int fd = open(fz::to_string(lockfile).c_str(), O_RDWR | O_CLOEXEC, 0);
  134         if (fd >= 0) {
  135             // Try to lock 1 byte region in the lockfile. m_type specifies the byte to lock.
  136             struct flock f = {};
  137             f.l_type = F_WRLCK;
  138             f.l_whence = SEEK_SET;
  139             f.l_start = 0;
  140             f.l_len = 1;
  141             f.l_pid = getpid();
  142             if (fcntl(fd, F_SETLK, &f)) {
  143                 // In use by other process
  144                 close(fd);
  145                 return;
  146             }
  147             close(fd);
  148         }
  149 #endif
  150         fz::remove_file(fz::to_native(lockfile));
  151 
  152         if (wxFileName::FileExists(lockfile)) {
  153             return;
  154         }
  155     }
  156 
  157     wxLogNull log;
  158 
  159     {
  160         wxString file;
  161         wxDir dir(temp);
  162         bool res;
  163         for ((res = dir.GetFirst(&file, _T(""), wxDIR_FILES)); res; res = dir.GetNext(&file)) {
  164             wxRemoveFile(temp + file);
  165         }
  166     }
  167 
  168     wxRmdir(temp);
  169 
  170 }
  171 
  172 std::wstring CEditHandler::GetLocalDirectory()
  173 {
  174     if (!m_localDir.empty()) {
  175         return m_localDir;
  176     }
  177 
  178     wxFileName tmpdir(wxFileName::GetTempDir(), _T(""));
  179     // Need to call GetLongPath on MSW, GetTempDir can return short path
  180     // which will cause problems when calculating maximum allowed file
  181     // length
  182     wxString dir = tmpdir.GetLongPath();
  183     if (dir.empty() || !wxFileName::DirExists(dir)) {
  184         return std::wstring();
  185     }
  186 
  187     if (dir.Last() != wxFileName::GetPathSeparator()) {
  188         dir += wxFileName::GetPathSeparator();
  189     }
  190 
  191     // On POSIX, the permissions of the created directory (700) ensure
  192     // that this is a safe operation.
  193     // On Windows, the user's profile directory and associated temp dir
  194     // already has the correct permissions which get inherited.
  195     int i = 1;
  196     do {
  197         wxString newDir = dir + wxString::Format(_T("fz3temp-%d"), ++i);
  198         if (wxFileName::FileExists(newDir) || wxFileName::DirExists(newDir)) {
  199             continue;
  200         }
  201 
  202         if (!wxMkdir(newDir, 0700)) {
  203             return std::wstring();
  204         }
  205 
  206         m_localDir = (newDir + wxFileName::GetPathSeparator()).ToStdWstring();
  207         break;
  208     } while (true);
  209 
  210     // Defer deleting stale directories until after having created our own
  211     // working directory.
  212     // This avoids some strange errors where freshly deleted directories
  213     // cannot be instantly recreated.
  214     RemoveTemporaryFiles(dir.ToStdWstring());
  215 
  216 #ifdef __WXMSW__
  217     m_lockfile_handle = ::CreateFile((m_localDir + L"fz3temp-lockfile").c_str(), GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, 0);
  218     if (m_lockfile_handle == INVALID_HANDLE_VALUE) {
  219         wxRmdir(m_localDir);
  220         m_localDir.clear();
  221     }
  222 #else
  223     auto file = fz::to_native(m_localDir) + "fz3temp-lockfile";
  224     m_lockfile_descriptor = open(file.c_str(), O_CREAT | O_RDWR | O_CLOEXEC, 0600);
  225     if (m_lockfile_descriptor >= 0) {
  226         // Lock 1 byte region in the lockfile.
  227         struct flock f = {};
  228         f.l_type = F_WRLCK;
  229         f.l_whence = SEEK_SET;
  230         f.l_start = 0;
  231         f.l_len = 1;
  232         f.l_pid = getpid();
  233         fcntl(m_lockfile_descriptor, F_SETLKW, &f);
  234     }
  235 #endif
  236 
  237     return m_localDir;
  238 }
  239 
  240 void CEditHandler::Release()
  241 {
  242     if (m_timer.IsRunning()) {
  243         m_timer.Stop();
  244     }
  245     if (m_busyTimer.IsRunning()) {
  246         m_busyTimer.Stop();
  247     }
  248 
  249     if (!m_localDir.empty()) {
  250 #ifdef __WXMSW__
  251         if (m_lockfile_handle != INVALID_HANDLE_VALUE) {
  252             CloseHandle(m_lockfile_handle);
  253         }
  254         wxRemoveFile(m_localDir + _T("fz3temp-lockfile"));
  255 #else
  256         wxRemoveFile(m_localDir + _T("fz3temp-lockfile"));
  257         if (m_lockfile_descriptor >= 0) {
  258             close(m_lockfile_descriptor);
  259         }
  260 #endif
  261 
  262         wxLogNull log;
  263         wxRemoveFile(m_localDir + _T("empty_file_yq744zm"));
  264         RemoveAll(true);
  265         RemoveTemporaryFilesInSpecificDir(m_localDir);
  266     }
  267 
  268     m_pEditHandler = 0;
  269     delete this;
  270 }
  271 
  272 CEditHandler::fileState CEditHandler::GetFileState(std::wstring const& fileName) const
  273 {
  274     std::list<t_fileData>::const_iterator iter = GetFile(fileName);
  275     if (iter == m_fileDataList[local].end()) {
  276         return unknown;
  277     }
  278 
  279     return iter->state;
  280 }
  281 
  282 CEditHandler::fileState CEditHandler::GetFileState(std::wstring const& fileName, CServerPath const& remotePath, Site const& site) const
  283 {
  284     std::list<t_fileData>::const_iterator iter = GetFile(fileName, remotePath, site);
  285     if (iter == m_fileDataList[remote].end()) {
  286         return unknown;
  287     }
  288 
  289     return iter->state;
  290 }
  291 
  292 int CEditHandler::GetFileCount(CEditHandler::fileType type, CEditHandler::fileState state, Site const& site) const
  293 {
  294     int count = 0;
  295     if (state == unknown) {
  296         wxASSERT(!site);
  297         if (type != remote) {
  298             count += m_fileDataList[local].size();
  299         }
  300         if (type != local) {
  301             count += m_fileDataList[remote].size();
  302         }
  303     }
  304     else {
  305         auto f = [state, &site](decltype(m_fileDataList[0]) & items) {
  306             int cnt = 0;
  307             for (auto const& data : items) {
  308                 if (data.state != state) {
  309                     continue;
  310                 }
  311 
  312                 if (!site || data.site == site) {
  313                     ++cnt;
  314                 }
  315             }
  316             return cnt;
  317         };
  318         if (type != remote) {
  319             count += f(m_fileDataList[local]);
  320         }
  321         if (type != local) {
  322             count += f(m_fileDataList[remote]);
  323         }
  324     }
  325 
  326     return count;
  327 }
  328 
  329 bool CEditHandler::AddFile(CEditHandler::fileType type, std::wstring const& localFile, std::wstring const& remoteFile, CServerPath const& remotePath, Site const& site, int64_t size)
  330 {
  331     wxASSERT(type != none);
  332 
  333     fileState state;
  334     if (type == local) {
  335         state = GetFileState(localFile);
  336     }
  337     else {
  338         state = GetFileState(remoteFile, remotePath, site);
  339     }
  340 
  341     // It should still be unknown, but due to having displayed dialogs with event loops, something else might have happened so check again just in case.
  342     if (state != unknown) {
  343         wxBell();
  344         return false;
  345     }
  346 
  347     t_fileData data;
  348     if (type == remote) {
  349         data.state = download;
  350     }
  351     else {
  352         data.state = edit;
  353     }
  354     data.localFile = localFile;
  355     data.remoteFile = remoteFile;
  356     data.remotePath = remotePath;
  357     data.site = site;
  358     
  359     
  360     if (type == local) {
  361         bool const launched = LaunchEditor(local, data);
  362 
  363         if (launched && COptions::Get()->GetOptionVal(OPTION_EDIT_TRACK_LOCAL)) {
  364             m_fileDataList[type].emplace_back(std::move(data));
  365         }
  366         if (!launched) {
  367             wxMessageBoxEx(wxString::Format(_("The file '%s' could not be opened:\nThe associated command failed"), localFile), _("Opening failed"), wxICON_EXCLAMATION);
  368         }
  369         return launched;
  370     }
  371     else {
  372         m_fileDataList[type].emplace_back(std::move(data));
  373 
  374         std::wstring localFileName;
  375         CLocalPath localPath(localFile, &localFileName);
  376         if (localFileName == remoteFile) {
  377             localFileName.clear();
  378         }
  379         m_pQueue->QueueFile(false, true, remoteFile, localFileName, localPath, remotePath, site, size, type, QueuePriority::high);
  380         m_pQueue->QueueFile_Finish(true);
  381     }
  382 
  383     return true;
  384 }
  385 
  386 bool CEditHandler::Remove(std::wstring const& fileName)
  387 {
  388     std::list<t_fileData>::iterator iter = GetFile(fileName);
  389     if (iter == m_fileDataList[local].end()) {
  390         return true;
  391     }
  392 
  393     wxASSERT(iter->state != upload && iter->state != upload_and_remove);
  394     if (iter->state == upload || iter->state == upload_and_remove) {
  395         return false;
  396     }
  397 
  398     m_fileDataList[local].erase(iter);
  399 
  400     return true;
  401 }
  402 
  403 bool CEditHandler::Remove(std::wstring const& fileName, CServerPath const& remotePath, Site const& site)
  404 {
  405     std::list<t_fileData>::iterator iter = GetFile(fileName, remotePath, site);
  406     if (iter == m_fileDataList[remote].end()) {
  407         return true;
  408     }
  409 
  410     wxASSERT(iter->state != download && iter->state != upload && iter->state != upload_and_remove);
  411     if (iter->state == download || iter->state == upload || iter->state == upload_and_remove) {
  412         return false;
  413     }
  414 
  415     if (wxFileName::FileExists(iter->localFile)) {
  416         if (!wxRemoveFile(iter->localFile)) {
  417             iter->state = removing;
  418             return false;
  419         }
  420     }
  421 
  422     m_fileDataList[remote].erase(iter);
  423 
  424     return true;
  425 }
  426 
  427 bool CEditHandler::RemoveAll(bool force)
  428 {
  429     std::list<t_fileData> keep;
  430 
  431     for (std::list<t_fileData>::iterator iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
  432         if (!force && (iter->state == download || iter->state == upload || iter->state == upload_and_remove)) {
  433             keep.push_back(*iter);
  434             continue;
  435         }
  436 
  437         if (wxFileName::FileExists(iter->localFile)) {
  438             if (!wxRemoveFile(iter->localFile)) {
  439                 iter->state = removing;
  440                 keep.push_back(*iter);
  441                 continue;
  442             }
  443         }
  444     }
  445     m_fileDataList[remote].swap(keep);
  446     keep.clear();
  447 
  448     for (auto iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
  449         if (force) {
  450             continue;
  451         }
  452 
  453         if (iter->state == upload || iter->state == upload_and_remove) {
  454             keep.push_back(*iter);
  455             continue;
  456         }
  457     }
  458     m_fileDataList[local].swap(keep);
  459 
  460     return m_fileDataList[local].empty() && m_fileDataList[remote].empty();
  461 }
  462 
  463 bool CEditHandler::RemoveAll(fileState state, Site const& site)
  464 {
  465     // Others not implemented
  466     wxASSERT(state == upload_and_remove_failed);
  467     if (state != upload_and_remove_failed) {
  468         return false;
  469     }
  470 
  471     std::list<t_fileData> keep;
  472 
  473     for (auto iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
  474         if (iter->state != state) {
  475             keep.push_back(*iter);
  476             continue;
  477         }
  478 
  479         if (site && iter->site != site) {
  480             keep.push_back(*iter);
  481             continue;
  482         }
  483 
  484         if (wxFileName::FileExists(iter->localFile)) {
  485             if (!wxRemoveFile(iter->localFile)) {
  486                 iter->state = removing;
  487                 keep.push_back(*iter);
  488                 continue;
  489             }
  490         }
  491     }
  492     m_fileDataList[remote].swap(keep);
  493 
  494     return true;
  495 }
  496 
  497 std::list<CEditHandler::t_fileData>::iterator CEditHandler::GetFile(std::wstring const& fileName)
  498 {
  499     std::list<t_fileData>::iterator iter;
  500     for (iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
  501         if (iter->localFile == fileName) {
  502             break;
  503         }
  504     }
  505 
  506     return iter;
  507 }
  508 
  509 std::list<CEditHandler::t_fileData>::const_iterator CEditHandler::GetFile(std::wstring const& fileName) const
  510 {
  511     std::list<t_fileData>::const_iterator iter;
  512     for (iter = m_fileDataList[local].begin(); iter != m_fileDataList[local].end(); ++iter) {
  513         if (iter->localFile == fileName) {
  514             break;
  515         }
  516     }
  517 
  518     return iter;
  519 }
  520 
  521 std::list<CEditHandler::t_fileData>::iterator CEditHandler::GetFile(std::wstring const& fileName, CServerPath const& remotePath, Site const& site)
  522 {
  523     std::list<t_fileData>::iterator iter;
  524     for (iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
  525         if (iter->remoteFile != fileName) {
  526             continue;
  527         }
  528 
  529         if (iter->site != site) {
  530             continue;
  531         }
  532 
  533         if (iter->remotePath != remotePath) {
  534             continue;
  535         }
  536 
  537         return iter;
  538     }
  539 
  540     return iter;
  541 }
  542 
  543 std::list<CEditHandler::t_fileData>::const_iterator CEditHandler::GetFile(std::wstring const& fileName, CServerPath const& remotePath, Site const& site) const
  544 {
  545     std::list<t_fileData>::const_iterator iter;
  546     for (iter = m_fileDataList[remote].begin(); iter != m_fileDataList[remote].end(); ++iter) {
  547         if (iter->remoteFile != fileName) {
  548             continue;
  549         }
  550 
  551         if (iter->site != site) {
  552             continue;
  553         }
  554 
  555         if (iter->remotePath != remotePath) {
  556             continue;
  557         }
  558 
  559         return iter;
  560     }
  561 
  562     return iter;
  563 }
  564 
  565 void CEditHandler::FinishTransfer(bool, std::wstring const& fileName)
  566 {
  567     auto iter = GetFile(fileName);
  568     if (iter == m_fileDataList[local].end()) {
  569         return;
  570     }
  571 
  572     wxASSERT(iter->state == upload || iter->state == upload_and_remove);
  573 
  574     switch (iter->state)
  575     {
  576     case upload_and_remove:
  577         m_fileDataList[local].erase(iter);
  578         break;
  579     case upload:
  580         if (wxFileName::FileExists(fileName)) {
  581             iter->state = edit;
  582         }
  583         else {
  584             m_fileDataList[local].erase(iter);
  585         }
  586         break;
  587     default:
  588         return;
  589     }
  590 
  591     SetTimerState();
  592 }
  593 
  594 void CEditHandler::FinishTransfer(bool successful, std::wstring const& fileName, CServerPath const& remotePath, Site const& site)
  595 {
  596     auto iter = GetFile(fileName, remotePath, site);
  597     if (iter == m_fileDataList[remote].end()) {
  598         return;
  599     }
  600 
  601     wxASSERT(iter->state == download || iter->state == upload || iter->state == upload_and_remove);
  602 
  603     switch (iter->state)
  604     {
  605     case upload_and_remove:
  606         if (successful) {
  607             if (wxFileName::FileExists(iter->localFile) && !wxRemoveFile(iter->localFile)) {
  608                 iter->state = removing;
  609             }
  610             else {
  611                 m_fileDataList[remote].erase(iter);
  612             }
  613         }
  614         else {
  615             if (!wxFileName::FileExists(iter->localFile)) {
  616                 m_fileDataList[remote].erase(iter);
  617             }
  618             else {
  619                 iter->state = upload_and_remove_failed;
  620             }
  621         }
  622         break;
  623     case upload:
  624         if (wxFileName::FileExists(iter->localFile)) {
  625             iter->state = edit;
  626         }
  627         else {
  628             m_fileDataList[remote].erase(iter);
  629         }
  630         break;
  631     case download:
  632         if (wxFileName::FileExists(iter->localFile)) {
  633             iter->state = edit;
  634             if (LaunchEditor(remote, *iter)) {
  635                 break;
  636             }
  637         }
  638         if (wxFileName::FileExists(iter->localFile) && !wxRemoveFile(iter->localFile)) {
  639             iter->state = removing;
  640         }
  641         else {
  642             m_fileDataList[remote].erase(iter);
  643         }
  644         break;
  645     default:
  646         return;
  647     }
  648 
  649     SetTimerState();
  650 }
  651 
  652 bool CEditHandler::LaunchEditor(std::wstring const& file)
  653 {
  654     auto iter = GetFile(file);
  655     if (iter == m_fileDataList[local].end()) {
  656         return false;
  657     }
  658 
  659     return LaunchEditor(local, *iter);
  660 }
  661 
  662 bool CEditHandler::LaunchEditor(std::wstring const& file, CServerPath const& remotePath, Site const& site)
  663 {
  664     auto iter = GetFile(file, remotePath, site);
  665     if (iter == m_fileDataList[remote].end()) {
  666         return false;
  667     }
  668 
  669     return LaunchEditor(remote, *iter);
  670 }
  671 
  672 bool CEditHandler::LaunchEditor(CEditHandler::fileType type, t_fileData& data)
  673 {
  674     wxASSERT(type != none);
  675     wxASSERT(data.state == edit);
  676 
  677     bool is_link;
  678     if (fz::local_filesys::get_file_info(fz::to_native(data.localFile), is_link, 0, &data.modificationTime, 0) != fz::local_filesys::file) {
  679         return false;
  680     }
  681 
  682     auto cmd_with_args = GetAssociation((type == local) ? data.localFile : data.remoteFile);
  683     if (cmd_with_args.empty() || !ProgramExists(cmd_with_args.front())) {
  684         return false;
  685     }
  686     
  687     return fz::spawn_detached_process(AssociationToCommand(cmd_with_args, data.localFile));
  688 }
  689 
  690 void CEditHandler::CheckForModifications(bool emitEvent)
  691 {
  692     static bool insideCheckForModifications = false;
  693     if (insideCheckForModifications) {
  694         return;
  695     }
  696 
  697     if (emitEvent) {
  698         QueueEvent(new wxCommandEvent(fzEDIT_CHANGEDFILE));
  699         return;
  700     }
  701 
  702     insideCheckForModifications = true;
  703 
  704     for (int i = 0; i < 2; ++i) {
  705 checkmodifications_loopbegin:
  706         for (auto iter = m_fileDataList[i].begin(); iter != m_fileDataList[i].end(); ++iter) {
  707             if (iter->state != edit) {
  708                 continue;
  709             }
  710 
  711             fz::datetime mtime;
  712             bool is_link;
  713             if (fz::local_filesys::get_file_info(fz::to_native(iter->localFile), is_link, 0, &mtime, 0) != fz::local_filesys::file) {
  714                 m_fileDataList[i].erase(iter);
  715 
  716                 // Evil goto. Imo the next C++ standard needs a comefrom keyword.
  717                 goto checkmodifications_loopbegin;
  718             }
  719 
  720             if (mtime.empty()) {
  721                 continue;
  722             }
  723 
  724             if (!iter->modificationTime.empty() && !iter->modificationTime.compare(mtime)) {
  725                 continue;
  726             }
  727 
  728             // File has changed, ask user what to do
  729 
  730             m_busyTimer.Stop();
  731             if (!wxDialogEx::CanShowPopupDialog()) {
  732                 m_busyTimer.Start(1000, true);
  733                 insideCheckForModifications = false;
  734                 return;
  735             }
  736             wxTopLevelWindow* pTopWindow = (wxTopLevelWindow*)wxTheApp->GetTopWindow();
  737             if (pTopWindow && pTopWindow->IsIconized()) {
  738                 pTopWindow->RequestUserAttention(wxUSER_ATTENTION_INFO);
  739                 insideCheckForModifications = false;
  740                 return;
  741             }
  742 
  743             bool remove;
  744             int res = DisplayChangeNotification(CEditHandler::fileType(i), *iter, remove);
  745             if (res == -1) {
  746                 continue;
  747             }
  748 
  749             if (res == wxID_YES) {
  750                 UploadFile(CEditHandler::fileType(i), iter, remove);
  751                 goto checkmodifications_loopbegin;
  752             }
  753             else if (remove) {
  754                 if (i == static_cast<int>(remote)) {
  755                     if (fz::local_filesys::get_file_info(fz::to_native(iter->localFile), is_link, 0, &mtime, 0) != fz::local_filesys::file || wxRemoveFile(iter->localFile)) {
  756                         m_fileDataList[i].erase(iter);
  757                         goto checkmodifications_loopbegin;
  758                     }
  759                     iter->state = removing;
  760                 }
  761                 else {
  762                     m_fileDataList[i].erase(iter);
  763                     goto checkmodifications_loopbegin;
  764                 }
  765             }
  766             else if (fz::local_filesys::get_file_info(fz::to_native(iter->localFile), is_link, 0, &mtime, 0) != fz::local_filesys::file) {
  767                 m_fileDataList[i].erase(iter);
  768                 goto checkmodifications_loopbegin;
  769             }
  770             else {
  771                 iter->modificationTime = mtime;
  772             }
  773         }
  774     }
  775 
  776     SetTimerState();
  777 
  778     insideCheckForModifications = false;
  779 }
  780 
  781 int CEditHandler::DisplayChangeNotification(CEditHandler::fileType type, CEditHandler::t_fileData const& data, bool& remove)
  782 {
  783     wxDialogEx dlg;
  784 
  785     if (!dlg.Create(wxTheApp->GetTopWindow(), -1, _("File has changed"))) {
  786         return -1;
  787     }
  788 
  789     auto& lay = dlg.layout();
  790 
  791     auto main = lay.createMain(&dlg, 1);
  792     main->AddGrowableCol(0);
  793 
  794     main->Add(new wxStaticText(&dlg, -1, _("A file previously opened has been changed.")));
  795 
  796     auto inner = lay.createFlex(2);
  797     main->Add(inner);
  798 
  799     inner->Add(new wxStaticText(&dlg, -1, _("Filename:")));
  800     inner->Add(new wxStaticText(&dlg, -1, LabelEscape((type == local) ? data.localFile : data.remoteFile)));
  801 
  802     if (type == remote) {
  803         std::wstring file = data.localFile;
  804         size_t pos = file.rfind(wxFileName::GetPathSeparator());
  805         if (pos != std::wstring::npos) {
  806             file = file.substr(pos + 1);
  807         }
  808         if (file != data.remoteFile) {
  809             inner->Add(new wxStaticText(&dlg, -1, _("Opened as:")));
  810             inner->Add(new wxStaticText(&dlg, -1, LabelEscape(file)));
  811         }
  812     }
  813 
  814     inner->Add(new wxStaticText(&dlg, -1, _("Server:")));
  815     inner->Add(new wxStaticText(&dlg, -1, LabelEscape(data.site.Format(ServerFormat::with_user_and_optional_port))));
  816     
  817     inner->Add(new wxStaticText(&dlg, -1, _("Remote path:")));
  818     inner->Add(new wxStaticText(&dlg, -1, LabelEscape(data.remotePath.GetPath())));
  819 
  820     main->Add(new wxStaticLine(&dlg), lay.grow);
  821 
  822     wxCheckBox* cb{};
  823     if (type == local) {
  824         main->Add(new wxStaticText(&dlg, -1, _("Upload this file to the server?")));
  825         cb = new wxCheckBox(&dlg, -1, _("&Finish editing"));
  826     }
  827     else {
  828         main->Add(new wxStaticText(&dlg, -1, _("Upload this file back to the server?")));
  829         cb = new wxCheckBox(&dlg, -1, _("&Finish editing and delete local file"));
  830 
  831     }
  832     main->Add(cb);
  833 
  834     auto buttons = lay.createButtonSizer(&dlg, main, false);
  835     auto yes = new wxButton(&dlg, wxID_YES, _("&Yes"));
  836     yes->SetDefault();
  837     buttons->AddButton(yes);
  838     auto no = new wxButton(&dlg, wxID_NO, _("&No"));
  839     buttons->AddButton(no);
  840     buttons->Realize();
  841 
  842     yes->Bind(wxEVT_BUTTON, [&dlg](wxEvent const&) { dlg.EndDialog(wxID_YES); });
  843     no->Bind(wxEVT_BUTTON, [&dlg](wxEvent const&) { dlg.EndDialog(wxID_NO); });
  844 
  845     dlg.Layout();
  846     dlg.GetSizer()->Fit(&dlg);
  847 
  848     int res = dlg.ShowModal();
  849 
  850     remove = cb->IsChecked();
  851 
  852     return res;
  853 }
  854 
  855 bool CEditHandler::UploadFile(std::wstring const& file, CServerPath const& remotePath, Site const& site, bool unedit)
  856 {
  857     std::list<t_fileData>::iterator iter = GetFile(file, remotePath, site);
  858     return UploadFile(remote, iter, unedit);
  859 }
  860 
  861 bool CEditHandler::UploadFile(std::wstring const& file, bool unedit)
  862 {
  863     std::list<t_fileData>::iterator iter = GetFile(file);
  864     return UploadFile(local, iter, unedit);
  865 }
  866 
  867 bool CEditHandler::UploadFile(fileType type, std::list<t_fileData>::iterator iter, bool unedit)
  868 {
  869     wxCHECK(type != none, false);
  870 
  871     if (iter == m_fileDataList[type].end()) {
  872         return false;
  873     }
  874 
  875     wxASSERT(iter->state == edit || iter->state == upload_and_remove_failed);
  876     if (iter->state != edit && iter->state != upload_and_remove_failed) {
  877         return false;
  878     }
  879 
  880     iter->state = unedit ? upload_and_remove : upload;
  881 
  882     int64_t size;
  883     fz::datetime mtime;
  884 
  885     bool is_link;
  886     if (fz::local_filesys::get_file_info(fz::to_native(iter->localFile), is_link, &size, &mtime, 0) != fz::local_filesys::file) {
  887         m_fileDataList[type].erase(iter);
  888         return false;
  889     }
  890 
  891     if (mtime.empty()) {
  892         mtime = fz::datetime::now();
  893     }
  894 
  895     iter->modificationTime = mtime;
  896 
  897     wxASSERT(m_pQueue);
  898 
  899     std::wstring file;
  900     CLocalPath localPath(iter->localFile, &file);
  901     if (file.empty()) {
  902         m_fileDataList[type].erase(iter);
  903         return false;
  904     }
  905 
  906     m_pQueue->QueueFile(false, false, file, (file == iter->remoteFile) ? std::wstring() : iter->remoteFile, localPath, iter->remotePath, iter->site, size, type, QueuePriority::high);
  907     m_pQueue->QueueFile_Finish(true);
  908 
  909     return true;
  910 }
  911 
  912 void CEditHandler::OnTimerEvent(wxTimerEvent&)
  913 {
  914 #ifdef __WXMSW__
  915     // Don't check for changes if mouse is captured,
  916     // e.g. if user is dragging a file
  917     if (GetCapture()) {
  918         return;
  919     }
  920 #endif
  921 
  922     CheckForModifications(true);
  923 }
  924 
  925 void CEditHandler::SetTimerState()
  926 {
  927     bool editing = GetFileCount(none, edit) != 0;
  928 
  929     if (m_timer.IsRunning()) {
  930         if (!editing) {
  931             m_timer.Stop();
  932         }
  933     }
  934     else if (editing) {
  935         m_timer.Start(15000);
  936     }
  937 }
  938 
  939 std::vector<std::wstring> CEditHandler::CanOpen(std::wstring const& fileName, bool &program_exists)
  940 {
  941     auto cmd_with_args = GetAssociation(fileName);
  942     if (cmd_with_args.empty()) {
  943         return cmd_with_args;
  944     }
  945 
  946     program_exists = ProgramExists(cmd_with_args.front());
  947 
  948     return cmd_with_args;
  949 }
  950 
  951 std::vector<std::wstring> CEditHandler::GetAssociation(std::wstring const& file)
  952 {
  953     std::vector<std::wstring> ret;
  954 
  955     if (!COptions::Get()->GetOptionVal(OPTION_EDIT_ALWAYSDEFAULT)) {
  956         ret = GetCustomAssociation(file);
  957     }
  958 
  959     if (ret.empty()) {
  960         std::wstring command = COptions::Get()->GetOption(OPTION_EDIT_DEFAULTEDITOR);
  961         if (!command.empty()) {
  962             if (command[0] == '1') {
  963                 // Text editor
  964                 ret = GetSystemAssociation(L"foo.txt");
  965             }
  966             else if (command[0] == '2') {
  967                 ret = UnquoteCommand(std::wstring_view(command).substr(1));
  968             }
  969         }
  970     }
  971 
  972     return ret;
  973 }
  974 
  975 std::vector<std::wstring> CEditHandler::GetCustomAssociation(std::wstring_view const& file)
  976 {
  977     std::vector<std::wstring> ret;
  978 
  979     std::wstring ext = GetExtension(file);
  980     if (ext.empty()) {
  981         ext = L"/";
  982     }
  983 
  984     auto assocs = LoadAssociations();
  985     auto it = assocs.find(ext);
  986     if (it != assocs.end()) {
  987         ret = it->second;
  988     }
  989     return ret;
  990 }
  991 
  992 void CEditHandler::OnChangedFileEvent(wxCommandEvent&)
  993 {
  994     CheckForModifications();
  995 }
  996 
  997 std::wstring CEditHandler::GetTemporaryFile(std::wstring name)
  998 {
  999     name = CQueueView::ReplaceInvalidCharacters(name, true);
 1000 #ifdef __WXMSW__
 1001     // MAX_PATH - 1 is theoretical limit, we subtract another 4 to allow
 1002     // editors which create temporary files
 1003     size_t max = MAX_PATH - 5;
 1004 #else
 1005     size_t max = std::wstring::npos;
 1006 #endif
 1007     if (max != std::wstring::npos) {
 1008         name = TruncateFilename(m_localDir, name, max);
 1009         if (name.empty()) {
 1010             return std::wstring();
 1011         }
 1012     }
 1013 
 1014     std::wstring file = m_localDir + name;
 1015     if (!FilenameExists(file)) {
 1016         return file;
 1017     }
 1018 
 1019     if (max != std::wstring::npos) {
 1020         --max;
 1021     }
 1022     int cutoff = 1;
 1023     int n = 1;
 1024     while (++n < 10000) { // Just to give up eventually
 1025         // Further reduce length if needed
 1026         if (max != std::wstring::npos && n >= cutoff) {
 1027             cutoff *= 10;
 1028             --max;
 1029             name = TruncateFilename(m_localDir, name, max);
 1030             if (name.empty()) {
 1031                 return std::wstring();
 1032             }
 1033         }
 1034 
 1035         size_t pos = name.rfind('.');
 1036         if (pos == std::wstring::npos || !pos) {
 1037             file = m_localDir + name + fz::sprintf(L" %d", n);
 1038         }
 1039         else {
 1040             file = m_localDir + name.substr(0, pos) + fz::sprintf(L" %d", n) + name.substr(pos);
 1041         }
 1042 
 1043         if (!FilenameExists(file)) {
 1044             return file;
 1045         }
 1046     }
 1047 
 1048     return std::wstring();
 1049 }
 1050 
 1051 std::wstring CEditHandler::TruncateFilename(std::wstring const& path, std::wstring const& name, size_t max)
 1052 {
 1053     size_t const pathlen = path.size();
 1054     size_t const namelen = name.size();
 1055 
 1056     if (namelen + pathlen > max) {
 1057         size_t pos = name.rfind('.');
 1058         if (pos != std::wstring::npos) {
 1059             size_t extlen = namelen - pos;
 1060             if (pathlen + extlen >= max)
 1061             {
 1062                 // Cannot truncate extension
 1063                 return std::wstring();
 1064             }
 1065 
 1066             return name.substr(0, max - pathlen - extlen) + name.substr(pos);
 1067         }
 1068     }
 1069 
 1070     return name;
 1071 }
 1072 
 1073 bool CEditHandler::FilenameExists(std::wstring const& file)
 1074 {
 1075     for (auto const& fileData : m_fileDataList[remote]) {
 1076         // Always ignore case, we don't know which type of filesystem the user profile
 1077         // is installed upon.
 1078         if (!wxString(fileData.localFile).CmpNoCase(file)) {
 1079             return true;
 1080         }
 1081     }
 1082 
 1083     if (wxFileName::FileExists(file)) {
 1084         // Save to remove, it's not marked as edited anymore.
 1085         {
 1086             wxLogNull log;
 1087             wxRemoveFile(file);
 1088         }
 1089 
 1090         if (wxFileName::FileExists(file)) {
 1091             return true;
 1092         }
 1093     }
 1094 
 1095     return false;
 1096 }
 1097 
 1098 
 1099 
 1100 bool CEditHandler::Edit(CEditHandler::fileType type, std::wstring const& fileName, CServerPath const& path, Site const& site, int64_t size, wxWindow* parent)
 1101 {
 1102     std::vector<FileData> data;
 1103     FileData d{fileName, size};
 1104     data.push_back(d);
 1105 
 1106     return Edit(type, data, path, site, parent);
 1107 }
 1108 
 1109 bool CEditHandler::Edit(CEditHandler::fileType type, std::vector<FileData> const& data, CServerPath const& path, Site const& site, wxWindow* parent)
 1110 {
 1111     if (type == CEditHandler::remote) {
 1112         std::wstring const& localDir = GetLocalDirectory();
 1113         if (localDir.empty()) {
 1114             wxMessageBoxEx(_("Could not get temporary directory to download file into."), _("Cannot edit file"), wxICON_STOP);
 1115             return false;
 1116         }
 1117     }
 1118 
 1119     if (data.empty()) {
 1120         wxBell();
 1121         return false;
 1122     }
 1123 
 1124     if (data.size() > 10) {
 1125         CConditionalDialog dlg(parent, CConditionalDialog::many_selected_for_edit, CConditionalDialog::yesno);
 1126         dlg.SetTitle(_("Confirmation needed"));
 1127         dlg.AddText(_("You have selected more than 10 files for editing, do you really want to continue?"));
 1128 
 1129         if (!dlg.Run()) {
 1130             return false;
 1131         }
 1132     }
 1133 
 1134     bool success = true;
 1135     int already_editing_action{};
 1136     for (auto const& file : data) {
 1137         if (!DoEdit(type, file, path, site, parent, data.size(), already_editing_action)) {
 1138             success = false;
 1139         }
 1140     }
 1141 
 1142     return success;
 1143 }
 1144 
 1145 bool CEditHandler::DoEdit(CEditHandler::fileType type, FileData const& file, CServerPath const& path, Site const& site, wxWindow* parent, size_t fileCount, int& already_editing_action)
 1146 {
 1147     for (auto const& c : GetExtension(file.name)) {
 1148         if (c < 32 && c != '\t') {
 1149             wxMessageBoxEx(_("Forbidden character in file extension."), _("Cannot view/edit selected file"), wxICON_EXCLAMATION);
 1150             return false;
 1151         }
 1152     }
 1153 
 1154     // First check whether this file is already being edited
 1155     fileState state;
 1156     if (type == local) {
 1157         state = GetFileState(file.name);
 1158     }
 1159     else {
 1160         state = GetFileState(file.name, path, site);
 1161     }
 1162     switch (state)
 1163     {
 1164     case CEditHandler::download:
 1165     case CEditHandler::upload:
 1166     case CEditHandler::upload_and_remove:
 1167     case CEditHandler::upload_and_remove_failed:
 1168         wxMessageBoxEx(_("A file with that name is already being transferred."), _("Cannot view/edit selected file"), wxICON_EXCLAMATION);
 1169         return false;
 1170     case CEditHandler::removing:
 1171         if (!Remove(file.name, path, site)) {
 1172             wxMessageBoxEx(_("A file with that name is still being edited. Please close it and try again."), _("Selected file is already opened"), wxICON_EXCLAMATION);
 1173             return false;
 1174         }
 1175         break;
 1176     case CEditHandler::edit:
 1177     {
 1178         int action = already_editing_action;
 1179         if (!action) {
 1180             wxDialogEx dlg;
 1181             if (!dlg.Create(parent, -1, _("Selected file already being edited"))) {
 1182                 wxBell();
 1183                 return false;
 1184             }
 1185 
 1186             auto& lay = dlg.layout();
 1187             auto main = lay.createMain(&dlg, 1);
 1188             main->AddGrowableCol(0);
 1189 
 1190             main->Add(new wxStaticText(&dlg, -1, _("The selected file is already being edited:")));
 1191             main->Add(new wxStaticText(&dlg, -1, LabelEscape(file.name)));
 1192 
 1193             main->AddSpacer(0);
 1194 
 1195             int choices = COptions::Get()->GetOptionVal(OPTION_PERSISTENT_CHOICES);
 1196 
 1197             wxRadioButton* reopen{};
 1198             if (type == local) {
 1199                 main->Add(new wxStaticText(&dlg, -1, _("Do you want to reopen this file?")));
 1200             }
 1201             else {
 1202                 main->Add(new wxStaticText(&dlg, -1, _("Action to perform:")));
 1203 
 1204                 reopen = new wxRadioButton(&dlg, -1, _("&Reopen local file"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
 1205                 main->Add(reopen);
 1206                 wxRadioButton* retransfer = new wxRadioButton(&dlg, -1, _("&Discard local file then download and edit file anew"));
 1207                 main->Add(retransfer);
 1208 
 1209                 if (choices & edit_choices::edit_existing_action) {
 1210                     retransfer->SetValue(true);
 1211                 }
 1212                 else {
 1213                     reopen->SetValue(true);
 1214                 }
 1215             }
 1216 
 1217             wxCheckBox* always{};
 1218             if (fileCount > 1) {
 1219                 always = new wxCheckBox(&dlg, -1, _("Do the same with &all selected files already being edited"));
 1220                 main->Add(always);
 1221                 if (choices & edit_choices::edit_existing_always) {
 1222                     always->SetValue(true);
 1223                 }
 1224             }
 1225 
 1226             auto buttons = lay.createButtonSizer(&dlg, main, true);
 1227 
 1228             if (type == remote) {
 1229                 auto ok = new wxButton(&dlg, wxID_OK, _("OK"));
 1230                 ok->SetDefault();
 1231                 buttons->AddButton(ok);
 1232                 auto cancel = new wxButton(&dlg, wxID_CANCEL, _("Cancel"));
 1233                 buttons->AddButton(cancel);
 1234             }
 1235             else {
 1236                 auto yes = new wxButton(&dlg, wxID_YES, _("&Yes"));
 1237                 yes->SetDefault();
 1238                 buttons->AddButton(yes);
 1239                 auto no = new wxButton(&dlg, wxID_NO, _("&No"));
 1240                 buttons->AddButton(no);
 1241                 yes->Bind(wxEVT_BUTTON, [&dlg](wxEvent const&) { dlg.EndDialog(wxID_YES); });
 1242                 no->Bind(wxEVT_BUTTON, [&dlg](wxEvent const&) { dlg.EndDialog(wxID_NO); });
 1243             }
 1244             buttons->Realize();
 1245 
 1246             dlg.GetSizer()->Fit(&dlg);
 1247             int res = dlg.ShowModal();
 1248             if (res != wxID_OK && res != wxID_YES) {
 1249                 wxBell();
 1250                 action = -1;
 1251             }
 1252             else if (type == CEditHandler::local || (reopen && reopen->GetValue())) {
 1253                 action = 1;
 1254                 if (type == CEditHandler::remote) {
 1255                     choices &= ~edit_choices::edit_existing_action;
 1256                 }
 1257             }
 1258             else {
 1259                 action = 2;
 1260                 choices |= edit_choices::edit_existing_action;
 1261             }
 1262 
 1263             if (fileCount > 1) {
 1264                 if (always && always->GetValue()) {
 1265                     already_editing_action = action;
 1266                     choices |= edit_choices::edit_existing_always;
 1267                 }
 1268                 else {
 1269                     choices &= ~edit_choices::edit_existing_always;
 1270                 }
 1271             }
 1272             COptions::Get()->SetOption(OPTION_PERSISTENT_CHOICES, choices);
 1273         }
 1274 
 1275         if (action == -1) {
 1276             return false;
 1277         }
 1278         else if (action == 1) {
 1279             if (type == CEditHandler::local) {
 1280                 LaunchEditor(file.name);
 1281             }
 1282             else {
 1283                 LaunchEditor(file.name, path, site);
 1284             }
 1285             return true;
 1286         }
 1287         else {
 1288             if (!Remove(file.name, path, site)) {
 1289                 wxMessageBoxEx(_("The selected file is still opened in some other program, please close it."), _("Selected file is still being edited"), wxICON_EXCLAMATION);
 1290                 return false;
 1291             }
 1292         }
 1293     }
 1294     break;
 1295     default:
 1296         break;
 1297     }
 1298 
 1299     // Create local filename if needed
 1300     std::wstring localFile;
 1301     std::wstring remoteFile;
 1302     if (type == fileType::local) {
 1303         localFile = file.name;
 1304 
 1305         CLocalPath localPath(localFile, &remoteFile);
 1306         if (localPath.empty()) {
 1307             wxBell();
 1308             return false;
 1309         }
 1310     }
 1311     else {
 1312         localFile = GetTemporaryFile(file.name);
 1313         if (localFile.empty()) {
 1314             wxMessageBoxEx(_("Could not create temporary file name."), _("Cannot view/edit selected file"), wxICON_EXCLAMATION);
 1315             return false;
 1316         }
 1317         remoteFile = file.name;
 1318     }
 1319 
 1320 
 1321     // Find associated program
 1322     bool program_exists = false;
 1323     std::vector<std::wstring> cmd_with_args;
 1324     if (!wxGetKeyState(WXK_SHIFT) || COptions::Get()->GetOptionVal(OPTION_EDIT_ALWAYSDEFAULT)) {
 1325         cmd_with_args = CanOpen(file.name, program_exists);
 1326     }
 1327     if (cmd_with_args.empty()) {
 1328         CNewAssociationDialog dlg(parent);
 1329         if (!dlg.Run(file.name)) {
 1330             return false;
 1331         }
 1332         cmd_with_args = CanOpen(file.name, program_exists);
 1333         if (cmd_with_args.empty()) {
 1334             wxMessageBoxEx(wxString::Format(_("The file '%s' could not be opened:\nNo program has been associated on your system with this file type."), file.name), _("Opening failed"), wxICON_EXCLAMATION);
 1335             return false;
 1336         }
 1337     }
 1338     if (!program_exists) {
 1339         wxString msg = wxString::Format(_("The file '%s' cannot be opened:\nThe associated program (%s) could not be found.\nPlease check your filetype associations."), file.name, QuoteCommand(cmd_with_args));
 1340         wxMessageBoxEx(msg, _("Cannot edit file"), wxICON_EXCLAMATION);
 1341         return false;
 1342     }
 1343 
 1344     // We can proceed with adding the item and either open it or transfer it.
 1345     return AddFile(type, localFile, remoteFile, path, site, file.size);
 1346 }
 1347 
 1348 
 1349 #define COLUMN_NAME 0
 1350 #define COLUMN_TYPE 1
 1351 #define COLUMN_REMOTEPATH 2
 1352 #define COLUMN_STATUS 3
 1353 
 1354 struct CEditHandlerStatusDialog::impl final
 1355 {
 1356     wxWindow* parent_{};
 1357 
 1358     wxListCtrlEx* listCtrl_{};
 1359 
 1360     wxButton* unedit_{};
 1361     wxButton* upload_{};
 1362     wxButton* upload_and_unedit_{};
 1363     wxButton* open_{};
 1364     CEditHandler* editHandler_{};
 1365 
 1366     std::unique_ptr<CWindowStateManager> windowStateManager_;
 1367 };
 1368 
 1369 CEditHandlerStatusDialog::CEditHandlerStatusDialog(wxWindow* parent)
 1370     : impl_(std::make_unique<impl>())
 1371 {
 1372     impl_->parent_ = parent;
 1373 }
 1374 
 1375 CEditHandlerStatusDialog::~CEditHandlerStatusDialog()
 1376 {
 1377     if (impl_ && impl_->windowStateManager_) {
 1378         impl_->windowStateManager_->Remember(OPTION_EDITSTATUSDIALOG_SIZE);
 1379     }
 1380 }
 1381 
 1382 int CEditHandlerStatusDialog::ShowModal()
 1383 {
 1384     impl_->editHandler_ = CEditHandler::Get();
 1385     if (!impl_->editHandler_) {
 1386         return wxID_CANCEL;
 1387     }
 1388 
 1389     if (!impl_->editHandler_->GetFileCount(CEditHandler::none, CEditHandler::unknown)) {
 1390         wxMessageBoxEx(_("No files are currently being edited."), _("Cannot show dialog"), wxICON_INFORMATION, impl_->parent_);
 1391         return wxID_CANCEL;
 1392     }
 1393 
 1394     if (!Create(impl_->parent_, -1, _("Files currently being edited"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)) {
 1395         return wxID_CANCEL;
 1396     }
 1397 
 1398     auto & lay = layout();
 1399     auto main = lay.createMain(this, 1);
 1400 
 1401     main->Add(new wxStaticText(this, -1, _("The &following files are currently being edited:")));
 1402 
 1403     impl_->listCtrl_ = new wxListCtrlEx(this, -1, wxDefaultPosition, wxDefaultSize, wxLC_REPORT);
 1404     impl_->listCtrl_->SetFocus();
 1405     main->Add(impl_->listCtrl_, lay.grow);
 1406     main->AddGrowableCol(0);
 1407     main->AddGrowableRow(1);
 1408 
 1409     impl_->listCtrl_->InsertColumn(0, _("Filename"));
 1410     impl_->listCtrl_->InsertColumn(1, _("Type"));
 1411     impl_->listCtrl_->InsertColumn(2, _("Remote path"));
 1412     impl_->listCtrl_->InsertColumn(3, _("Status"));
 1413 
 1414     {
 1415         const std::list<CEditHandler::t_fileData>& files = impl_->editHandler_->GetFiles(CEditHandler::remote);
 1416         unsigned int i = 0;
 1417         for (std::list<CEditHandler::t_fileData>::const_iterator iter = files.begin(); iter != files.end(); ++iter, ++i) {
 1418             impl_->listCtrl_->InsertItem(i, iter->remoteFile);
 1419             impl_->listCtrl_->SetItem(i, COLUMN_TYPE, _("Remote"));
 1420             switch (iter->state)
 1421             {
 1422             case CEditHandler::download:
 1423                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Downloading"));
 1424                 break;
 1425             case CEditHandler::upload:
 1426                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading"));
 1427                 break;
 1428             case CEditHandler::upload_and_remove:
 1429                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading and pending removal"));
 1430                 break;
 1431             case CEditHandler::upload_and_remove_failed:
 1432                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Upload failed"));
 1433                 break;
 1434             case CEditHandler::removing:
 1435                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Pending removal"));
 1436                 break;
 1437             case CEditHandler::edit:
 1438                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Being edited"));
 1439                 break;
 1440             default:
 1441                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Unknown"));
 1442                 break;
 1443             }
 1444             impl_->listCtrl_->SetItem(i, COLUMN_REMOTEPATH, iter->site.Format(ServerFormat::with_user_and_optional_port) + iter->remotePath.GetPath());
 1445             CEditHandler::t_fileData* pData = new CEditHandler::t_fileData(*iter);
 1446             impl_->listCtrl_->SetItemPtrData(i, (wxUIntPtr)pData);
 1447         }
 1448     }
 1449 
 1450     {
 1451         const std::list<CEditHandler::t_fileData>& files = impl_->editHandler_->GetFiles(CEditHandler::local);
 1452         unsigned int i = 0;
 1453         for (std::list<CEditHandler::t_fileData>::const_iterator iter = files.begin(); iter != files.end(); ++iter, ++i) {
 1454             impl_->listCtrl_->InsertItem(i, iter->localFile);
 1455             impl_->listCtrl_->SetItem(i, COLUMN_TYPE, _("Local"));
 1456             switch (iter->state)
 1457             {
 1458             case CEditHandler::upload:
 1459                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading"));
 1460                 break;
 1461             case CEditHandler::upload_and_remove:
 1462                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading and unediting"));
 1463                 break;
 1464             case CEditHandler::edit:
 1465                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Being edited"));
 1466                 break;
 1467             default:
 1468                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Unknown"));
 1469                 break;
 1470             }
 1471             impl_->listCtrl_->SetItem(i, COLUMN_REMOTEPATH, iter->site.Format(ServerFormat::with_user_and_optional_port) + iter->remotePath.GetPath());
 1472             CEditHandler::t_fileData* pData = new CEditHandler::t_fileData(*iter);
 1473             impl_->listCtrl_->SetItemPtrData(i, (wxUIntPtr)pData);
 1474         }
 1475     }
 1476 
 1477     for (int i = 0; i < 4; ++i) {
 1478         impl_->listCtrl_->SetColumnWidth(i, wxLIST_AUTOSIZE);
 1479     }
 1480     impl_->listCtrl_->SetMinSize(wxSize(impl_->listCtrl_->GetColumnWidth(0) + impl_->listCtrl_->GetColumnWidth(1) + impl_->listCtrl_->GetColumnWidth(2) + impl_->listCtrl_->GetColumnWidth(3) + lay.dlgUnits(10), impl_->listCtrl_->GetMinSize().GetHeight()));
 1481 
 1482     auto onsel = [this](wxListEvent const&) { SetCtrlState(); };
 1483     impl_->listCtrl_->Bind(wxEVT_LIST_ITEM_SELECTED, onsel);
 1484     impl_->listCtrl_->Bind(wxEVT_LIST_ITEM_DESELECTED, onsel);
 1485 
 1486     main->Add(new wxStaticText(this, -1, _("Action on selected file:")));
 1487 
 1488     auto inner = lay.createGrid(2, 2);
 1489     main->Add(inner, lay.halign);
 1490     
 1491     impl_->unedit_ = new wxButton(this, -1, _("&Unedit"));
 1492     impl_->unedit_->Bind(wxEVT_BUTTON, [this](wxCommandEvent const&) { OnUnedit(); });
 1493     inner->Add(impl_->unedit_, lay.valigng);
 1494     impl_->upload_ = new wxButton(this, -1, _("U&pload"));
 1495     impl_->upload_->Bind(wxEVT_BUTTON, [this](wxCommandEvent const&) { OnUpload(false); });
 1496     inner->Add(impl_->upload_, lay.valigng);
 1497     impl_->upload_and_unedit_ = new wxButton(this, -1, _("Up&load and unedit"));
 1498     impl_->upload_and_unedit_->Bind(wxEVT_BUTTON, [this](wxCommandEvent const&) { OnUpload(true); });
 1499     inner->Add(impl_->upload_and_unedit_, lay.valigng);
 1500     impl_->open_ = new wxButton(this, -1, _("Op&en file"));
 1501     impl_->open_->Bind(wxEVT_BUTTON, [this](wxCommandEvent const&) { OnEdit(); });
 1502     inner->Add(impl_->open_, lay.valigng);
 1503 
 1504 
 1505     auto buttons = lay.createButtonSizer(this, main, true);
 1506     auto ok = new wxButton(this, wxID_OK, _("OK"));
 1507     ok->SetDefault();
 1508     buttons->AddButton(ok);
 1509     buttons->Realize();
 1510 
 1511     GetSizer()->Fit(this);
 1512     SetMinClientSize(GetSizer()->GetMinSize());
 1513 
 1514     impl_->windowStateManager_ = std::make_unique<CWindowStateManager>(static_cast<wxTopLevelWindow*>(this));
 1515     impl_->windowStateManager_->Restore(OPTION_EDITSTATUSDIALOG_SIZE, GetSize());
 1516 
 1517     SetCtrlState();
 1518 
 1519     int res = wxDialogEx::ShowModal();
 1520 
 1521     for (int i = 0; i < impl_->listCtrl_->GetItemCount(); ++i) {
 1522         delete (CEditHandler::t_fileData*)impl_->listCtrl_->GetItemData(i);
 1523     }
 1524 
 1525     return res;
 1526 }
 1527 
 1528 void CEditHandlerStatusDialog::SetCtrlState()
 1529 {
 1530     bool selectedEdited = false;
 1531     bool selectedOther = false;
 1532     bool selectedUploadRemoveFailed = false;
 1533 
 1534     int item = -1;
 1535     while ((item = impl_->listCtrl_->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
 1536         CEditHandler::fileType type;
 1537         CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
 1538         if (pData->state == CEditHandler::edit) {
 1539             selectedEdited = true;
 1540         }
 1541         else if (pData->state == CEditHandler::upload_and_remove_failed) {
 1542             selectedUploadRemoveFailed = true;
 1543         }
 1544         else {
 1545             selectedOther = true;
 1546         }
 1547     }
 1548 
 1549     bool const select = selectedEdited && !selectedOther && !selectedUploadRemoveFailed;
 1550     impl_->unedit_->Enable(select || (!selectedOther && selectedUploadRemoveFailed));
 1551     impl_->upload_->Enable(select || (!selectedEdited && !selectedOther && selectedUploadRemoveFailed));
 1552     impl_->upload_and_unedit_->Enable(select || (!selectedEdited && !selectedOther && selectedUploadRemoveFailed));
 1553     impl_->open_->Enable(select);
 1554 }
 1555 
 1556 void CEditHandlerStatusDialog::OnUnedit()
 1557 {
 1558     std::list<int> files;
 1559     int item = -1;
 1560     while ((item = impl_->listCtrl_->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
 1561         impl_->listCtrl_->SetItemState(item, 0, wxLIST_STATE_SELECTED);
 1562         CEditHandler::fileType type;
 1563         CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
 1564         if (pData->state != CEditHandler::edit && pData->state != CEditHandler::upload_and_remove_failed) {
 1565             wxBell();
 1566             return;
 1567         }
 1568 
 1569         files.push_front(item);
 1570     }
 1571 
 1572     for (std::list<int>::const_iterator iter = files.begin(); iter != files.end(); ++iter) {
 1573         const int i = *iter;
 1574 
 1575         CEditHandler::fileType type;
 1576         CEditHandler::t_fileData* pData = GetDataFromItem(i, type);
 1577 
 1578         if (type == CEditHandler::local) {
 1579             impl_->editHandler_->Remove(pData->localFile);
 1580             delete pData;
 1581             impl_->listCtrl_->DeleteItem(i);
 1582         }
 1583         else {
 1584             if (impl_->editHandler_->Remove(pData->remoteFile, pData->remotePath, pData->site)) {
 1585                 delete pData;
 1586                 impl_->listCtrl_->DeleteItem(i);
 1587             }
 1588             else {
 1589                 impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Pending removal"));
 1590             }
 1591         }
 1592     }
 1593 
 1594     SetCtrlState();
 1595 }
 1596 
 1597 void CEditHandlerStatusDialog::OnUpload(bool unedit_after)
 1598 {
 1599     std::list<int> files;
 1600     int item = -1;
 1601     while ((item = impl_->listCtrl_->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
 1602         impl_->listCtrl_->SetItemState(item, 0, wxLIST_STATE_SELECTED);
 1603 
 1604         CEditHandler::fileType type;
 1605         CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
 1606 
 1607         if (pData->state != CEditHandler::edit && pData->state != CEditHandler::upload_and_remove_failed) {
 1608             wxBell();
 1609             return;
 1610         }
 1611         files.push_front(item);
 1612     }
 1613 
 1614     for (std::list<int>::const_iterator iter = files.begin(); iter != files.end(); ++iter) {
 1615         const int i = *iter;
 1616 
 1617         CEditHandler::fileType type;
 1618         CEditHandler::t_fileData* pData = GetDataFromItem(i, type);
 1619 
 1620         bool unedit = unedit_after || pData->state == CEditHandler::upload_and_remove_failed;
 1621 
 1622         if (type == CEditHandler::local) {
 1623             impl_->editHandler_->UploadFile(pData->localFile, unedit);
 1624         }
 1625         else {
 1626             impl_->editHandler_->UploadFile(pData->remoteFile, pData->remotePath, pData->site, unedit);
 1627         }
 1628 
 1629         if (!unedit) {
 1630             impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading"));
 1631         }
 1632         else if (type == CEditHandler::remote) {
 1633             impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading and pending removal"));
 1634         }
 1635         else {
 1636             impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Uploading and unediting"));
 1637         }
 1638     }
 1639 
 1640     SetCtrlState();
 1641 }
 1642 
 1643 void CEditHandlerStatusDialog::OnEdit()
 1644 {
 1645     std::list<int> files;
 1646     int item = -1;
 1647     while ((item = impl_->listCtrl_->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED)) != -1) {
 1648         impl_->listCtrl_->SetItemState(item, 0, wxLIST_STATE_SELECTED);
 1649 
 1650         CEditHandler::fileType type;
 1651         CEditHandler::t_fileData* pData = GetDataFromItem(item, type);
 1652 
 1653         if (pData->state != CEditHandler::edit) {
 1654             wxBell();
 1655             return;
 1656         }
 1657         files.push_front(item);
 1658     }
 1659 
 1660     for (std::list<int>::const_iterator iter = files.begin(); iter != files.end(); ++iter) {
 1661         const int i = *iter;
 1662 
 1663         CEditHandler::fileType type;
 1664         CEditHandler::t_fileData* pData = GetDataFromItem(i, type);
 1665 
 1666         if (type == CEditHandler::local) {
 1667             if (!impl_->editHandler_->LaunchEditor(pData->localFile)) {
 1668                 if (impl_->editHandler_->Remove(pData->localFile)) {
 1669                     delete pData;
 1670                     impl_->listCtrl_->DeleteItem(i);
 1671                 }
 1672                 else {
 1673                     impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Pending removal"));
 1674                 }
 1675             }
 1676         }
 1677         else {
 1678             if (!impl_->editHandler_->LaunchEditor(pData->remoteFile, pData->remotePath, pData->site)) {
 1679                 if (impl_->editHandler_->Remove(pData->remoteFile, pData->remotePath, pData->site)) {
 1680                     delete pData;
 1681                     impl_->listCtrl_->DeleteItem(i);
 1682                 }
 1683                 else {
 1684                     impl_->listCtrl_->SetItem(i, COLUMN_STATUS, _("Pending removal"));
 1685                 }
 1686             }
 1687         }
 1688     }
 1689 
 1690     SetCtrlState();
 1691 }
 1692 
 1693 CEditHandler::t_fileData* CEditHandlerStatusDialog::GetDataFromItem(int item, CEditHandler::fileType &type)
 1694 {
 1695     CEditHandler::t_fileData* pData = (CEditHandler::t_fileData*)impl_->listCtrl_->GetItemData(item);
 1696     wxASSERT(pData);
 1697 
 1698     wxListItem info;
 1699     info.SetMask(wxLIST_MASK_TEXT);
 1700     info.SetId(item);
 1701     info.SetColumn(1);
 1702     impl_->listCtrl_->GetItem(info);
 1703     if (info.GetText() == _("Local")) {
 1704         type = CEditHandler::local;
 1705     }
 1706     else {
 1707         type = CEditHandler::remote;
 1708     }
 1709 
 1710     return pData;
 1711 }
 1712 
 1713 
 1714 
 1715 struct CNewAssociationDialog::impl
 1716 {
 1717     wxRadioButton* rbSystem_{};
 1718     wxRadioButton* rbDefault_{};
 1719     wxRadioButton* rbCustom_{};
 1720 
 1721     wxCheckBox* always_{};
 1722 
 1723     wxTextCtrlEx* custom_{};
 1724     wxButton* browse_{};
 1725 };
 1726 
 1727 CNewAssociationDialog::CNewAssociationDialog(wxWindow *parent)
 1728     : parent_(parent)
 1729 {
 1730 }
 1731 
 1732 CNewAssociationDialog::~CNewAssociationDialog()
 1733 {
 1734 }
 1735 
 1736 void ShowQuotingRules(wxWindow* parent);
 1737 
 1738 bool CNewAssociationDialog::Run(std::wstring const& file)
 1739 {
 1740     file_ = file;
 1741 
 1742     ext_ = GetExtension(file);
 1743 
 1744     impl_ = std::make_unique<impl>();
 1745 
 1746     Create(parent_, -1, _("No program associated with filetype"));
 1747 
 1748     auto & lay = layout();
 1749 
 1750     auto * main = lay.createMain(this, 1);
 1751 
 1752     if (ext_.empty()) {
 1753         main->Add(new wxStaticText(this, -1, _("No program has been associated to edit extensionless files.")));
 1754     }
 1755     else if (ext_ == L".") {
 1756         main->Add(new wxStaticText(this, -1, _("No program has been associated to edit dotfiles.")));
 1757     }
 1758     else {
 1759         main->Add(new wxStaticText(this, -1, wxString::Format(_("No program has been associated to edit files with the extension '%s'."), LabelEscape(ext_))));
 1760     }
 1761 
 1762     main->Add(new wxStaticText(this, -1, _("Select how these files should be opened.")));
 1763 
 1764     {
 1765         auto const cmd_with_args = GetSystemAssociation(file);
 1766         if (!cmd_with_args.empty()) {
 1767             impl_->rbSystem_ = new wxRadioButton(this, -1, _("Use system association"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
 1768             impl_->rbSystem_->Bind(wxEVT_RADIOBUTTON, [this](wxEvent const&) { SetCtrlState(); });
 1769             impl_->rbSystem_->SetValue(true);
 1770             main->Add(impl_->rbSystem_);
 1771             main->Add(new wxStaticText(this, -1, _("The default editor for this file type is:") + L" " + LabelEscape(QuoteCommand(cmd_with_args))), 0, wxLEFT, lay.indent);
 1772         }
 1773     }
 1774 
 1775     {
 1776         auto const cmd_with_args = GetSystemAssociation(L"foo.txt");
 1777         if (!cmd_with_args.empty()) {
 1778             impl_->rbDefault_ = new wxRadioButton(this, -1, _("Use &default editor for text files"), wxDefaultPosition, wxDefaultSize, impl_->rbSystem_ ? 0 : wxRB_GROUP);
 1779             impl_->rbDefault_->Bind(wxEVT_RADIOBUTTON, [this](wxEvent const&) { SetCtrlState(); });
 1780             if (!impl_->rbSystem_) {
 1781                 impl_->rbDefault_->SetValue(true);
 1782             }
 1783             main->Add(impl_->rbDefault_);
 1784             main->Add(new wxStaticText(this, -1, _("The default editor for text files is:") + " " + LabelEscape(QuoteCommand(cmd_with_args))), 0, wxLEFT, lay.indent);
 1785             impl_->always_ = new wxCheckBox(this, -1, _("&Always use selection for all unassociated files"));
 1786             main->Add(impl_->always_, 0, wxLEFT, lay.indent);
 1787         }
 1788     }
 1789 
 1790     impl_->rbCustom_ = new wxRadioButton(this, -1, _("&Use custom program"), wxDefaultPosition, wxDefaultSize, (impl_->rbSystem_ || impl_->rbDefault_) ? 0 : wxRB_GROUP);
 1791     impl_->rbCustom_->Bind(wxEVT_RADIOBUTTON, [this](wxEvent const&) { SetCtrlState(); });
 1792     if (!impl_->rbSystem_ && !impl_->rbDefault_) {
 1793         impl_->rbCustom_->SetValue(true);
 1794     }
 1795     main->Add(impl_->rbCustom_);
 1796     auto row = lay.createFlex(2);
 1797     row->AddGrowableCol(0);
 1798     main->Add(row, 0, wxLEFT|wxGROW, lay.indent);
 1799     
 1800     auto rules = new wxHyperlinkCtrl(this, -1, _("Quoting rules"), wxString());
 1801     main->Add(rules, 0, wxLEFT, lay.indent);
 1802     rules->Bind(wxEVT_HYPERLINK, [this](wxHyperlinkEvent const&) { ShowQuotingRules(this); });
 1803 
 1804 
 1805     impl_->custom_ = new wxTextCtrlEx(this, -1, wxString());
 1806     row->Add(impl_->custom_, lay.valigng);
 1807     impl_->browse_ = new wxButton(this, -1, _("&Browse..."));
 1808     impl_->browse_->Bind(wxEVT_BUTTON, [this](wxEvent const&) { OnBrowseEditor(); });
 1809     row->Add(impl_->browse_, lay.valign);
 1810 
 1811     auto buttons = lay.createButtonSizer(this, main, true);
 1812     auto ok = new wxButton(this, wxID_OK, _("OK"));
 1813     ok->SetDefault();
 1814     buttons->AddButton(ok);
 1815     auto cancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
 1816     buttons->AddButton(cancel);
 1817     buttons->Realize();
 1818 
 1819     ok->Bind(wxEVT_BUTTON, [this](wxEvent const&) { OnOK(); });
 1820 
 1821     Layout();
 1822     GetSizer()->Fit(this);
 1823 
 1824     SetCtrlState();
 1825 
 1826     return ShowModal() == wxID_OK;
 1827 }
 1828 
 1829 void CNewAssociationDialog::SetCtrlState()
 1830 {
 1831     if (impl_->custom_) {
 1832         impl_->custom_->Enable(impl_->rbCustom_->GetValue());
 1833     }
 1834     if (impl_->browse_) {
 1835         impl_->browse_->Enable(impl_->rbCustom_->GetValue());
 1836     }
 1837     if (impl_->always_) {
 1838         impl_->always_->Enable(impl_->rbDefault_->GetValue());
 1839     }
 1840 }
 1841 
 1842 void CNewAssociationDialog::OnOK()
 1843 {
 1844     const bool custom = impl_->rbCustom_->GetValue();
 1845     const bool def = impl_->rbDefault_->GetValue();
 1846     const bool always = impl_->always_->GetValue();
 1847 
 1848     if (def && always) {
 1849         COptions::Get()->SetOption(OPTION_EDIT_DEFAULTEDITOR, _T("1"));
 1850         EndModal(wxID_OK);
 1851 
 1852         return;
 1853     }
 1854 
 1855     std::vector<std::wstring> cmd_with_args;
 1856     if (custom) {
 1857         std::wstring cmd = impl_->custom_->GetValue().ToStdWstring();
 1858         cmd_with_args = UnquoteCommand(cmd);
 1859         if (cmd_with_args.empty()) {
 1860             impl_->custom_->SetFocus();
 1861             wxMessageBoxEx(_("You need to enter a properly quoted command."), _("Cannot set file association"), wxICON_EXCLAMATION);
 1862             return;
 1863         }
 1864         if (!ProgramExists(cmd_with_args.front())) {
 1865             impl_->custom_->SetFocus();
 1866             wxMessageBoxEx(_("Selected editor does not exist."), _("Cannot set file association"), wxICON_EXCLAMATION, this);
 1867             return;
 1868         }
 1869         cmd = QuoteCommand(cmd_with_args);
 1870         impl_->custom_->ChangeValue(cmd);
 1871     }
 1872     else {
 1873         if (def) {
 1874             cmd_with_args = GetSystemAssociation(L"foo.txt");
 1875         }
 1876         else {
 1877             cmd_with_args = GetSystemAssociation(file_);
 1878         }
 1879         if (cmd_with_args.empty()) {
 1880             wxMessageBoxEx(_("The associated program could not be found."), _("Cannot set file association"), wxICON_EXCLAMATION, this);
 1881             return;
 1882         }
 1883     }
 1884 
 1885     if (ext_.empty()) {
 1886         ext_ = L"/";
 1887     }
 1888     auto associations = LoadAssociations();
 1889     associations[ext_] = cmd_with_args;
 1890     SaveAssociations(associations);
 1891 
 1892     EndModal(wxID_OK);
 1893 }
 1894 
 1895 void CNewAssociationDialog::OnBrowseEditor()
 1896 {
 1897     wxFileDialog dlg(this, _("Select default editor"), _T(""), _T(""),
 1898 #ifdef __WXMSW__
 1899         _T("Executable file (*.exe)|*.exe"),
 1900 #elif __WXMAC__
 1901         _T("Applications (*.app)|*.app"),
 1902 #else
 1903         wxFileSelectorDefaultWildcardStr,
 1904 #endif
 1905         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
 1906 
 1907     if (dlg.ShowModal() != wxID_OK) {
 1908         return;
 1909     }
 1910 
 1911     std::wstring editor = dlg.GetPath().ToStdWstring();
 1912     if (editor.empty()) {
 1913         return;
 1914     }
 1915 
 1916     if (!ProgramExists(editor)) {
 1917         impl_->custom_->SetFocus();
 1918         wxMessageBoxEx(_("Selected editor does not exist."), _("File not found"), wxICON_EXCLAMATION, this);
 1919         return;
 1920     }
 1921 
 1922     if (editor.find_first_of(L" \t'\"") != std::wstring::npos) {
 1923         fz::replace_substrings(editor, L"\"", L"\"\"");
 1924         editor = L"\"" + editor + L"\"";
 1925     }
 1926 
 1927     impl_->custom_->ChangeValue(editor);
 1928 }