source: git/src/cavernlog.cc @ 9463b1d

log-selectstereo-2025warn-only-for-hanging-survey
Last change on this file since 9463b1d was 9463b1d, checked in by Olly Betts <olly@…>, 14 months ago

Remove unnecessary probe for sys/select.h

We haven't use select() since fc626ae2804ce77d0f22ba24fc7eb285d46f5c34
in 2015.

  • Property mode set to 100644
File size: 22.1 KB
RevLine 
[6bec10c]1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
[76bc864f]4 * Copyright (C) 2005-2022 Olly Betts
[6bec10c]5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19 */
20
[4c83f84]21#include <config.h>
[6bec10c]22
23#include "aven.h"
24#include "cavernlog.h"
25#include "filename.h"
[fb5887c]26#include "mainfrm.h"
[6bec10c]27#include "message.h"
[4b40a9c]28#include "osalloc.h"
[6bec10c]29
[baeae66]30#include <errno.h>
[6bec10c]31#include <stdio.h>
[a90632c]32#include <stdlib.h>
[6bec10c]33
34#include <sys/time.h>
35#include <sys/types.h>
36#include <unistd.h>
37
[15ba0b5]38#include <wx/process.h>
39
[cc2d7ad]40#define GVIM_COMMAND "gvim +'call cursor($l,$c)' $f"
41#define VIM_COMMAND "x-terminal-emulator -e vim +'call cursor($l,$c)' $f"
[81d94a4]42#define NVIM_COMMAND "x-terminal-emulator -e nvim +'call cursor($l,$c)' $f"
[cc2d7ad]43#define GEDIT_COMMAND "gedit $f +$l:$c"
44// Pluma currently ignores the column, but include it assuming some future
45// version will add support.
46#define PLUMA_COMMAND "pluma +$l:$c $f"
47#define EMACS_COMMAND "x-terminal-emulator -e emacs +$l:$c $f"
48#define NANO_COMMAND "x-terminal-emulator -e nano +$l,$c $f"
49#define JED_COMMAND "x-terminal-emulator -e jed $f -g $l"
50#define KATE_COMMAND "kate -l $l -c $c $f"
51
52#ifdef __WXMSW__
53# define DEFAULT_EDITOR_COMMAND "notepad $f"
54#elif defined __WXMAC__
55# define DEFAULT_EDITOR_COMMAND "open -t $f"
56#else
57# define DEFAULT_EDITOR_COMMAND VIM_COMMAND
58#endif
59
[0e81a88]60enum { LOG_REPROCESS = 1234, LOG_SAVE = 1235 };
[fb5887c]61
[e768f29]62static const wxString badutf8_html(
63    wxT("<span style=\"color:white;background-color:red;\">&#xfffd;</span>"));
64static const wxString badutf8(wxUniChar(0xfffd));
65
[fc626ae]66// New event type for passing a chunk of cavern output from the worker thread
[15ba0b5]67// to the main thread (or from the idle event handler if we're not using
68// threads).
[fc626ae]69class CavernOutputEvent;
70
71wxDEFINE_EVENT(wxEVT_CAVERN_OUTPUT, CavernOutputEvent);
72
73class CavernOutputEvent : public wxEvent {
74  public:
75    char buf[1000];
76    int len;
77    CavernOutputEvent() : wxEvent(0, wxEVT_CAVERN_OUTPUT), len(0) { }
78
79    wxEvent * Clone() const {
80        CavernOutputEvent * e = new CavernOutputEvent();
81        e->len = len;
82        if (len > 0) memcpy(e->buf, buf, len);
83        return e;
84    }
85};
86
[15ba0b5]87#ifdef CAVERNLOG_USE_THREADS
[fc626ae]88class CavernThread : public wxThread {
89  protected:
90    virtual ExitCode Entry();
91
92    CavernLogWindow *handler;
93
[15ba0b5]94    wxInputStream * in;
[fc626ae]95
96  public:
[15ba0b5]97    CavernThread(CavernLogWindow *handler_, wxInputStream * in_)
98        : wxThread(wxTHREAD_DETACHED), handler(handler_), in(in_) { }
[fc626ae]99
100    ~CavernThread() {
101        wxCriticalSectionLocker enter(handler->thread_lock);
102        handler->thread = NULL;
103    }
104};
105
106wxThread::ExitCode
107CavernThread::Entry()
108{
[15ba0b5]109    while (true) {
110        CavernOutputEvent * e = new CavernOutputEvent();
111        in->Read(e->buf, sizeof(e->buf));
112        size_t n = in->LastRead();
113        if (n == 0 || TestDestroy()) {
114            delete e;
115            return (wxThread::ExitCode)0;
116        }
117        if (n == 1 && e->buf[0] == '\n') {
118            // Don't send an event with just a blank line in.
119            in->Read(e->buf + 1, sizeof(e->buf) - 1);
120            n += in->LastRead();
121            if (TestDestroy()) {
122                delete e;
123                return (wxThread::ExitCode)0;
124            }
125        }
126        e->len = n;
127        handler->QueueEvent(e);
128    }
129}
130
131#else
132
133void
134CavernLogWindow::OnIdle(wxIdleEvent& event)
135{
136    if (cavern_out == NULL) return;
137
138    wxInputStream * in = cavern_out->GetInputStream();
139
140    if (!in->CanRead()) {
141        // Avoid a tight busy-loop on idle events.
142        wxMilliSleep(10);
143    }
144    if (in->CanRead()) {
[fc626ae]145        CavernOutputEvent * e = new CavernOutputEvent();
[15ba0b5]146        in->Read(e->buf, sizeof(e->buf));
147        size_t n = in->LastRead();
148        if (n == 0) {
[fc626ae]149            delete e;
[15ba0b5]150            return;
[fc626ae]151        }
[15ba0b5]152        e->len = n;
153        QueueEvent(e);
154    }
155
156    event.RequestMore();
[fc626ae]157}
158#endif
159
[fb5887c]160BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
[81e1aa4]161    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
[0e81a88]162    EVT_BUTTON(LOG_SAVE, CavernLogWindow::OnSave)
[fb5887c]163    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
[15ba0b5]164    EVT_COMMAND(wxID_ANY, wxEVT_CAVERN_OUTPUT, CavernLogWindow::OnCavernOutput)
[0415bac]165#ifdef CAVERNLOG_USE_THREADS
[fc626ae]166    EVT_CLOSE(CavernLogWindow::OnClose)
167#else
[8991d7f]168    EVT_IDLE(CavernLogWindow::OnIdle)
[fc626ae]169#endif
[15ba0b5]170    EVT_END_PROCESS(wxID_ANY, CavernLogWindow::OnEndProcess)
[fb5887c]171END_EVENT_TABLE()
172
[549eb37]173wxString escape_for_shell(wxString s, bool protect_dash)
[6bec10c]174{
175#ifdef __WXMSW__
[faf83bee]176    // Correct quoting rules are insane:
177    //
[764fe32]178    // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
[faf83bee]179    //
180    // Thankfully wxExecute passes the command string to CreateProcess(), so
181    // at least we don't need to quote for cmd.exe too.
182    if (s.empty() || s.find_first_of(wxT(" \"\t\n\v")) != s.npos) {
183        // Need to quote.
184        s.insert(0, wxT('"'));
185        for (size_t p = 1; p < s.size(); ++p) {
186            size_t backslashes = 0;
187            while (s[p] == wxT('\\')) {
188                ++backslashes;
189                if (++p == s.size()) {
190                    // Escape all the backslashes, since they're before
191                    // the closing quote we add below.
192                    s.append(backslashes, wxT('\\'));
193                    goto done;
194                }
195            }
196
197            if (s[p] == wxT('"')) {
198                // Escape any preceding backslashes and this quote.
199                s.insert(p, backslashes + 1, wxT('\\'));
200                p += backslashes + 1;
[eff69a7]201            }
[6bec10c]202        }
[faf83bee]203done:
204        s.append(wxT('"'));
[6bec10c]205    }
206#else
[faf83bee]207    size_t p = 0;
[6bec10c]208    if (protect_dash && !s.empty() && s[0u] == '-') {
209        // If the filename starts with a '-', protect it from being
210        // treated as an option by prepending "./".
[5627cbb]211        s.insert(0, wxT("./"));
[6bec10c]212        p = 2;
213    }
214    while (p < s.size()) {
215        // Exclude a few safe characters which are common in filenames
[8adbe49]216        if (!isalnum((unsigned char)s[p]) && strchr("/._-", s[p]) == NULL) {
[6baad4a]217            s.insert(p, 1, wxT('\\'));
[6bec10c]218            ++p;
219        }
220        ++p;
221    }
222#endif
223    return s;
224}
225
[549eb37]226wxString get_command_path(const wxChar * command_name)
227{
228#ifdef __WXMSW__
229    wxString cmd;
230    {
231        DWORD len = 256;
232        wchar_t *buf = NULL;
233        while (1) {
234            DWORD got;
235            buf = (wchar_t*)osrealloc(buf, len * 2);
236            got = GetModuleFileNameW(NULL, buf, len);
237            if (got < len) break;
238            len += len;
239        }
240        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
241        wchar_t *start = buf;
242        if (wcsncmp(start, L"\\\\?\\", 4) == 0) start += 4;
243        wchar_t * slash = wcsrchr(start, L'\\');
244        if (slash) {
245            cmd.assign(start, slash - start + 1);
246        }
247        osfree(buf);
248    }
249#else
250    wxString cmd = wxString(msg_exepth(), wxConvUTF8);
251#endif
252    cmd += command_name;
253    return cmd;
254}
255
[d7b53e3]256CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
[b004e36]257    : wxHtmlWindow(parent),
[76bc864f]258      mainfrm(mainfrm_),
259      end(buf), survey(survey_)
[fb5887c]260{
[93ff5cc]261    int fsize = parent->GetFont().GetPointSize();
262    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
[5627cbb]263    SetFonts(wxString(), wxString(), sizes);
[93ff5cc]264}
265
[8991d7f]266CavernLogWindow::~CavernLogWindow()
267{
[15ba0b5]268#ifdef CAVERNLOG_USE_THREADS
269    if (thread) stop_thread();
270#endif
[c1144fe]271    if (cavern_out) {
272        wxEndBusyCursor();
[15ba0b5]273        cavern_out->Detach();
[c1144fe]274    }
[8991d7f]275}
276
[0415bac]277#ifdef CAVERNLOG_USE_THREADS
[fc626ae]278void
279CavernLogWindow::stop_thread()
280{
[15ba0b5]281    // Killing the subprocess by its pid is theoretically racy, but in practice
282    // it's not going to cause issues, and it's all the wxProcess API seems to
283    // allow us to do.  If we don't kill the subprocess, we need to wait for it
284    // to write out some output - there seems to be no way to do the equivalent
[b4ae834]285    // of select() with a timeout on a wxInputStream.
[15ba0b5]286    //
287    // The only alternative to this seems to be to do:
288    //
289    //     while (!s.CanRead()) {
290    //         if (TestDestroy()) return (wxThread::ExitCode)0;
291    //         wxMilliSleep(N);
292    //     }
293    //
294    // But that makes the log window update sluggishly, and we're using a
295    // worker thread precisely to try to avoid having to do dumb stuff like
296    // this.
297    wxProcess::Kill(cavern_out->GetPid());
298
[fc626ae]299    {
300        wxCriticalSectionLocker enter(thread_lock);
301        if (thread) {
[2a00719]302            wxThreadError res;
303            res = thread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
304            if (res != wxTHREAD_NO_ERROR) {
[fc626ae]305                // FIXME
306            }
307        }
308    }
309
310    // Wait for thread to complete.
311    while (true) {
312        {
313            wxCriticalSectionLocker enter(thread_lock);
314            if (!thread) break;
315        }
316        wxMilliSleep(1);
317    }
318}
319
320void
321CavernLogWindow::OnClose(wxCloseEvent &)
322{
323    if (thread) stop_thread();
324    Destroy();
325}
326#endif
327
[6bec10c]328void
329CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
330{
331    wxString href = link.GetHref();
332    wxString title = link.GetTarget();
[544a46c]333    size_t colon2 = href.rfind(wxT(':'));
334    if (colon2 == wxString::npos)
335        return;
336    size_t colon = href.rfind(wxT(':'), colon2 - 1);
[3d3fb6c]337    if (colon == wxString::npos)
338        return;
[cc2d7ad]339    wxString cmd;
[b8ba399]340    wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
[3d3fb6c]341    if (p) {
[b8ba399]342        cmd = p;
[3d3fb6c]343        if (!cmd.find(wxT("$f"))) {
344            cmd += wxT(" $f");
[6bec10c]345        }
[cc2d7ad]346    } else {
347        p = wxGetenv(wxT("VISUAL"));
348        if (!p) p = wxGetenv(wxT("EDITOR"));
349        if (!p) {
350            cmd = wxT(DEFAULT_EDITOR_COMMAND);
351        } else {
352            cmd = p;
353            if (cmd == "gvim") {
354                cmd = wxT(GVIM_COMMAND);
355            } else if (cmd == "vim") {
356                cmd = wxT(VIM_COMMAND);
[81d94a4]357            } else if (cmd == "nvim") {
358                cmd = wxT(NVIM_COMMAND);
[cc2d7ad]359            } else if (cmd == "gedit") {
360                cmd = wxT(GEDIT_COMMAND);
361            } else if (cmd == "pluma") {
362                cmd = wxT(PLUMA_COMMAND);
363            } else if (cmd == "emacs") {
364                cmd = wxT(EMACS_COMMAND);
365            } else if (cmd == "nano") {
366                cmd = wxT(NANO_COMMAND);
367            } else if (cmd == "jed") {
368                cmd = wxT(JED_COMMAND);
369            } else if (cmd == "kate") {
370                cmd = wxT(KATE_COMMAND);
371            } else {
372                // Escape any $.
373                cmd.Replace(wxT("$"), wxT("$$"));
374                cmd += wxT(" $f");
375            }
376        }
[3d3fb6c]377    }
378    size_t i = 0;
379    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
380        if (++i >= cmd.size()) break;
381        switch ((int)cmd[i]) {
382            case wxT('$'):
383                cmd.erase(i, 1);
384                break;
385            case wxT('f'): {
386                wxString f = escape_for_shell(href.substr(0, colon), true);
387                cmd.replace(i - 1, 2, f);
388                i += f.size() - 1;
389                break;
390            }
391            case wxT('t'): {
392                wxString t = escape_for_shell(title);
393                cmd.replace(i - 1, 2, t);
394                i += t.size() - 1;
395                break;
396            }
397            case wxT('l'): {
398                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
399                cmd.replace(i - 1, 2, l);
400                i += l.size() - 1;
401                break;
[1d71195]402            }
[3d3fb6c]403            case wxT('c'): {
404                wxString l;
[b2b852d]405                if (colon2 >= href.size() - 1)
[3d3fb6c]406                    l = wxT("0");
407                else
408                    l = escape_for_shell(href.substr(colon2 + 1));
409                cmd.replace(i - 1, 2, l);
410                i += l.size() - 1;
411                break;
412            }
413            default:
414                ++i;
[6bec10c]415        }
[3d3fb6c]416    }
[faf83bee]417
418    if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
[3d3fb6c]419        return;
[faf83bee]420
[3d3fb6c]421    wxString m;
[736f7df]422    // TRANSLATORS: %s is replaced by the command we attempted to run.
[3d3fb6c]423    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
424    m += wxT(" (");
425    m += wxString(strerror(errno), wxConvUTF8);
426    m += wxT(')');
427    wxGetApp().ReportError(m);
[6bec10c]428}
429
[8991d7f]430void
[6bec10c]431CavernLogWindow::process(const wxString &file)
432{
[00f4e69]433    SetPage(wxString());
[0415bac]434#ifdef CAVERNLOG_USE_THREADS
[fc626ae]435    if (thread) stop_thread();
436#endif
[c1144fe]437    if (cavern_out) {
[15ba0b5]438        cavern_out->Detach();
[15033fd]439        cavern_out = NULL;
[c1144fe]440    } else {
441        wxBeginBusyCursor();
442    }
443
[fb5887c]444    SetFocus();
445    filename = file;
446
[76bc864f]447    info_count = 0;
[8991d7f]448    link_count = 0;
449    cur.resize(0);
[0e81a88]450    log_txt.resize(0);
451
[6bec10c]452#ifdef __WXMSW__
[15322f2]453    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
[6bec10c]454#else
[06b1227]455    setenv("SURVEX_UTF8", "1", 1);
[6bec10c]456#endif
[93ff5cc]457
[6bec10c]458    wxString escaped_file = escape_for_shell(file, true);
[549eb37]459    wxString cmd = get_command_path(L"cavern");
[9e50f755]460    cmd = escape_for_shell(cmd, false);
[5627cbb]461    cmd += wxT(" -o ");
[6bec10c]462    cmd += escaped_file;
[5627cbb]463    cmd += wxT(' ');
[6bec10c]464    cmd += escaped_file;
465
[15ba0b5]466    cavern_out = wxProcess::Open(cmd);
[6bec10c]467    if (!cavern_out) {
[5627cbb]468        wxString m;
[3d3fb6c]469        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
[5627cbb]470        m += wxT(" (");
[2e4b8cd]471        m += wxString(strerror(errno), wxConvUTF8);
[5627cbb]472        m += wxT(')');
[6bec10c]473        wxGetApp().ReportError(m);
[8991d7f]474        return;
[6bec10c]475    }
476
[15ba0b5]477    // We want to receive the wxProcessEvent when cavern exits.
478    cavern_out->SetNextHandler(this);
[40b02e8]479
[0415bac]480#ifdef CAVERNLOG_USE_THREADS
[15ba0b5]481    thread = new CavernThread(this, cavern_out->GetInputStream());
[fc626ae]482    if (thread->Run() != wxTHREAD_NO_ERROR) {
483        wxGetApp().ReportError(wxT("Thread failed to start"));
484        delete thread;
485        thread = NULL;
486    }
[15ba0b5]487#endif
[fc626ae]488}
489
490void
491CavernLogWindow::OnCavernOutput(wxCommandEvent & e_)
492{
493    CavernOutputEvent & e = (CavernOutputEvent&)e_;
494
495    if (e.len > 0) {
496        ssize_t n = e.len;
[27222f8]497        if (size_t(n) > sizeof(buf) - (end - buf)) abort();
[fc626ae]498        memcpy(end, e.buf, n);
[330cb03]499        log_txt.append((const char *)end, n);
500        end += n;
[b3ee5f5]501
502        const unsigned char * p = buf;
503
504        while (p != end) {
[5605a4b]505            int ch = *p;
[b3ee5f5]506            if (ch >= 0x80) {
507                // Decode multi-byte UTF-8 sequence.
508                if (ch < 0xc0) {
[184d6d4]509                    // Invalid UTF-8 sequence.
510                    goto bad_utf8;
[b3ee5f5]511                } else if (ch < 0xe0) {
512                    /* 2 byte sequence */
[5605a4b]513                    if (end - p < 2) {
[b3ee5f5]514                        // Incomplete UTF-8 sequence - try to read more.
515                        break;
516                    }
[5605a4b]517                    int ch1 = *++p;
[b3ee5f5]518                    if ((ch1 & 0xc0) != 0x80) {
[184d6d4]519                        // Invalid UTF-8 sequence.
520                        goto bad_utf8;
[b3ee5f5]521                    }
522                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
523                } else if (ch < 0xf0) {
524                    /* 3 byte sequence */
[5605a4b]525                    if (end - p < 3) {
[b3ee5f5]526                        // Incomplete UTF-8 sequence - try to read more.
527                        break;
528                    }
[5605a4b]529                    int ch1 = *++p;
[b3ee5f5]530                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
531                    if ((ch1 & 0xc0) != 0x80) {
[184d6d4]532                        // Invalid UTF-8 sequence.
533                        goto bad_utf8;
[b3ee5f5]534                    }
[5605a4b]535                    int ch2 = *++p;
[b3ee5f5]536                    if ((ch2 & 0xc0) != 0x80) {
[184d6d4]537                        // Invalid UTF-8 sequence.
538                        goto bad_utf8;
[b3ee5f5]539                    }
540                    ch |= (ch2 & 0x3f);
[6bec10c]541                } else {
[184d6d4]542                    // Overlong UTF-8 sequence.
543                    goto bad_utf8;
[6bec10c]544                }
545            }
[5605a4b]546            ++p;
[6bec10c]547
[e768f29]548            if (false) {
549bad_utf8:
550                // Resync to next byte which starts a UTF-8 sequence.
551                while (p != end) {
552                    if (*p < 0x80 || (*p >= 0xc0 && *p < 0xf0)) break;
553                    ++p;
554                }
555                cur += badutf8_html;
556                continue;
557            }
558
[b3ee5f5]559            switch (ch) {
560                case '\r':
561                    // Ignore.
562                    break;
563                case '\n': {
564                    if (cur.empty()) continue;
[99865c9]565                    if (cur[0] == ' ') {
566                        if (source_line.empty()) {
567                            // Source line shown for context.  Store it so we
568                            // can use the caret line to highlight it.
569                            swap(source_line, cur);
570                        } else {
571                            size_t caret = cur.rfind('^');
572                            if (caret != wxString::npos) {
573                                size_t tilde = cur.rfind('~');
574                                if (tilde == wxString::npos || tilde < caret) {
575                                    tilde = caret;
576                                }
577                                cur = "&nbsp;";
578                                // FIXME: Need to count each & entity as one character...
579                                cur.append(source_line, 1, caret - 1);
[7ffb1c5]580                                if (caret < source_line.size()) {
581                                    cur.append("<b>");
[76bc864f]582                                    cur.append(highlight ? highlight : wxT("<span \"color:blue\">"));
[7ffb1c5]583                                    cur.append(source_line, caret, tilde + 1 - caret);
584                                    cur.append("</span></b>");
585                                }
[816eae6]586                                if (tilde + 1 < source_line.size()) {
587                                    cur.append(source_line, tilde + 1, wxString::npos);
588                                }
[99865c9]589                            } else {
[f207751]590                                // No caret in second line - just output both.
591                                source_line.replace(0, 1, "&nbsp;");
592                                source_line += "<br>\n&nbsp;";
593                                source_line.append(cur, 1, wxString::npos);
[99865c9]594                                swap(cur, source_line);
595                            }
596                            cur += "<br>\n";
597                            AppendToPage(cur);
598                            cur.clear();
599                            source_line.clear();
600                        }
601                        continue;
602                    }
[f207751]603
604                    if (!source_line.empty()) {
605                        // Previous line was a source line without column info
606                        // so just show it.
607                        source_line.replace(0, 1, "&nbsp;");
608                        source_line += "<br>\n";
609                        AppendToPage(source_line);
610                        source_line.clear();
611                    }
[a07ee70]612#ifndef __WXMSW__
[b3ee5f5]613                    size_t colon = cur.find(':');
[a07ee70]614#else
[b3ee5f5]615                    // If the path is "C:\path\to\file.svx" then don't split at the
616                    // : after the drive letter!  FIXME: better to look for ": "?
617                    size_t colon = cur.find(':', 2);
[6bec10c]618#endif
[b3ee5f5]619                    if (colon != wxString::npos && colon < cur.size() - 2) {
620                        ++colon;
621                        size_t i = colon;
622                        while (i < cur.size() - 2 &&
623                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
624                            ++i;
625                        }
626                        if (i > colon && cur[i] == wxT(':') ) {
[da96015]627                            colon = i;
[b3ee5f5]628                            // Check for column number.
629                            while (++i < cur.size() - 2 &&
630                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
[f9f0f0c]631                            bool have_column = (i > colon + 1 && cur[i] == wxT(':'));
632                            if (have_column) {
[b3ee5f5]633                                colon = i;
[544a46c]634                            } else {
635                                // If there's no colon, include a trailing ':'
636                                // so that we can unambiguously split the href
637                                // value up into filename, line and column.
638                                ++colon;
[b3ee5f5]639                            }
640                            wxString tag = wxT("<a href=\"");
641                            tag.append(cur, 0, colon);
642                            while (cur[++i] == wxT(' ')) { }
643                            tag += wxT("\" target=\"");
[e768f29]644                            wxString target(cur, i, wxString::npos);
645                            target.Replace(badutf8_html, badutf8);
646                            tag += target;
[b3ee5f5]647                            tag += wxT("\">");
648                            cur.insert(0, tag);
[40b02e8]649                            size_t offset = colon + tag.size();
650                            cur.insert(offset, wxT("</a>"));
651                            offset += 4 + 2;
[8991d7f]652
[f9f0f0c]653                            if (!have_column) --offset;
654
[8991d7f]655                            static const wxString & error_marker = wmsg(/*error*/93) + ":";
656                            static const wxString & warning_marker = wmsg(/*warning*/4) + ":";
[76bc864f]657                            static const wxString & info_marker = wmsg(/*info*/485) + ":";
[8991d7f]658
[40b02e8]659                            if (cur.substr(offset, error_marker.size()) == error_marker) {
660                                // Show "error" marker in red.
[99865c9]661                                highlight = wxT("<span style=\"color:red\">");
662                                cur.insert(offset, highlight);
[58055e11]663                                offset += 24 + error_marker.size() - 1;
[40b02e8]664                                cur.insert(offset, wxT("</span>"));
665                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
666                                // Show "warning" marker in orange.
[99865c9]667                                highlight = wxT("<span style=\"color:orange\">");
668                                cur.insert(offset, highlight);
[58055e11]669                                offset += 27 + warning_marker.size() - 1;
[40b02e8]670                                cur.insert(offset, wxT("</span>"));
[76bc864f]671                            } else if (cur.substr(offset, info_marker.size()) == info_marker) {
672                                // Show "info" marker in blue.
673                                ++info_count;
674                                highlight = wxT("<span style=\"color:blue\">");
675                                cur.insert(offset, highlight);
676                                offset += 25 + info_marker.size() - 1;
677                                cur.insert(offset, wxT("</span>"));
[99865c9]678                            } else {
679                                highlight = NULL;
[40b02e8]680                            }
681
[b3ee5f5]682                            ++link_count;
[da96015]683                        }
[6bec10c]684                    }
[93ff5cc]685
[b3ee5f5]686                    // Save the scrollbar positions.
687                    int scroll_x = 0, scroll_y = 0;
688                    GetViewStart(&scroll_x, &scroll_y);
[93ff5cc]689
[b3ee5f5]690                    cur += wxT("<br>\n");
691                    AppendToPage(cur);
[93ff5cc]692
[b3ee5f5]693                    if (!link_count) {
[8991d7f]694                        // Auto-scroll the window until we've reported a
695                        // warning or error.
[b3ee5f5]696                        int x, y;
697                        GetVirtualSize(&x, &y);
698                        int xs, ys;
699                        GetClientSize(&xs, &ys);
700                        y -= ys;
701                        int xu, yu;
702                        GetScrollPixelsPerUnit(&xu, &yu);
703                        Scroll(scroll_x, y / yu);
704                    } else {
705                        // Restore the scrollbar positions.
706                        Scroll(scroll_x, scroll_y);
707                    }
[a75f5a1]708
[b3ee5f5]709                    cur.clear();
710                    break;
[6bec10c]711                }
[b3ee5f5]712                case '<':
713                    cur += wxT("&lt;");
714                    break;
715                case '>':
716                    cur += wxT("&gt;");
717                    break;
718                case '&':
719                    cur += wxT("&amp;");
720                    break;
721                case '"':
[bc9c09a]722                    cur += wxT("&#34;");
[b3ee5f5]723                    continue;
724                default:
[aef136e]725#ifdef wxUSE_UNICODE
726                    cur += wxChar(ch);
727#else
728                    // This approach means that highlighting of "error" or
729                    // "warning" won't work in translations where they contain
730                    // non-ASCII characters, but wxWidgets >= 3.0 in always
731                    // Unicode, so this corner case is already very uncommon,
732                    // and will become irrelevant with time.
[b3ee5f5]733                    if (ch >= 128) {
734                        cur += wxString::Format(wxT("&#%u;"), ch);
735                    } else {
736                        cur += (char)ch;
737                    }
[aef136e]738#endif
[b3ee5f5]739            }
[6bec10c]740        }
[b3ee5f5]741
742        size_t left = end - p;
743        end = buf + left;
744        if (left) memmove(buf, p, left);
[8991d7f]745        Update();
746        return;
[6bec10c]747    }
[8991d7f]748
[f207751]749    if (!source_line.empty()) {
750        // Previous line was a source line without column info
751        // so just show it.
752        source_line.replace(0, 1, "&nbsp;");
753        source_line += "<br>\n";
754        AppendToPage(source_line);
755        source_line.clear();
756    }
757
[15ba0b5]758    if (e.len <= 0 && buf != end) {
[184d6d4]759        // Truncated UTF-8 sequence.
[e768f29]760        cur += badutf8_html;
[fc626ae]761    }
[f207751]762    if (!cur.empty()) {
763        cur += "<br>\n";
764        AppendToPage("<hr>" + cur);
765    }
[fb5887c]766
[0e81a88]767    /* TRANSLATORS: Label for button in aven’s cavern log window which
768     * allows the user to save the log to a file. */
769    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
770                                  (int)LOG_SAVE,
[9cd0657]771                                  wmsg(/*&Save Log*/446).c_str()));
[c1144fe]772    wxEndBusyCursor();
[15ba0b5]773    delete cavern_out;
[8991d7f]774    cavern_out = NULL;
[15ba0b5]775    if (e.len < 0) {
776        /* Negative length indicates non-zero exit status from cavern. */
[736f7df]777        /* TRANSLATORS: Label for button in aven’s cavern log window which
778         * causes the survey data to be reprocessed. */
[bbed692]779        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
780                                      (int)LOG_REPROCESS,
[9cd0657]781                                      wmsg(/*&Reprocess*/184).c_str()));
[8991d7f]782        return;
[6bec10c]783    }
[bbed692]784    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
785                                  (int)LOG_REPROCESS,
[9cd0657]786                                  wmsg(/*&Reprocess*/184).c_str()));
[d7b53e3]787    AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
788    Update();
789    init_done = false;
[fb5887c]790
[15ba0b5]791    {
792        wxString file3d(filename, 0, filename.length() - 3);
793        file3d.append(wxT("3d"));
794        if (!mainfrm->LoadData(file3d, survey)) {
795            return;
796        }
[d7b53e3]797    }
[15ba0b5]798
[76bc864f]799    // Don't stay on log if there there are only "info" diagnostics.
800    if (link_count == info_count) {
[8991d7f]801        wxCommandEvent dummy;
802        OnOK(dummy);
[fb5887c]803    }
[15ba0b5]804}
805
806void
807CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
808{
809    CavernOutputEvent * e = new CavernOutputEvent();
810    // Zero length indicates successful exit, negative length unsuccessful exit.
811    e->len = (evt.GetExitCode() == 0 ? 0 : -1);
812    QueueEvent(e);
[fb5887c]813}
814
[0e81a88]815void
[330cb03]816CavernLogWindow::OnReprocess(wxCommandEvent &)
[8991d7f]817{
818    process(filename);
819}
820
821void
[0e81a88]822CavernLogWindow::OnSave(wxCommandEvent &)
823{
824    wxString filelog(filename, 0, filename.length() - 3);
825    filelog += wxT("log");
826#ifdef __WXMOTIF__
827    wxString ext(wxT("*.log"));
828#else
[bb71423]829    /* TRANSLATORS: Log files from running cavern (extension .log) */
[0e81a88]830    wxString ext = wmsg(/*Log files*/447);
831    ext += wxT("|*.log");
832#endif
833    wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
834                     wxString(), filelog, ext,
835                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
836    if (dlg.ShowModal() != wxID_OK) return;
837    filelog = dlg.GetPath();
[3206c12]838    FILE * fh_log = wxFopen(filelog, wxT("w"));
[0e81a88]839    if (!fh_log) {
840        wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
841        return;
842    }
843    fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
844    fclose(fh_log);
845}
846
[fb5887c]847void
848CavernLogWindow::OnOK(wxCommandEvent &)
849{
[d7b53e3]850    if (init_done) {
851        mainfrm->HideLog(this);
852    } else {
[5e0b9f9d]853        mainfrm->InitialiseAfterLoad(filename, survey);
[d7b53e3]854        init_done = true;
855    }
[fb5887c]856}
Note: See TracBrowser for help on using the repository browser.