source: git/src/cavernlog.cc @ f9f0f0c

RELEASE/1.2debug-cidebug-ci-sanitisersstereowalls-data
Last change on this file since f9f0f0c was f9f0f0c, checked in by Olly Betts <olly@…>, 8 years ago

Fix colouring of error/warning without column

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