source: git/src/cavernlog.cc @ 9990aab

debug-cidebug-ci-sanitisersfaster-cavernloglog-selectstereo-2025walls-datawalls-data-hanging-as-warningwarn-only-for-hanging-survey
Last change on this file since 9990aab was 5605a4b, checked in by Olly Betts <olly@…>, 4 years ago

aven: Fix handle of partly read UTF-8 sequences

When reading cavern output for the log window, we need special handling
for the case when a chunk of output ends mid-way through a UTF-8
sequence. Previously we lost the first byte of the sequence in this
case (and would then show it as an invalid character), but now it is
handled correctly.

In practice, most of the cavern log output is ASCII so it's quite
possible nobody's ever actually hit this - I spotted it from looking
at the code.

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