source: git/src/cavernlog.cc @ 83eaae1

log-select
Last change on this file since 83eaae1 was 83eaae1, checked in by Olly Betts <olly@…>, 14 months ago

WIP

  • Property mode set to 100644
File size: 18.7 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.
200    if (s.empty() || s.find_first_of(wxT(" \"\t\n\v")) != s.npos) {
201        // Need to quote.
202        s.insert(0, wxT('"'));
203        for (size_t p = 1; p < s.size(); ++p) {
204            size_t backslashes = 0;
205            while (s[p] == wxT('\\')) {
206                ++backslashes;
207                if (++p == s.size()) {
208                    // Escape all the backslashes, since they're before
209                    // the closing quote we add below.
210                    s.append(backslashes, wxT('\\'));
211                    goto done;
212                }
213            }
214
215            if (s[p] == wxT('"')) {
216                // Escape any preceding backslashes and this quote.
217                s.insert(p, backslashes + 1, wxT('\\'));
218                p += backslashes + 1;
[eff69a7]219            }
[6bec10c]220        }
[faf83bee]221done:
222        s.append(wxT('"'));
[6bec10c]223    }
224#else
[faf83bee]225    size_t p = 0;
[6bec10c]226    if (protect_dash && !s.empty() && s[0u] == '-') {
227        // If the filename starts with a '-', protect it from being
228        // treated as an option by prepending "./".
[5627cbb]229        s.insert(0, wxT("./"));
[6bec10c]230        p = 2;
231    }
232    while (p < s.size()) {
233        // Exclude a few safe characters which are common in filenames
[8adbe49]234        if (!isalnum((unsigned char)s[p]) && strchr("/._-", s[p]) == NULL) {
[6baad4a]235            s.insert(p, 1, wxT('\\'));
[6bec10c]236            ++p;
237        }
238        ++p;
239    }
240#endif
241    return s;
242}
243
[549eb37]244wxString get_command_path(const wxChar * command_name)
245{
246#ifdef __WXMSW__
247    wxString cmd;
248    {
249        DWORD len = 256;
250        wchar_t *buf = NULL;
251        while (1) {
252            DWORD got;
253            buf = (wchar_t*)osrealloc(buf, len * 2);
254            got = GetModuleFileNameW(NULL, buf, len);
255            if (got < len) break;
256            len += len;
257        }
258        /* Strange Win32 nastiness - strip prefix "\\?\" if present */
259        wchar_t *start = buf;
260        if (wcsncmp(start, L"\\\\?\\", 4) == 0) start += 4;
261        wchar_t * slash = wcsrchr(start, L'\\');
262        if (slash) {
263            cmd.assign(start, slash - start + 1);
264        }
265        osfree(buf);
266    }
267#else
[ac20829b]268    wxString cmd = wxString::FromUTF8(msg_exepth());
[549eb37]269#endif
270    cmd += command_name;
271    return cmd;
272}
273
[d7b53e3]274CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
[ac20829b]275    : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
276                       wxFULL_REPAINT_ON_RESIZE),
[76bc864f]277      mainfrm(mainfrm_),
[ac20829b]278      survey(survey_),
279      timer(this)
[fb5887c]280{
[93ff5cc]281}
282
[8991d7f]283CavernLogWindow::~CavernLogWindow()
284{
[ac20829b]285    timer.Stop();
[c1144fe]286    if (cavern_out) {
287        wxEndBusyCursor();
[15ba0b5]288        cavern_out->Detach();
[c1144fe]289    }
[8991d7f]290}
291
[fc626ae]292void
[ac20829b]293CavernLogWindow::OnMouseMove(wxMouseEvent& e)
[fc626ae]294{
[ac20829b]295    const auto& pos = e.GetPosition();
296    int fsize = GetFont().GetPixelSize().GetHeight();
297    int scroll_x = 0, scroll_y = 0;
298    GetViewStart(&scroll_x, &scroll_y);
299    unsigned line = pos.y / fsize + scroll_y;
300    unsigned x = pos.x + scroll_x * fsize - fsize / 2;
301    if (line < line_info.size() && x <= line_info[line].link_pixel_width) {
302        SetCursor(wxCursor(wxCURSOR_HAND));
303    } else {
304        SetCursor(wxNullCursor);
[fc626ae]305    }
[83eaae1]306    if (!Dragging()) {
307        return;
308    }
309    if (line >= line_info.size()) { print "after end\n"; return; }
310    if (x > line_info[line].
[fc626ae]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.