source: git/src/cavernlog.cc @ 58eeab4

RELEASE/1.2debug-cidebug-ci-sanitiserswalls-data
Last change on this file since 58eeab4 was 9cd0657, checked in by Olly Betts <olly@…>, 6 years ago

Add shortcuts to buttons in cavern log window

Currently these don't work completely in wxGTK as the menu bar shortcuts
seem to take precedence when the same shortcut exists there.

  • Property mode set to 100644
File size: 21.9 KB
Line 
1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
4 * Copyright (C) 2005,2006,2010,2011,2012,2014,2015,2016 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#if wxCHECK_VERSION(2,9,2)
312            res = thread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
313#else
314            res = thread->Delete();
315#endif
316            if (res != wxTHREAD_NO_ERROR) {
317                // FIXME
318            }
319        }
320    }
321
322    // Wait for thread to complete.
323    while (true) {
324        {
325            wxCriticalSectionLocker enter(thread_lock);
326            if (!thread) break;
327        }
328        wxMilliSleep(1);
329    }
330}
331
332void
333CavernLogWindow::OnClose(wxCloseEvent &)
334{
335    if (thread) stop_thread();
336    Destroy();
337}
338#endif
339
340void
341CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
342{
343    wxString href = link.GetHref();
344    wxString title = link.GetTarget();
345    size_t colon2 = href.rfind(wxT(':'));
346    if (colon2 == wxString::npos)
347        return;
348    size_t colon = href.rfind(wxT(':'), colon2 - 1);
349    if (colon == wxString::npos)
350        return;
351    wxString cmd;
352    wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
353    if (p) {
354        cmd = p;
355        if (!cmd.find(wxT("$f"))) {
356            cmd += wxT(" $f");
357        }
358    } else {
359        p = wxGetenv(wxT("VISUAL"));
360        if (!p) p = wxGetenv(wxT("EDITOR"));
361        if (!p) {
362            cmd = wxT(DEFAULT_EDITOR_COMMAND);
363        } else {
364            cmd = p;
365            if (cmd == "gvim") {
366                cmd = wxT(GVIM_COMMAND);
367            } else if (cmd == "vim") {
368                cmd = wxT(VIM_COMMAND);
369            } else if (cmd == "nvim") {
370                cmd = wxT(NVIM_COMMAND);
371            } else if (cmd == "gedit") {
372                cmd = wxT(GEDIT_COMMAND);
373            } else if (cmd == "pluma") {
374                cmd = wxT(PLUMA_COMMAND);
375            } else if (cmd == "emacs") {
376                cmd = wxT(EMACS_COMMAND);
377            } else if (cmd == "nano") {
378                cmd = wxT(NANO_COMMAND);
379            } else if (cmd == "jed") {
380                cmd = wxT(JED_COMMAND);
381            } else if (cmd == "kate") {
382                cmd = wxT(KATE_COMMAND);
383            } else {
384                // Escape any $.
385                cmd.Replace(wxT("$"), wxT("$$"));
386                cmd += wxT(" $f");
387            }
388        }
389    }
390    size_t i = 0;
391    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
392        if (++i >= cmd.size()) break;
393        switch ((int)cmd[i]) {
394            case wxT('$'):
395                cmd.erase(i, 1);
396                break;
397            case wxT('f'): {
398                wxString f = escape_for_shell(href.substr(0, colon), true);
399                cmd.replace(i - 1, 2, f);
400                i += f.size() - 1;
401                break;
402            }
403            case wxT('t'): {
404                wxString t = escape_for_shell(title);
405                cmd.replace(i - 1, 2, t);
406                i += t.size() - 1;
407                break;
408            }
409            case wxT('l'): {
410                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
411                cmd.replace(i - 1, 2, l);
412                i += l.size() - 1;
413                break;
414            }
415            case wxT('c'): {
416                wxString l;
417                if (colon2 >= href.size() - 1)
418                    l = wxT("0");
419                else
420                    l = escape_for_shell(href.substr(colon2 + 1));
421                cmd.replace(i - 1, 2, l);
422                i += l.size() - 1;
423                break;
424            }
425            default:
426                ++i;
427        }
428    }
429
430    if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
431        return;
432
433    wxString m;
434    // TRANSLATORS: %s is replaced by the command we attempted to run.
435    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
436    m += wxT(" (");
437    m += wxString(strerror(errno), wxConvUTF8);
438    m += wxT(')');
439    wxGetApp().ReportError(m);
440}
441
442void
443CavernLogWindow::process(const wxString &file)
444{
445    SetPage(wxString());
446#ifdef CAVERNLOG_USE_THREADS
447    if (thread) stop_thread();
448#endif
449    if (cavern_out) {
450        cavern_out->Detach();
451        cavern_out = NULL;
452    } else {
453        wxBeginBusyCursor();
454    }
455
456    SetFocus();
457    filename = file;
458
459    link_count = 0;
460    cur.resize(0);
461    log_txt.resize(0);
462
463#ifdef __WXMSW__
464    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
465#else
466    setenv("SURVEX_UTF8", "1", 1);
467#endif
468
469    wxString escaped_file = escape_for_shell(file, true);
470    wxString cmd = get_command_path(L"cavern");
471    cmd = escape_for_shell(cmd, false);
472    cmd += wxT(" -o ");
473    cmd += escaped_file;
474    cmd += wxT(' ');
475    cmd += escaped_file;
476
477    cavern_out = wxProcess::Open(cmd);
478    if (!cavern_out) {
479        wxString m;
480        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
481        m += wxT(" (");
482        m += wxString(strerror(errno), wxConvUTF8);
483        m += wxT(')');
484        wxGetApp().ReportError(m);
485        return;
486    }
487
488    // We want to receive the wxProcessEvent when cavern exits.
489    cavern_out->SetNextHandler(this);
490
491#ifdef CAVERNLOG_USE_THREADS
492    thread = new CavernThread(this, cavern_out->GetInputStream());
493    if (thread->Run() != wxTHREAD_NO_ERROR) {
494        wxGetApp().ReportError(wxT("Thread failed to start"));
495        delete thread;
496        thread = NULL;
497    }
498#endif
499}
500
501void
502CavernLogWindow::OnCavernOutput(wxCommandEvent & e_)
503{
504    CavernOutputEvent & e = (CavernOutputEvent&)e_;
505
506    if (e.len > 0) {
507        ssize_t n = e.len;
508        if (size_t(n) > sizeof(buf) - (end - buf)) abort();
509        memcpy(end, e.buf, n);
510        log_txt.append((const char *)end, n);
511        end += n;
512
513        const unsigned char * p = buf;
514
515        while (p != end) {
516            int ch = *p++;
517            if (ch >= 0x80) {
518                // Decode multi-byte UTF-8 sequence.
519                if (ch < 0xc0) {
520                    // Invalid UTF-8 sequence.
521                    goto bad_utf8;
522                } else if (ch < 0xe0) {
523                    /* 2 byte sequence */
524                    if (p == end) {
525                        // Incomplete UTF-8 sequence - try to read more.
526                        break;
527                    }
528                    int ch1 = *p++;
529                    if ((ch1 & 0xc0) != 0x80) {
530                        // Invalid UTF-8 sequence.
531                        goto bad_utf8;
532                    }
533                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
534                } else if (ch < 0xf0) {
535                    /* 3 byte sequence */
536                    if (end - p <= 1) {
537                        // Incomplete UTF-8 sequence - try to read more.
538                        break;
539                    }
540                    int ch1 = *p++;
541                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
542                    if ((ch1 & 0xc0) != 0x80) {
543                        // Invalid UTF-8 sequence.
544                        goto bad_utf8;
545                    }
546                    int ch2 = *p++;
547                    if ((ch2 & 0xc0) != 0x80) {
548                        // Invalid UTF-8 sequence.
549                        goto bad_utf8;
550                    }
551                    ch |= (ch2 & 0x3f);
552                } else {
553                    // Overlong UTF-8 sequence.
554                    goto bad_utf8;
555                }
556            }
557
558            if (false) {
559bad_utf8:
560                // Resync to next byte which starts a UTF-8 sequence.
561                while (p != end) {
562                    if (*p < 0x80 || (*p >= 0xc0 && *p < 0xf0)) break;
563                    ++p;
564                }
565                cur += badutf8_html;
566                continue;
567            }
568
569            switch (ch) {
570                case '\r':
571                    // Ignore.
572                    break;
573                case '\n': {
574                    if (cur.empty()) continue;
575                    if (cur[0] == ' ') {
576                        if (source_line.empty()) {
577                            // Source line shown for context.  Store it so we
578                            // can use the caret line to highlight it.
579                            swap(source_line, cur);
580                        } else {
581                            size_t caret = cur.rfind('^');
582                            if (caret != wxString::npos) {
583                                size_t tilde = cur.rfind('~');
584                                if (tilde == wxString::npos || tilde < caret) {
585                                    tilde = caret;
586                                }
587                                cur = "&nbsp;";
588                                // FIXME: Need to count each & entity as one character...
589                                cur.append(source_line, 1, caret - 1);
590                                cur.append("<b>");
591                                cur.append(highlight ? highlight : wxT("<span \"color:green\">"));
592                                cur.append(source_line, caret, tilde + 1 - caret);
593                                cur.append("</span></b>");
594                                if (tilde + 1 < source_line.size()) {
595                                    cur.append(source_line, tilde + 1, wxString::npos);
596                                }
597                            } else {
598                                // No caret in second line - just output both.
599                                source_line.replace(0, 1, "&nbsp;");
600                                source_line += "<br>\n&nbsp;";
601                                source_line.append(cur, 1, wxString::npos);
602                                swap(cur, source_line);
603                            }
604                            cur += "<br>\n";
605                            AppendToPage(cur);
606                            cur.clear();
607                            source_line.clear();
608                        }
609                        continue;
610                    }
611
612                    if (!source_line.empty()) {
613                        // Previous line was a source line without column info
614                        // so just show it.
615                        source_line.replace(0, 1, "&nbsp;");
616                        source_line += "<br>\n";
617                        AppendToPage(source_line);
618                        source_line.clear();
619                    }
620#ifndef __WXMSW__
621                    size_t colon = cur.find(':');
622#else
623                    // If the path is "C:\path\to\file.svx" then don't split at the
624                    // : after the drive letter!  FIXME: better to look for ": "?
625                    size_t colon = cur.find(':', 2);
626#endif
627                    if (colon != wxString::npos && colon < cur.size() - 2) {
628                        ++colon;
629                        size_t i = colon;
630                        while (i < cur.size() - 2 &&
631                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
632                            ++i;
633                        }
634                        if (i > colon && cur[i] == wxT(':') ) {
635                            colon = i;
636                            // Check for column number.
637                            while (++i < cur.size() - 2 &&
638                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
639                            bool have_column = (i > colon + 1 && cur[i] == wxT(':'));
640                            if (have_column) {
641                                colon = i;
642                            } else {
643                                // If there's no colon, include a trailing ':'
644                                // so that we can unambiguously split the href
645                                // value up into filename, line and column.
646                                ++colon;
647                            }
648                            wxString tag = wxT("<a href=\"");
649                            tag.append(cur, 0, colon);
650                            while (cur[++i] == wxT(' ')) { }
651                            tag += wxT("\" target=\"");
652                            wxString target(cur, i, wxString::npos);
653                            target.Replace(badutf8_html, badutf8);
654                            tag += target;
655                            tag += wxT("\">");
656                            cur.insert(0, tag);
657                            size_t offset = colon + tag.size();
658                            cur.insert(offset, wxT("</a>"));
659                            offset += 4 + 2;
660
661                            if (!have_column) --offset;
662
663                            static const wxString & error_marker = wmsg(/*error*/93) + ":";
664                            static const wxString & warning_marker = wmsg(/*warning*/4) + ":";
665
666                            if (cur.substr(offset, error_marker.size()) == error_marker) {
667                                // Show "error" marker in red.
668                                highlight = wxT("<span style=\"color:red\">");
669                                cur.insert(offset, highlight);
670                                offset += 24 + error_marker.size() - 1;
671                                cur.insert(offset, wxT("</span>"));
672                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
673                                // Show "warning" marker in orange.
674                                highlight = wxT("<span style=\"color:orange\">");
675                                cur.insert(offset, highlight);
676                                offset += 27 + warning_marker.size() - 1;
677                                cur.insert(offset, wxT("</span>"));
678                            } else {
679                                highlight = NULL;
680                            }
681
682                            ++link_count;
683                        }
684                    }
685
686                    // Save the scrollbar positions.
687                    int scroll_x = 0, scroll_y = 0;
688                    GetViewStart(&scroll_x, &scroll_y);
689
690                    cur += wxT("<br>\n");
691                    AppendToPage(cur);
692
693                    if (!link_count) {
694                        // Auto-scroll the window until we've reported a
695                        // warning or error.
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                    }
708
709                    cur.clear();
710                    break;
711                }
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 '"':
722                    cur += wxT("&#34;");
723                    continue;
724                default:
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.
733                    if (ch >= 128) {
734                        cur += wxString::Format(wxT("&#%u;"), ch);
735                    } else {
736                        cur += (char)ch;
737                    }
738#endif
739            }
740        }
741
742        size_t left = end - p;
743        end = buf + left;
744        if (left) memmove(buf, p, left);
745        Update();
746        return;
747    }
748
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
758    if (e.len <= 0 && buf != end) {
759        // Truncated UTF-8 sequence.
760        cur += badutf8_html;
761    }
762    if (!cur.empty()) {
763        cur += "<br>\n";
764        AppendToPage("<hr>" + cur);
765    }
766
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,
771                                  wmsg(/*&Save Log*/446).c_str()));
772    wxEndBusyCursor();
773    delete cavern_out;
774    cavern_out = NULL;
775    if (e.len < 0) {
776        /* Negative length indicates non-zero exit status from cavern. */
777        /* TRANSLATORS: Label for button in aven’s cavern log window which
778         * causes the survey data to be reprocessed. */
779        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
780                                      (int)LOG_REPROCESS,
781                                      wmsg(/*&Reprocess*/184).c_str()));
782        return;
783    }
784    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
785                                  (int)LOG_REPROCESS,
786                                  wmsg(/*&Reprocess*/184).c_str()));
787    AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
788    Update();
789    init_done = false;
790
791    {
792        wxString file3d(filename, 0, filename.length() - 3);
793        file3d.append(wxT("3d"));
794        if (!mainfrm->LoadData(file3d, survey)) {
795            return;
796        }
797    }
798
799    if (link_count == 0) {
800        wxCommandEvent dummy;
801        OnOK(dummy);
802    }
803}
804
805void
806CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
807{
808    CavernOutputEvent * e = new CavernOutputEvent();
809    // Zero length indicates successful exit, negative length unsuccessful exit.
810    e->len = (evt.GetExitCode() == 0 ? 0 : -1);
811    QueueEvent(e);
812}
813
814void
815CavernLogWindow::OnReprocess(wxCommandEvent &)
816{
817    process(filename);
818}
819
820void
821CavernLogWindow::OnSave(wxCommandEvent &)
822{
823    wxString filelog(filename, 0, filename.length() - 3);
824    filelog += wxT("log");
825    AvenAllowOnTop ontop(mainfrm);
826#ifdef __WXMOTIF__
827    wxString ext(wxT("*.log"));
828#else
829    /* TRANSLATORS: Log files from running cavern (extension .log) */
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();
838    FILE * fh_log = wxFopen(filelog, wxT("w"));
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
847void
848CavernLogWindow::OnOK(wxCommandEvent &)
849{
850    if (init_done) {
851        mainfrm->HideLog(this);
852    } else {
853        mainfrm->InitialiseAfterLoad(filename, survey);
854        init_done = true;
855    }
856}
Note: See TracBrowser for help on using the repository browser.