source: git/src/cavernlog.cc @ 04078a7

faster-cavernlogwalls-datawalls-data-hanging-as-warning
Last change on this file since 04078a7 was 8adbe49, checked in by Olly Betts <olly@…>, 2 months ago

Don't pass signed char values to ctype.h functions

They're only defined on unsigned char values and EOF.

  • Property mode set to 100644
File size: 22.2 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005-2022 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((unsigned char)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_),
264      end(buf), survey(survey_)
265{
266    int fsize = parent->GetFont().GetPointSize();
267    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
268    SetFonts(wxString(), wxString(), sizes);
269}
270
271CavernLogWindow::~CavernLogWindow()
272{
273#ifdef CAVERNLOG_USE_THREADS
274    if (thread) stop_thread();
275#endif
276    if (cavern_out) {
277        wxEndBusyCursor();
278        cavern_out->Detach();
279    }
280}
281
282#ifdef CAVERNLOG_USE_THREADS
283void
284CavernLogWindow::stop_thread()
285{
286    // Killing the subprocess by its pid is theoretically racy, but in practice
287    // it's not going to cause issues, and it's all the wxProcess API seems to
288    // allow us to do.  If we don't kill the subprocess, we need to wait for it
289    // to write out some output - there seems to be no way to do the equivalent
290    // of select() with a timeout on a wxInputStream.
291    //
292    // The only alternative to this seems to be to do:
293    //
294    //     while (!s.CanRead()) {
295    //         if (TestDestroy()) return (wxThread::ExitCode)0;
296    //         wxMilliSleep(N);
297    //     }
298    //
299    // But that makes the log window update sluggishly, and we're using a
300    // worker thread precisely to try to avoid having to do dumb stuff like
301    // this.
302    wxProcess::Kill(cavern_out->GetPid());
303
304    {
305        wxCriticalSectionLocker enter(thread_lock);
306        if (thread) {
307            wxThreadError res;
308            res = thread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
309            if (res != wxTHREAD_NO_ERROR) {
310                // FIXME
311            }
312        }
313    }
314
315    // Wait for thread to complete.
316    while (true) {
317        {
318            wxCriticalSectionLocker enter(thread_lock);
319            if (!thread) break;
320        }
321        wxMilliSleep(1);
322    }
323}
324
325void
326CavernLogWindow::OnClose(wxCloseEvent &)
327{
328    if (thread) stop_thread();
329    Destroy();
330}
331#endif
332
333void
334CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
335{
336    wxString href = link.GetHref();
337    wxString title = link.GetTarget();
338    size_t colon2 = href.rfind(wxT(':'));
339    if (colon2 == wxString::npos)
340        return;
341    size_t colon = href.rfind(wxT(':'), colon2 - 1);
342    if (colon == wxString::npos)
343        return;
344    wxString cmd;
345    wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
346    if (p) {
347        cmd = p;
348        if (!cmd.find(wxT("$f"))) {
349            cmd += wxT(" $f");
350        }
351    } else {
352        p = wxGetenv(wxT("VISUAL"));
353        if (!p) p = wxGetenv(wxT("EDITOR"));
354        if (!p) {
355            cmd = wxT(DEFAULT_EDITOR_COMMAND);
356        } else {
357            cmd = p;
358            if (cmd == "gvim") {
359                cmd = wxT(GVIM_COMMAND);
360            } else if (cmd == "vim") {
361                cmd = wxT(VIM_COMMAND);
362            } else if (cmd == "nvim") {
363                cmd = wxT(NVIM_COMMAND);
364            } else if (cmd == "gedit") {
365                cmd = wxT(GEDIT_COMMAND);
366            } else if (cmd == "pluma") {
367                cmd = wxT(PLUMA_COMMAND);
368            } else if (cmd == "emacs") {
369                cmd = wxT(EMACS_COMMAND);
370            } else if (cmd == "nano") {
371                cmd = wxT(NANO_COMMAND);
372            } else if (cmd == "jed") {
373                cmd = wxT(JED_COMMAND);
374            } else if (cmd == "kate") {
375                cmd = wxT(KATE_COMMAND);
376            } else {
377                // Escape any $.
378                cmd.Replace(wxT("$"), wxT("$$"));
379                cmd += wxT(" $f");
380            }
381        }
382    }
383    size_t i = 0;
384    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
385        if (++i >= cmd.size()) break;
386        switch ((int)cmd[i]) {
387            case wxT('$'):
388                cmd.erase(i, 1);
389                break;
390            case wxT('f'): {
391                wxString f = escape_for_shell(href.substr(0, colon), true);
392                cmd.replace(i - 1, 2, f);
393                i += f.size() - 1;
394                break;
395            }
396            case wxT('t'): {
397                wxString t = escape_for_shell(title);
398                cmd.replace(i - 1, 2, t);
399                i += t.size() - 1;
400                break;
401            }
402            case wxT('l'): {
403                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
404                cmd.replace(i - 1, 2, l);
405                i += l.size() - 1;
406                break;
407            }
408            case wxT('c'): {
409                wxString l;
410                if (colon2 >= href.size() - 1)
411                    l = wxT("0");
412                else
413                    l = escape_for_shell(href.substr(colon2 + 1));
414                cmd.replace(i - 1, 2, l);
415                i += l.size() - 1;
416                break;
417            }
418            default:
419                ++i;
420        }
421    }
422
423    if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
424        return;
425
426    wxString m;
427    // TRANSLATORS: %s is replaced by the command we attempted to run.
428    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
429    m += wxT(" (");
430    m += wxString(strerror(errno), wxConvUTF8);
431    m += wxT(')');
432    wxGetApp().ReportError(m);
433}
434
435void
436CavernLogWindow::process(const wxString &file)
437{
438    SetPage(wxString());
439#ifdef CAVERNLOG_USE_THREADS
440    if (thread) stop_thread();
441#endif
442    if (cavern_out) {
443        cavern_out->Detach();
444        cavern_out = NULL;
445    } else {
446        wxBeginBusyCursor();
447    }
448
449    SetFocus();
450    filename = file;
451
452    info_count = 0;
453    link_count = 0;
454    cur.resize(0);
455    log_txt.resize(0);
456
457#ifdef __WXMSW__
458    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
459#else
460    setenv("SURVEX_UTF8", "1", 1);
461#endif
462
463    wxString escaped_file = escape_for_shell(file, true);
464    wxString cmd = get_command_path(L"cavern");
465    cmd = escape_for_shell(cmd, false);
466    cmd += wxT(" -o ");
467    cmd += escaped_file;
468    cmd += wxT(' ');
469    cmd += escaped_file;
470
471    cavern_out = wxProcess::Open(cmd);
472    if (!cavern_out) {
473        wxString m;
474        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
475        m += wxT(" (");
476        m += wxString(strerror(errno), wxConvUTF8);
477        m += wxT(')');
478        wxGetApp().ReportError(m);
479        return;
480    }
481
482    // We want to receive the wxProcessEvent when cavern exits.
483    cavern_out->SetNextHandler(this);
484
485#ifdef CAVERNLOG_USE_THREADS
486    thread = new CavernThread(this, cavern_out->GetInputStream());
487    if (thread->Run() != wxTHREAD_NO_ERROR) {
488        wxGetApp().ReportError(wxT("Thread failed to start"));
489        delete thread;
490        thread = NULL;
491    }
492#endif
493}
494
495void
496CavernLogWindow::OnCavernOutput(wxCommandEvent & e_)
497{
498    CavernOutputEvent & e = (CavernOutputEvent&)e_;
499
500    if (e.len > 0) {
501        ssize_t n = e.len;
502        if (size_t(n) > sizeof(buf) - (end - buf)) abort();
503        memcpy(end, e.buf, n);
504        log_txt.append((const char *)end, n);
505        end += n;
506
507        const unsigned char * p = buf;
508
509        while (p != end) {
510            int ch = *p;
511            if (ch >= 0x80) {
512                // Decode multi-byte UTF-8 sequence.
513                if (ch < 0xc0) {
514                    // Invalid UTF-8 sequence.
515                    goto bad_utf8;
516                } else if (ch < 0xe0) {
517                    /* 2 byte sequence */
518                    if (end - p < 2) {
519                        // Incomplete UTF-8 sequence - try to read more.
520                        break;
521                    }
522                    int ch1 = *++p;
523                    if ((ch1 & 0xc0) != 0x80) {
524                        // Invalid UTF-8 sequence.
525                        goto bad_utf8;
526                    }
527                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
528                } else if (ch < 0xf0) {
529                    /* 3 byte sequence */
530                    if (end - p < 3) {
531                        // Incomplete UTF-8 sequence - try to read more.
532                        break;
533                    }
534                    int ch1 = *++p;
535                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
536                    if ((ch1 & 0xc0) != 0x80) {
537                        // Invalid UTF-8 sequence.
538                        goto bad_utf8;
539                    }
540                    int ch2 = *++p;
541                    if ((ch2 & 0xc0) != 0x80) {
542                        // Invalid UTF-8 sequence.
543                        goto bad_utf8;
544                    }
545                    ch |= (ch2 & 0x3f);
546                } else {
547                    // Overlong UTF-8 sequence.
548                    goto bad_utf8;
549                }
550            }
551            ++p;
552
553            if (false) {
554bad_utf8:
555                // Resync to next byte which starts a UTF-8 sequence.
556                while (p != end) {
557                    if (*p < 0x80 || (*p >= 0xc0 && *p < 0xf0)) break;
558                    ++p;
559                }
560                cur += badutf8_html;
561                continue;
562            }
563
564            switch (ch) {
565                case '\r':
566                    // Ignore.
567                    break;
568                case '\n': {
569                    if (cur.empty()) continue;
570                    if (cur[0] == ' ') {
571                        if (source_line.empty()) {
572                            // Source line shown for context.  Store it so we
573                            // can use the caret line to highlight it.
574                            swap(source_line, cur);
575                        } else {
576                            size_t caret = cur.rfind('^');
577                            if (caret != wxString::npos) {
578                                size_t tilde = cur.rfind('~');
579                                if (tilde == wxString::npos || tilde < caret) {
580                                    tilde = caret;
581                                }
582                                cur = "&nbsp;";
583                                // FIXME: Need to count each & entity as one character...
584                                cur.append(source_line, 1, caret - 1);
585                                if (caret < source_line.size()) {
586                                    cur.append("<b>");
587                                    cur.append(highlight ? highlight : wxT("<span \"color:blue\">"));
588                                    cur.append(source_line, caret, tilde + 1 - caret);
589                                    cur.append("</span></b>");
590                                }
591                                if (tilde + 1 < source_line.size()) {
592                                    cur.append(source_line, tilde + 1, wxString::npos);
593                                }
594                            } else {
595                                // No caret in second line - just output both.
596                                source_line.replace(0, 1, "&nbsp;");
597                                source_line += "<br>\n&nbsp;";
598                                source_line.append(cur, 1, wxString::npos);
599                                swap(cur, source_line);
600                            }
601                            cur += "<br>\n";
602                            AppendToPage(cur);
603                            cur.clear();
604                            source_line.clear();
605                        }
606                        continue;
607                    }
608
609                    if (!source_line.empty()) {
610                        // Previous line was a source line without column info
611                        // so just show it.
612                        source_line.replace(0, 1, "&nbsp;");
613                        source_line += "<br>\n";
614                        AppendToPage(source_line);
615                        source_line.clear();
616                    }
617#ifndef __WXMSW__
618                    size_t colon = cur.find(':');
619#else
620                    // If the path is "C:\path\to\file.svx" then don't split at the
621                    // : after the drive letter!  FIXME: better to look for ": "?
622                    size_t colon = cur.find(':', 2);
623#endif
624                    if (colon != wxString::npos && colon < cur.size() - 2) {
625                        ++colon;
626                        size_t i = colon;
627                        while (i < cur.size() - 2 &&
628                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
629                            ++i;
630                        }
631                        if (i > colon && cur[i] == wxT(':') ) {
632                            colon = i;
633                            // Check for column number.
634                            while (++i < cur.size() - 2 &&
635                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
636                            bool have_column = (i > colon + 1 && cur[i] == wxT(':'));
637                            if (have_column) {
638                                colon = i;
639                            } else {
640                                // If there's no colon, include a trailing ':'
641                                // so that we can unambiguously split the href
642                                // value up into filename, line and column.
643                                ++colon;
644                            }
645                            wxString tag = wxT("<a href=\"");
646                            tag.append(cur, 0, colon);
647                            while (cur[++i] == wxT(' ')) { }
648                            tag += wxT("\" target=\"");
649                            wxString target(cur, i, wxString::npos);
650                            target.Replace(badutf8_html, badutf8);
651                            tag += target;
652                            tag += wxT("\">");
653                            cur.insert(0, tag);
654                            size_t offset = colon + tag.size();
655                            cur.insert(offset, wxT("</a>"));
656                            offset += 4 + 2;
657
658                            if (!have_column) --offset;
659
660                            static const wxString & error_marker = wmsg(/*error*/93) + ":";
661                            static const wxString & warning_marker = wmsg(/*warning*/4) + ":";
662                            static const wxString & info_marker = wmsg(/*info*/485) + ":";
663
664                            if (cur.substr(offset, error_marker.size()) == error_marker) {
665                                // Show "error" marker in red.
666                                highlight = wxT("<span style=\"color:red\">");
667                                cur.insert(offset, highlight);
668                                offset += 24 + error_marker.size() - 1;
669                                cur.insert(offset, wxT("</span>"));
670                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
671                                // Show "warning" marker in orange.
672                                highlight = wxT("<span style=\"color:orange\">");
673                                cur.insert(offset, highlight);
674                                offset += 27 + warning_marker.size() - 1;
675                                cur.insert(offset, wxT("</span>"));
676                            } else if (cur.substr(offset, info_marker.size()) == info_marker) {
677                                // Show "info" marker in blue.
678                                ++info_count;
679                                highlight = wxT("<span style=\"color:blue\">");
680                                cur.insert(offset, highlight);
681                                offset += 25 + info_marker.size() - 1;
682                                cur.insert(offset, wxT("</span>"));
683                            } else {
684                                highlight = NULL;
685                            }
686
687                            ++link_count;
688                        }
689                    }
690
691                    // Save the scrollbar positions.
692                    int scroll_x = 0, scroll_y = 0;
693                    GetViewStart(&scroll_x, &scroll_y);
694
695                    cur += wxT("<br>\n");
696                    AppendToPage(cur);
697
698                    if (!link_count) {
699                        // Auto-scroll the window until we've reported a
700                        // warning or error.
701                        int x, y;
702                        GetVirtualSize(&x, &y);
703                        int xs, ys;
704                        GetClientSize(&xs, &ys);
705                        y -= ys;
706                        int xu, yu;
707                        GetScrollPixelsPerUnit(&xu, &yu);
708                        Scroll(scroll_x, y / yu);
709                    } else {
710                        // Restore the scrollbar positions.
711                        Scroll(scroll_x, scroll_y);
712                    }
713
714                    cur.clear();
715                    break;
716                }
717                case '<':
718                    cur += wxT("&lt;");
719                    break;
720                case '>':
721                    cur += wxT("&gt;");
722                    break;
723                case '&':
724                    cur += wxT("&amp;");
725                    break;
726                case '"':
727                    cur += wxT("&#34;");
728                    continue;
729                default:
730#ifdef wxUSE_UNICODE
731                    cur += wxChar(ch);
732#else
733                    // This approach means that highlighting of "error" or
734                    // "warning" won't work in translations where they contain
735                    // non-ASCII characters, but wxWidgets >= 3.0 in always
736                    // Unicode, so this corner case is already very uncommon,
737                    // and will become irrelevant with time.
738                    if (ch >= 128) {
739                        cur += wxString::Format(wxT("&#%u;"), ch);
740                    } else {
741                        cur += (char)ch;
742                    }
743#endif
744            }
745        }
746
747        size_t left = end - p;
748        end = buf + left;
749        if (left) memmove(buf, p, left);
750        Update();
751        return;
752    }
753
754    if (!source_line.empty()) {
755        // Previous line was a source line without column info
756        // so just show it.
757        source_line.replace(0, 1, "&nbsp;");
758        source_line += "<br>\n";
759        AppendToPage(source_line);
760        source_line.clear();
761    }
762
763    if (e.len <= 0 && buf != end) {
764        // Truncated UTF-8 sequence.
765        cur += badutf8_html;
766    }
767    if (!cur.empty()) {
768        cur += "<br>\n";
769        AppendToPage("<hr>" + cur);
770    }
771
772    /* TRANSLATORS: Label for button in aven’s cavern log window which
773     * allows the user to save the log to a file. */
774    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
775                                  (int)LOG_SAVE,
776                                  wmsg(/*&Save Log*/446).c_str()));
777    wxEndBusyCursor();
778    delete cavern_out;
779    cavern_out = NULL;
780    if (e.len < 0) {
781        /* Negative length indicates non-zero exit status from cavern. */
782        /* TRANSLATORS: Label for button in aven’s cavern log window which
783         * causes the survey data to be reprocessed. */
784        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
785                                      (int)LOG_REPROCESS,
786                                      wmsg(/*&Reprocess*/184).c_str()));
787        return;
788    }
789    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
790                                  (int)LOG_REPROCESS,
791                                  wmsg(/*&Reprocess*/184).c_str()));
792    AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
793    Update();
794    init_done = false;
795
796    {
797        wxString file3d(filename, 0, filename.length() - 3);
798        file3d.append(wxT("3d"));
799        if (!mainfrm->LoadData(file3d, survey)) {
800            return;
801        }
802    }
803
804    // Don't stay on log if there there are only "info" diagnostics.
805    if (link_count == info_count) {
806        wxCommandEvent dummy;
807        OnOK(dummy);
808    }
809}
810
811void
812CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
813{
814    CavernOutputEvent * e = new CavernOutputEvent();
815    // Zero length indicates successful exit, negative length unsuccessful exit.
816    e->len = (evt.GetExitCode() == 0 ? 0 : -1);
817    QueueEvent(e);
818}
819
820void
821CavernLogWindow::OnReprocess(wxCommandEvent &)
822{
823    process(filename);
824}
825
826void
827CavernLogWindow::OnSave(wxCommandEvent &)
828{
829    wxString filelog(filename, 0, filename.length() - 3);
830    filelog += wxT("log");
831#ifdef __WXMOTIF__
832    wxString ext(wxT("*.log"));
833#else
834    /* TRANSLATORS: Log files from running cavern (extension .log) */
835    wxString ext = wmsg(/*Log files*/447);
836    ext += wxT("|*.log");
837#endif
838    wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
839                     wxString(), filelog, ext,
840                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
841    if (dlg.ShowModal() != wxID_OK) return;
842    filelog = dlg.GetPath();
843    FILE * fh_log = wxFopen(filelog, wxT("w"));
844    if (!fh_log) {
845        wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
846        return;
847    }
848    fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
849    fclose(fh_log);
850}
851
852void
853CavernLogWindow::OnOK(wxCommandEvent &)
854{
855    if (init_done) {
856        mainfrm->HideLog(this);
857    } else {
858        mainfrm->InitialiseAfterLoad(filename, survey);
859        init_done = true;
860    }
861}
Note: See TracBrowser for help on using the repository browser.