source: git/src/cavernlog.cc

v1.4.8
Last change on this file was 4b40a9c, checked in by Olly Betts <olly@…>, 41 hours ago

Add missing osalloc.h include for mingw

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