source: git/src/cavernlog.cc

Last change on this file was fd0e32a, checked in by Olly Betts <olly@…>, 3 months ago

Raise wxWidgets requirement to >= 3.2.0

Previously it was >= 3.0.0 but it is starting to become hard to
keep everything working with 3.0.0 and the time and effort seems
better directed to other things.

The last 3.0.x release was 3.0.5.1 released 2020-05-02.

wx3.2.0 was released 2022-07-06 and it seems everywhere with wxWidgets
packages upgraded to 3.2.x some time ago.

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