source: git/src/cavernlog.cc @ 9695dfe

stereo-2025warn-only-for-hanging-survey
Last change on this file since 9695dfe was 8204040, checked in by Olly Betts <olly@…>, 12 months ago

Protect filenames which start with a dash

This applies when loading files from the cavern log window in aven.
We weren't doing this protecting on Microsoft Windows on the
assumption that it isn't needed there because the filenames will be
fully qualified, but that may not be the case if aven if run by hand
from the command line or from some other launcher.

  • Property mode set to 100644
File size: 18.8 KB
RevLine 
[6bec10c]1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
[ac20829b]4 * Copyright (C) 2005-2024 Olly Betts
[6bec10c]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
[4c83f84]21#include <config.h>
[6bec10c]22
23#include "aven.h"
24#include "cavernlog.h"
25#include "filename.h"
[fb5887c]26#include "mainfrm.h"
[6bec10c]27#include "message.h"
[4b40a9c]28#include "osalloc.h"
[6bec10c]29
[baeae66]30#include <errno.h>
[6bec10c]31#include <stdio.h>
[a90632c]32#include <stdlib.h>
[6bec10c]33
34#include <sys/time.h>
35#include <sys/types.h>
36#include <unistd.h>
37
[15ba0b5]38#include <wx/process.h>
39
[cc2d7ad]40#define GVIM_COMMAND "gvim +'call cursor($l,$c)' $f"
41#define VIM_COMMAND "x-terminal-emulator -e vim +'call cursor($l,$c)' $f"
[81d94a4]42#define NVIM_COMMAND "x-terminal-emulator -e nvim +'call cursor($l,$c)' $f"
[cc2d7ad]43#define GEDIT_COMMAND "gedit $f +$l:$c"
44// Pluma currently ignores the column, but include it assuming some future
45// version will add support.
46#define PLUMA_COMMAND "pluma +$l:$c $f"
47#define EMACS_COMMAND "x-terminal-emulator -e emacs +$l:$c $f"
48#define NANO_COMMAND "x-terminal-emulator -e nano +$l,$c $f"
49#define JED_COMMAND "x-terminal-emulator -e jed $f -g $l"
50#define KATE_COMMAND "kate -l $l -c $c $f"
51
52#ifdef __WXMSW__
53# define DEFAULT_EDITOR_COMMAND "notepad $f"
54#elif defined __WXMAC__
55# define DEFAULT_EDITOR_COMMAND "open -t $f"
56#else
57# define DEFAULT_EDITOR_COMMAND VIM_COMMAND
58#endif
59
[0e81a88]60enum { LOG_REPROCESS = 1234, LOG_SAVE = 1235 };
[fb5887c]61
[e768f29]62static const wxString badutf8_html(
63    wxT("<span style=\"color:white;background-color:red;\">&#xfffd;</span>"));
64static const wxString badutf8(wxUniChar(0xfffd));
65
[ac20829b]66// New event type for signalling cavern output to process.
67wxDEFINE_EVENT(EVT_CAVERN_OUTPUT, wxCommandEvent);
[fc626ae]68
[ac20829b]69void
70CavernLogWindow::CheckForOutput(bool immediate)
71{
72    timer.Stop();
73    if (cavern_out == NULL) return;
[fc626ae]74
[ac20829b]75    wxInputStream * in = cavern_out->GetInputStream();
[fc626ae]76
[ac20829b]77    if (!in->CanRead()) {
78        timer.StartOnce();
79        return;
[fc626ae]80    }
81
[ac20829b]82    size_t real_size = log_txt.size();
83    size_t allow = 1024;
84    log_txt.resize(real_size + allow);
85    in->Read(&log_txt[real_size], allow);
86    size_t n = in->LastRead();
87    log_txt.resize(real_size + n);
88    if (n) {
89        if (immediate) {
90            ProcessCavernOutput();
91        } else {
92            QueueEvent(new wxCommandEvent(EVT_CAVERN_OUTPUT));
93        }
[fc626ae]94    }
[ac20829b]95}
[fc626ae]96
[ac20829b]97int
98CavernLogWindow::OnPaintButton(wxButton* b, int x)
[fc626ae]99{
[ac20829b]100    if (b) {
101        x -= 4;
102        const wxSize& bsize = b->GetSize();
103        x -= bsize.x;
104        b->SetSize(x, 4, bsize.x, bsize.y);
105        x -= 4;
[15ba0b5]106    }
[ac20829b]107    return x;
[15ba0b5]108}
109
110void
[ac20829b]111CavernLogWindow::OnPaint(wxPaintEvent&)
[15ba0b5]112{
[ac20829b]113    wxPaintDC dc(this);
114    wxFont font = dc.GetFont();
115    wxFont bold_font = font.Bold();
116    wxFont underlined_font = font.Underlined();
117    const wxRegion& region = GetUpdateRegion();
118    const wxRect& rect = region.GetBox();
119    int scroll_x = 0, scroll_y = 0;
120    GetViewStart(&scroll_x, &scroll_y);
121    int fsize = dc.GetFont().GetPixelSize().GetHeight();
122    int limit = min((rect.y + rect.height + fsize - 1) / fsize + scroll_y, int(line_info.size()) - 1);
123    for (int i = max(rect.y / fsize, scroll_y); i <= limit ; ++i) {
124        LineInfo& info = line_info[i];
125        // Leave a small margin to the left.
126        int x = fsize / 2 - scroll_x * fsize;
127        int y = (i - scroll_y) * fsize;
128        unsigned offset = info.start_offset;
129        unsigned len = info.len;
130        if (info.link_len) {
131            dc.SetFont(underlined_font);
132            dc.SetTextForeground(wxColour(192, 0, 192));
133            wxString link = wxString::FromUTF8(&log_txt[offset], info.link_len);
134            offset += info.link_len;
135            len -= info.link_len;
136            dc.DrawText(link, x, y);
137            x += info.link_pixel_width;
138            dc.SetFont(font);
[fc626ae]139        }
[ac20829b]140        if (info.colour_len) {
141            dc.SetTextForeground(*wxBLACK);
142            {
143                size_t s_len = info.start_offset + info.colour_start - offset;
144                wxString s = wxString::FromUTF8(&log_txt[offset], s_len);
145                offset += s_len;
146                len -= s_len;
147                dc.DrawText(s, x, y);
148                x += dc.GetTextExtent(s).GetWidth();
149            }
150            switch (info.colour) {
151                case LOG_ERROR:
152                    dc.SetTextForeground(*wxRED);
153                    break;
154                case LOG_WARNING:
155                    dc.SetTextForeground(wxColour(0xf2, 0x8C, 0x28));
156                    break;
157                case LOG_INFO:
158                    dc.SetTextForeground(*wxBLUE);
159                    break;
160            }
161            dc.SetFont(bold_font);
162            wxString d = wxString::FromUTF8(&log_txt[offset], info.colour_len);
163            offset += info.colour_len;
164            len -= info.colour_len;
165            dc.DrawText(d, x, y);
166            x += dc.GetTextExtent(d).GetWidth();
167            dc.SetFont(font);
168        }
169        dc.SetTextForeground(*wxBLACK);
170        dc.DrawText(wxString::FromUTF8(&log_txt[offset], len), x, y);
[15ba0b5]171    }
[ac20829b]172    int x = GetClientSize().x;
173    x = OnPaintButton(ok_button, x);
174    x = OnPaintButton(reprocess_button, x);
175    OnPaintButton(save_button, x);
[fc626ae]176}
177
[ac20829b]178BEGIN_EVENT_TABLE(CavernLogWindow, wxScrolledWindow)
[81e1aa4]179    EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
[0e81a88]180    EVT_BUTTON(LOG_SAVE, CavernLogWindow::OnSave)
[fb5887c]181    EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
[ac20829b]182    EVT_COMMAND(wxID_ANY, EVT_CAVERN_OUTPUT, CavernLogWindow::OnCavernOutput)
[8991d7f]183    EVT_IDLE(CavernLogWindow::OnIdle)
[ac20829b]184    EVT_TIMER(wxID_ANY, CavernLogWindow::OnTimer)
185    EVT_PAINT(CavernLogWindow::OnPaint)
186    EVT_MOTION(CavernLogWindow::OnMouseMove)
187    EVT_LEFT_UP(CavernLogWindow::OnLinkClicked)
[15ba0b5]188    EVT_END_PROCESS(wxID_ANY, CavernLogWindow::OnEndProcess)
[fb5887c]189END_EVENT_TABLE()
190
[549eb37]191wxString escape_for_shell(wxString s, bool protect_dash)
[6bec10c]192{
193#ifdef __WXMSW__
[faf83bee]194    // Correct quoting rules are insane:
195    //
[764fe32]196    // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
[faf83bee]197    //
198    // Thankfully wxExecute passes the command string to CreateProcess(), so
199    // at least we don't need to quote for cmd.exe too.
[8204040]200    if (protect_dash && !s.empty() && s[0u] == '-') {
201        // If the filename starts with a '-', protect it from being
202        // treated as an option by prepending ".\".
203        s.insert(0, wxT(".\\"));
204    }
[faf83bee]205    if (s.empty() || s.find_first_of(wxT(" \"\t\n\v")) != s.npos) {
206        // Need to quote.
207        s.insert(0, wxT('"'));
208        for (size_t p = 1; p < s.size(); ++p) {
209            size_t backslashes = 0;
210            while (s[p] == wxT('\\')) {
211                ++backslashes;
212                if (++p == s.size()) {
213                    // Escape all the backslashes, since they're before
214                    // the closing quote we add below.
215                    s.append(backslashes, wxT('\\'));
216                    goto done;
217                }
218            }
219
220            if (s[p] == wxT('"')) {
221                // Escape any preceding backslashes and this quote.
222                s.insert(p, backslashes + 1, wxT('\\'));
223                p += backslashes + 1;
[eff69a7]224            }
[6bec10c]225        }
[faf83bee]226done:
227        s.append(wxT('"'));
[6bec10c]228    }
229#else
[faf83bee]230    size_t p = 0;
[6bec10c]231    if (protect_dash && !s.empty() && s[0u] == '-') {
232        // If the filename starts with a '-', protect it from being
233        // treated as an option by prepending "./".
[5627cbb]234        s.insert(0, wxT("./"));
[6bec10c]235        p = 2;
236    }
237    while (p < s.size()) {
238        // Exclude a few safe characters which are common in filenames
[8adbe49]239        if (!isalnum((unsigned char)s[p]) && strchr("/._-", s[p]) == NULL) {
[6baad4a]240            s.insert(p, 1, wxT('\\'));
[6bec10c]241            ++p;
242        }
243        ++p;
244    }
245#endif
246    return s;
247}
248
[549eb37]249wxString get_command_path(const wxChar * command_name)
250{
251#ifdef __WXMSW__
252    wxString cmd;
253    {
254        DWORD len = 256;
255        wchar_t *buf = NULL;
256        while (1) {
257            DWORD got;
258            buf = (wchar_t*)osrealloc(buf, len * 2);
259            got = GetModuleFileNameW(NULL, buf, len);
260            if (got < len) break;
261            len += len;
262        }
263        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
264        wchar_t *start = buf;
265        if (wcsncmp(start, L"\\\\?\\", 4) == 0) start += 4;
266        wchar_t * slash = wcsrchr(start, L'\\');
267        if (slash) {
268            cmd.assign(start, slash - start + 1);
269        }
270        osfree(buf);
271    }
272#else
[ac20829b]273    wxString cmd = wxString::FromUTF8(msg_exepth());
[549eb37]274#endif
275    cmd += command_name;
276    return cmd;
277}
278
[d7b53e3]279CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
[ac20829b]280    : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
281                       wxFULL_REPAINT_ON_RESIZE),
[76bc864f]282      mainfrm(mainfrm_),
[ac20829b]283      survey(survey_),
284      timer(this)
[fb5887c]285{
[93ff5cc]286}
287
[8991d7f]288CavernLogWindow::~CavernLogWindow()
289{
[ac20829b]290    timer.Stop();
[c1144fe]291    if (cavern_out) {
292        wxEndBusyCursor();
[15ba0b5]293        cavern_out->Detach();
[c1144fe]294    }
[8991d7f]295}
296
[fc626ae]297void
[ac20829b]298CavernLogWindow::OnMouseMove(wxMouseEvent& e)
[fc626ae]299{
[ac20829b]300    const auto& pos = e.GetPosition();
301    int fsize = GetFont().GetPixelSize().GetHeight();
302    int scroll_x = 0, scroll_y = 0;
303    GetViewStart(&scroll_x, &scroll_y);
304    unsigned line = pos.y / fsize + scroll_y;
305    unsigned x = pos.x + scroll_x * fsize - fsize / 2;
306    if (line < line_info.size() && x <= line_info[line].link_pixel_width) {
307        SetCursor(wxCursor(wxCURSOR_HAND));
308    } else {
309        SetCursor(wxNullCursor);
[fc626ae]310    }
311}
312
313void
[ac20829b]314CavernLogWindow::OnLinkClicked(wxMouseEvent& e)
[6bec10c]315{
[ac20829b]316    const auto& pos = e.GetPosition();
317    int fsize = GetFont().GetPixelSize().GetHeight();
318    int scroll_x = 0, scroll_y = 0;
319    GetViewStart(&scroll_x, &scroll_y);
320    unsigned line = pos.y / fsize + scroll_y;
321    unsigned x = pos.x + scroll_x * fsize - fsize / 2;
322    if (!(line < line_info.size() && x <= line_info[line].link_pixel_width))
[3d3fb6c]323        return;
[ac20829b]324
325    const char* cur = &log_txt[line_info[line].start_offset];
326    size_t link_len = line_info[line].link_len;
327    size_t colon = link_len;
328    while (colon > 1 && (unsigned)(cur[--colon] - '0') <= 9) { }
329    size_t colon2 = colon;
330    while (colon > 1 && (unsigned)(cur[--colon] - '0') <= 9) { }
331    if (cur[colon] != ':') {
332        colon = colon2;
333        colon2 = link_len;
334    }
335
[cc2d7ad]336    wxString cmd;
[b8ba399]337    wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
[3d3fb6c]338    if (p) {
[b8ba399]339        cmd = p;
[3d3fb6c]340        if (!cmd.find(wxT("$f"))) {
341            cmd += wxT(" $f");
[6bec10c]342        }
[cc2d7ad]343    } else {
344        p = wxGetenv(wxT("VISUAL"));
345        if (!p) p = wxGetenv(wxT("EDITOR"));
346        if (!p) {
347            cmd = wxT(DEFAULT_EDITOR_COMMAND);
348        } else {
349            cmd = p;
350            if (cmd == "gvim") {
351                cmd = wxT(GVIM_COMMAND);
352            } else if (cmd == "vim") {
353                cmd = wxT(VIM_COMMAND);
[81d94a4]354            } else if (cmd == "nvim") {
355                cmd = wxT(NVIM_COMMAND);
[cc2d7ad]356            } else if (cmd == "gedit") {
357                cmd = wxT(GEDIT_COMMAND);
358            } else if (cmd == "pluma") {
359                cmd = wxT(PLUMA_COMMAND);
360            } else if (cmd == "emacs") {
361                cmd = wxT(EMACS_COMMAND);
362            } else if (cmd == "nano") {
363                cmd = wxT(NANO_COMMAND);
364            } else if (cmd == "jed") {
365                cmd = wxT(JED_COMMAND);
366            } else if (cmd == "kate") {
367                cmd = wxT(KATE_COMMAND);
368            } else {
369                // Escape any $.
370                cmd.Replace(wxT("$"), wxT("$$"));
371                cmd += wxT(" $f");
372            }
373        }
[3d3fb6c]374    }
375    size_t i = 0;
376    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
377        if (++i >= cmd.size()) break;
378        switch ((int)cmd[i]) {
379            case wxT('$'):
380                cmd.erase(i, 1);
381                break;
382            case wxT('f'): {
[ac20829b]383                wxString f = escape_for_shell(wxString(cur, colon), true);
[3d3fb6c]384                cmd.replace(i - 1, 2, f);
385                i += f.size() - 1;
386                break;
387            }
388            case wxT('l'): {
[ac20829b]389                wxString l = escape_for_shell(wxString(cur + colon + 1, colon2 - colon - 1));
[3d3fb6c]390                cmd.replace(i - 1, 2, l);
391                i += l.size() - 1;
392                break;
[1d71195]393            }
[3d3fb6c]394            case wxT('c'): {
395                wxString l;
[ac20829b]396                if (colon2 == link_len)
[3d3fb6c]397                    l = wxT("0");
398                else
[ac20829b]399                    l = escape_for_shell(wxString(cur + colon2 + 1, link_len - colon2 - 1));
[3d3fb6c]400                cmd.replace(i - 1, 2, l);
401                i += l.size() - 1;
402                break;
403            }
404            default:
405                ++i;
[6bec10c]406        }
[3d3fb6c]407    }
[faf83bee]408
409    if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
[3d3fb6c]410        return;
[faf83bee]411
[3d3fb6c]412    wxString m;
[736f7df]413    // TRANSLATORS: %s is replaced by the command we attempted to run.
[3d3fb6c]414    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
415    m += wxT(" (");
[ac20829b]416    m += wxString::FromUTF8(strerror(errno));
[3d3fb6c]417    m += wxT(')');
418    wxGetApp().ReportError(m);
[6bec10c]419}
420
[8991d7f]421void
[6bec10c]422CavernLogWindow::process(const wxString &file)
423{
[ac20829b]424    timer.Stop();
[c1144fe]425    if (cavern_out) {
[15ba0b5]426        cavern_out->Detach();
[15033fd]427        cavern_out = NULL;
[c1144fe]428    } else {
429        wxBeginBusyCursor();
430    }
431
[fb5887c]432    SetFocus();
433    filename = file;
434
[76bc864f]435    info_count = 0;
[8991d7f]436    link_count = 0;
[0e81a88]437    log_txt.resize(0);
[ac20829b]438    line_info.resize(0);
439    // Reserve enough that we won't need to grow the allocations in normal cases.
440    log_txt.reserve(16384);
441    line_info.reserve(256);
442    ptr = 0;
443    save_button = nullptr;
444    reprocess_button = nullptr;
445    ok_button = nullptr;
446    DestroyChildren();
447    SetVirtualSize(0, 0);
[0e81a88]448
[6bec10c]449#ifdef __WXMSW__
[15322f2]450    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
[6bec10c]451#else
[06b1227]452    setenv("SURVEX_UTF8", "1", 1);
[6bec10c]453#endif
[93ff5cc]454
[6bec10c]455    wxString escaped_file = escape_for_shell(file, true);
[549eb37]456    wxString cmd = get_command_path(L"cavern");
[9e50f755]457    cmd = escape_for_shell(cmd, false);
[5627cbb]458    cmd += wxT(" -o ");
[6bec10c]459    cmd += escaped_file;
[5627cbb]460    cmd += wxT(' ');
[6bec10c]461    cmd += escaped_file;
462
[15ba0b5]463    cavern_out = wxProcess::Open(cmd);
[6bec10c]464    if (!cavern_out) {
[5627cbb]465        wxString m;
[3d3fb6c]466        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
[5627cbb]467        m += wxT(" (");
[ac20829b]468        m += wxString::FromUTF8(strerror(errno));
[5627cbb]469        m += wxT(')');
[6bec10c]470        wxGetApp().ReportError(m);
[8991d7f]471        return;
[6bec10c]472    }
473
[15ba0b5]474    // We want to receive the wxProcessEvent when cavern exits.
475    cavern_out->SetNextHandler(this);
[40b02e8]476
[ac20829b]477    // Check for output after 500ms if we don't get an idle event sooner.
478    timer.StartOnce(500);
[fc626ae]479}
480
481void
[ac20829b]482CavernLogWindow::ProcessCavernOutput()
[fc626ae]483{
[ac20829b]484    // ptr gives the start of the first line we've not yet processed.
485
486    size_t nl;
487    while ((nl = log_txt.find('\n', ptr)) != std::string::npos) {
488        if (nl == ptr || (nl - ptr == 1 && log_txt[ptr] == '\r')) {
489            // Don't show empty lines in the window.
490            ptr = nl + 1;
491            continue;
492        }
493        size_t line_len = nl - ptr - (log_txt[nl - 1] == '\r');
494        // FIXME: Avoid copy, use string_view?
495        string cur(log_txt, ptr, line_len);
496        if (log_txt[ptr] == ' ') {
497            if (expecting_caret_line) {
498                // FIXME: Check the line is only space, `^` and `~`?
499                // Otherwise an error without caret info followed
500                // by an error which contains a '^' gets
501                // mishandled...
502                size_t caret = cur.rfind('^');
503                if (caret != wxString::npos) {
504                    size_t tilde = cur.rfind('~');
505                    if (tilde == wxString::npos || tilde < caret) {
506                        tilde = caret;
[b3ee5f5]507                    }
[ac20829b]508                    line_info.back().colour = line_info[line_info.size() - 2].colour;
509                    line_info.back().colour_start = caret;
510                    line_info.back().colour_len = tilde - caret + 1;
511                    expecting_caret_line = false;
512                    ptr = nl + 1;
513                    continue;
[6bec10c]514                }
515            }
[ac20829b]516            expecting_caret_line = true;
517        }
518        line_info.emplace_back(ptr);
519        line_info.back().len = line_len;
520        size_t colon = cur.find(": ");
521        if (colon != wxString::npos) {
522            size_t link_len = colon;
523            while (colon > 1 && (unsigned)(cur[--colon] - '0') <= 9) { }
524            if (cur[colon] == ':') {
525                line_info.back().link_len = link_len;
526
527                static string info_marker = string(msg(/*info*/485)) + ':';
528                static string warning_marker = string(msg(/*warning*/4)) + ':';
529                static string error_marker = string(msg(/*error*/93)) + ':';
530
531                size_t offset = link_len + 2;
532                if (cur.compare(offset, info_marker.size(), info_marker) == 0) {
533                    // Show "info" marker in blue.
534                    ++info_count;
535                    line_info.back().colour = LOG_INFO;
536                    line_info.back().colour_start = offset;
537                    line_info.back().colour_len = info_marker.size() - 1;
538                } else if (cur.compare(offset, warning_marker.size(), warning_marker) == 0) {
539                    // Show "warning" marker in orange.
540                    line_info.back().colour = LOG_WARNING;
541                    line_info.back().colour_start = offset;
542                    line_info.back().colour_len = warning_marker.size() - 1;
543                } else if (cur.compare(offset, error_marker.size(), error_marker) == 0) {
544                    // Show "error" marker in red.
545                    line_info.back().colour = LOG_ERROR;
546                    line_info.back().colour_start = offset;
547                    line_info.back().colour_len = error_marker.size() - 1;
[e768f29]548                }
[ac20829b]549                ++link_count;
[b3ee5f5]550            }
[6bec10c]551        }
[b3ee5f5]552
[ac20829b]553        int fsize = GetFont().GetPixelSize().GetHeight();
554        SetScrollRate(fsize, fsize);
555
556        auto& info = line_info.back();
557        info.link_pixel_width = GetTextExtent(wxString(&log_txt[ptr], info.link_len)).GetWidth();
558        auto rest_pixel_width = GetTextExtent(wxString(&log_txt[ptr + info.link_len], info.len - info.link_len)).GetWidth();
559        int width = max(GetVirtualSize().GetWidth(),
560                        int(fsize + info.link_pixel_width + rest_pixel_width));
561        int height = line_info.size();
562        SetVirtualSize(width, height * fsize);
563        if (!link_count) {
564            // Auto-scroll until the first diagnostic.
565            int scroll_x = 0, scroll_y = 0;
566            GetViewStart(&scroll_x, &scroll_y);
567            int xs, ys;
568            GetClientSize(&xs, &ys);
569            Scroll(scroll_x, line_info.size() * fsize - ys);
570        }
571        ptr = nl + 1;
[6bec10c]572    }
[ac20829b]573}
[8991d7f]574
[ac20829b]575void
576CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
577{
578    bool cavern_success = evt.GetExitCode() == 0;
[f207751]579
[ac20829b]580    // Read and process any remaining buffered output.
581    wxInputStream* in = cavern_out->GetInputStream();
582    while (!in->Eof()) {
583        CheckForOutput(true);
[f207751]584    }
[fb5887c]585
[c1144fe]586    wxEndBusyCursor();
[ac20829b]587
[15ba0b5]588    delete cavern_out;
[8991d7f]589    cavern_out = NULL;
[ac20829b]590
591    // Initially place buttons off the right of the window - they get moved to
592    // the desired position by OnPaintButton().
593    wxPoint off_right(GetSize().x, 0);
594    /* TRANSLATORS: Label for button in aven’s cavern log window which
595     * allows the user to save the log to a file. */
596    save_button = new wxButton(this, LOG_SAVE, wmsg(/*&Save Log*/446), off_right);
597
598    /* TRANSLATORS: Label for button in aven’s cavern log window which
599     * causes the survey data to be reprocessed. */
600    reprocess_button = new wxButton(this, LOG_REPROCESS, wmsg(/*&Reprocess*/184), off_right);
601
602    if (cavern_success) {
603        ok_button = new wxButton(this, wxID_OK, wxString(), off_right);
604        ok_button->SetDefault();
605    }
606
607    Refresh();
608    if (!cavern_success) {
[8991d7f]609        return;
[6bec10c]610    }
[ac20829b]611
[d7b53e3]612    init_done = false;
[fb5887c]613
[15ba0b5]614    {
615        wxString file3d(filename, 0, filename.length() - 3);
616        file3d.append(wxT("3d"));
617        if (!mainfrm->LoadData(file3d, survey)) {
618            return;
619        }
[d7b53e3]620    }
[15ba0b5]621
[76bc864f]622    // Don't stay on log if there there are only "info" diagnostics.
623    if (link_count == info_count) {
[8991d7f]624        wxCommandEvent dummy;
625        OnOK(dummy);
[fb5887c]626    }
[15ba0b5]627}
628
[0e81a88]629void
[330cb03]630CavernLogWindow::OnReprocess(wxCommandEvent &)
[8991d7f]631{
632    process(filename);
633}
634
635void
[0e81a88]636CavernLogWindow::OnSave(wxCommandEvent &)
637{
638    wxString filelog(filename, 0, filename.length() - 3);
[ac20829b]639#ifdef __WXMSW__
640    // We need to consistently use `\` here.
641    filelog.Replace("/", "\\");
642#endif
[0e81a88]643    filelog += wxT("log");
644#ifdef __WXMOTIF__
645    wxString ext(wxT("*.log"));
646#else
[bb71423]647    /* TRANSLATORS: Log files from running cavern (extension .log) */
[0e81a88]648    wxString ext = wmsg(/*Log files*/447);
649    ext += wxT("|*.log");
650#endif
651    wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
652                     wxString(), filelog, ext,
653                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
654    if (dlg.ShowModal() != wxID_OK) return;
655    filelog = dlg.GetPath();
[3206c12]656    FILE * fh_log = wxFopen(filelog, wxT("w"));
[0e81a88]657    if (!fh_log) {
658        wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
659        return;
660    }
661    fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
662    fclose(fh_log);
663}
664
[fb5887c]665void
666CavernLogWindow::OnOK(wxCommandEvent &)
667{
[d7b53e3]668    if (init_done) {
669        mainfrm->HideLog(this);
670    } else {
[5e0b9f9d]671        mainfrm->InitialiseAfterLoad(filename, survey);
[d7b53e3]672        init_done = true;
673    }
[fb5887c]674}
Note: See TracBrowser for help on using the repository browser.