source: git/src/cavernlog.cc @ c606770

walls-datawalls-data-hanging-as-warning
Last change on this file since c606770 was 4c83f84, checked in by Olly Betts <olly@…>, 13 days ago

Don't check HAVE_CONFIG_H in most cases

This check is only useful for img.c, which is intended to be usable
outside of Survex (and had fallbacks for functions which may not be
available which will get used if built in a non-autotools project).
For all the other source files it's just useless boilerplate.

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