source: git/src/cavernlog.cc @ 8c20717

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

Disable selection of text in cavern log window

You can't currently copy it to the clipboard, so until that's
implemented it seems better to disable the ability to select it.

  • Property mode set to 100644
File size: 18.7 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, wxID_ANY, wxDefaultPosition, wxDefaultSize,
243                   wxHW_DEFAULT_STYLE|wxHW_NO_SELECTION),
244      mainfrm(mainfrm_), cavern_out(NULL),
245      link_count(0), end(buf), init_done(false), survey(survey_)
246#ifdef CAVERNLOG_USE_THREADS
247      , thread(NULL)
248#endif
249{
250    int fsize = parent->GetFont().GetPointSize();
251    int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
252    SetFonts(wxString(), wxString(), sizes);
253}
254
255CavernLogWindow::~CavernLogWindow()
256{
257#ifdef CAVERNLOG_USE_THREADS
258    if (thread) stop_thread();
259#endif
260    if (cavern_out) {
261        wxEndBusyCursor();
262        cavern_out->Detach();
263    }
264}
265
266#ifdef CAVERNLOG_USE_THREADS
267void
268CavernLogWindow::stop_thread()
269{
270    // Killing the subprocess by its pid is theoretically racy, but in practice
271    // it's not going to cause issues, and it's all the wxProcess API seems to
272    // allow us to do.  If we don't kill the subprocess, we need to wait for it
273    // to write out some output - there seems to be no way to do the equivalent
274    // of select() with a timeout on a a wxInputStream.
275    //
276    // The only alternative to this seems to be to do:
277    //
278    //     while (!s.CanRead()) {
279    //         if (TestDestroy()) return (wxThread::ExitCode)0;
280    //         wxMilliSleep(N);
281    //     }
282    //
283    // But that makes the log window update sluggishly, and we're using a
284    // worker thread precisely to try to avoid having to do dumb stuff like
285    // this.
286    wxProcess::Kill(cavern_out->GetPid());
287
288    {
289        wxCriticalSectionLocker enter(thread_lock);
290        if (thread) {
291            wxThreadError res;
292#if wxCHECK_VERSION(2,9,2)
293            res = thread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
294#else
295            res = thread->Delete();
296#endif
297            if (res != wxTHREAD_NO_ERROR) {
298                // FIXME
299            }
300        }
301    }
302
303    // Wait for thread to complete.
304    while (true) {
305        {
306            wxCriticalSectionLocker enter(thread_lock);
307            if (!thread) break;
308        }
309        wxMilliSleep(1);
310    }
311}
312
313void
314CavernLogWindow::OnClose(wxCloseEvent &)
315{
316    if (thread) stop_thread();
317    Destroy();
318}
319#endif
320
321void
322CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
323{
324    wxString href = link.GetHref();
325    wxString title = link.GetTarget();
326    size_t colon2 = href.rfind(wxT(':'));
327    if (colon2 == wxString::npos)
328        return;
329    size_t colon = href.rfind(wxT(':'), colon2 - 1);
330    if (colon == wxString::npos)
331        return;
332#ifdef __WXMSW__
333    wxString cmd = wxT("notepad $f");
334#elif defined __WXMAC__
335    wxString cmd = wxT("open -t $f");
336#else
337    wxString cmd = wxT("x-terminal-emulator -e vim +'call cursor($l,$c)' $f");
338    // wxString cmd = wxT("gedit -b $f +$l:$c $f");
339    // wxString cmd = wxT("x-terminal-emulator -e emacs +$l $f");
340    // wxString cmd = wxT("x-terminal-emulator -e nano +$l $f");
341    // wxString cmd = wxT("x-terminal-emulator -e jed -g $l $f");
342#endif
343    wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
344    if (p) {
345        cmd = p;
346        if (!cmd.find(wxT("$f"))) {
347            cmd += wxT(" $f");
348        }
349    }
350    size_t i = 0;
351    while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
352        if (++i >= cmd.size()) break;
353        switch ((int)cmd[i]) {
354            case wxT('$'):
355                cmd.erase(i, 1);
356                break;
357            case wxT('f'): {
358                wxString f = escape_for_shell(href.substr(0, colon), true);
359                cmd.replace(i - 1, 2, f);
360                i += f.size() - 1;
361                break;
362            }
363            case wxT('t'): {
364                wxString t = escape_for_shell(title);
365                cmd.replace(i - 1, 2, t);
366                i += t.size() - 1;
367                break;
368            }
369            case wxT('l'): {
370                wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
371                cmd.replace(i - 1, 2, l);
372                i += l.size() - 1;
373                break;
374            }
375            case wxT('c'): {
376                wxString l;
377                if (colon2 >= href.size() - 1)
378                    l = wxT("0");
379                else
380                    l = escape_for_shell(href.substr(colon2 + 1));
381                cmd.replace(i - 1, 2, l);
382                i += l.size() - 1;
383                break;
384            }
385            default:
386                ++i;
387        }
388    }
389
390    if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
391        return;
392
393    wxString m;
394    // TRANSLATORS: %s is replaced by the command we attempted to run.
395    m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
396    m += wxT(" (");
397    m += wxString(strerror(errno), wxConvUTF8);
398    m += wxT(')');
399    wxGetApp().ReportError(m);
400}
401
402void
403CavernLogWindow::process(const wxString &file)
404{
405    SetPage(wxString());
406#ifdef CAVERNLOG_USE_THREADS
407    if (thread) stop_thread();
408#endif
409    if (cavern_out) {
410        cavern_out->Detach();
411        cavern_out = NULL;
412    } else {
413        wxBeginBusyCursor();
414    }
415
416    SetFocus();
417    filename = file;
418
419    link_count = 0;
420    cur.resize(0);
421    log_txt.resize(0);
422
423#ifdef __WXMSW__
424    SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
425#else
426    setenv("SURVEX_UTF8", "1", 1);
427#endif
428
429    wxString escaped_file = escape_for_shell(file, true);
430    wxString cmd = get_command_path(L"cavern");
431    cmd = escape_for_shell(cmd, false);
432    cmd += wxT(" -o ");
433    cmd += escaped_file;
434    cmd += wxT(' ');
435    cmd += escaped_file;
436
437    cavern_out = wxProcess::Open(cmd);
438    if (!cavern_out) {
439        wxString m;
440        m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
441        m += wxT(" (");
442        m += wxString(strerror(errno), wxConvUTF8);
443        m += wxT(')');
444        wxGetApp().ReportError(m);
445        return;
446    }
447
448    // We want to receive the wxProcessEvent when cavern exits.
449    cavern_out->SetNextHandler(this);
450
451#ifdef CAVERNLOG_USE_THREADS
452    thread = new CavernThread(this, cavern_out->GetInputStream());
453    if (thread->Run() != wxTHREAD_NO_ERROR) {
454        wxGetApp().ReportError(wxT("Thread failed to start"));
455        delete thread;
456        thread = NULL;
457    }
458#endif
459}
460
461void
462CavernLogWindow::OnCavernOutput(wxCommandEvent & e_)
463{
464    CavernOutputEvent & e = (CavernOutputEvent&)e_;
465
466    if (e.len > 0) {
467        ssize_t n = e.len;
468        if (size_t(n) > sizeof(buf) - (end - buf)) abort();
469        memcpy(end, e.buf, n);
470        log_txt.append((const char *)end, n);
471        end += n;
472
473        const unsigned char * p = buf;
474
475        while (p != end) {
476            int ch = *p++;
477            if (ch >= 0x80) {
478                // Decode multi-byte UTF-8 sequence.
479                if (ch < 0xc0) {
480                    // Invalid UTF-8 sequence.
481                    goto bad_utf8;
482                } else if (ch < 0xe0) {
483                    /* 2 byte sequence */
484                    if (p == end) {
485                        // Incomplete UTF-8 sequence - try to read more.
486                        break;
487                    }
488                    int ch1 = *p++;
489                    if ((ch1 & 0xc0) != 0x80) {
490                        // Invalid UTF-8 sequence.
491                        goto bad_utf8;
492                    }
493                    ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
494                } else if (ch < 0xf0) {
495                    /* 3 byte sequence */
496                    if (end - p <= 1) {
497                        // Incomplete UTF-8 sequence - try to read more.
498                        break;
499                    }
500                    int ch1 = *p++;
501                    ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
502                    if ((ch1 & 0xc0) != 0x80) {
503                        // Invalid UTF-8 sequence.
504                        goto bad_utf8;
505                    }
506                    int ch2 = *p++;
507                    if ((ch2 & 0xc0) != 0x80) {
508                        // Invalid UTF-8 sequence.
509                        goto bad_utf8;
510                    }
511                    ch |= (ch2 & 0x3f);
512                } else {
513                    // Overlong UTF-8 sequence.
514                    goto bad_utf8;
515                }
516            }
517
518            if (false) {
519bad_utf8:
520                // Resync to next byte which starts a UTF-8 sequence.
521                while (p != end) {
522                    if (*p < 0x80 || (*p >= 0xc0 && *p < 0xf0)) break;
523                    ++p;
524                }
525                cur += badutf8_html;
526                continue;
527            }
528
529            switch (ch) {
530                case '\r':
531                    // Ignore.
532                    break;
533                case '\n': {
534                    if (cur.empty()) continue;
535#ifndef __WXMSW__
536                    size_t colon = cur.find(':');
537#else
538                    // If the path is "C:\path\to\file.svx" then don't split at the
539                    // : after the drive letter!  FIXME: better to look for ": "?
540                    size_t colon = cur.find(':', 2);
541#endif
542                    if (colon != wxString::npos && colon < cur.size() - 2) {
543                        ++colon;
544                        size_t i = colon;
545                        while (i < cur.size() - 2 &&
546                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
547                            ++i;
548                        }
549                        if (i > colon && cur[i] == wxT(':') ) {
550                            colon = i;
551                            // Check for column number.
552                            while (++i < cur.size() - 2 &&
553                               cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
554                            bool have_column = (i > colon + 1 && cur[i] == wxT(':'));
555                            if (have_column) {
556                                colon = i;
557                            } else {
558                                // If there's no colon, include a trailing ':'
559                                // so that we can unambiguously split the href
560                                // value up into filename, line and column.
561                                ++colon;
562                            }
563                            wxString tag = wxT("<a href=\"");
564                            tag.append(cur, 0, colon);
565                            while (cur[++i] == wxT(' ')) { }
566                            tag += wxT("\" target=\"");
567                            wxString target(cur, i, wxString::npos);
568                            target.Replace(badutf8_html, badutf8);
569                            tag += target;
570                            tag += wxT("\">");
571                            cur.insert(0, tag);
572                            size_t offset = colon + tag.size();
573                            cur.insert(offset, wxT("</a>"));
574                            offset += 4 + 2;
575
576                            if (!have_column) --offset;
577
578                            static const wxString & error_marker = wmsg(/*error*/93) + ":";
579                            static const wxString & warning_marker = wmsg(/*warning*/4) + ":";
580
581                            if (cur.substr(offset, error_marker.size()) == error_marker) {
582                                // Show "error" marker in red.
583                                cur.insert(offset, wxT("<span style=\"color:red\">"));
584                                offset += 24 + error_marker.size() - 1;
585                                cur.insert(offset, wxT("</span>"));
586                            } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
587                                // Show "warning" marker in orange.
588                                cur.insert(offset, wxT("<span style=\"color:orange\">"));
589                                offset += 27 + warning_marker.size() - 1;
590                                cur.insert(offset, wxT("</span>"));
591                            }
592
593                            ++link_count;
594                        }
595                    }
596
597                    // Save the scrollbar positions.
598                    int scroll_x = 0, scroll_y = 0;
599                    GetViewStart(&scroll_x, &scroll_y);
600
601                    cur += wxT("<br>\n");
602                    AppendToPage(cur);
603
604                    if (!link_count) {
605                        // Auto-scroll the window until we've reported a
606                        // warning or error.
607                        int x, y;
608                        GetVirtualSize(&x, &y);
609                        int xs, ys;
610                        GetClientSize(&xs, &ys);
611                        y -= ys;
612                        int xu, yu;
613                        GetScrollPixelsPerUnit(&xu, &yu);
614                        Scroll(scroll_x, y / yu);
615                    } else {
616                        // Restore the scrollbar positions.
617                        Scroll(scroll_x, scroll_y);
618                    }
619
620                    cur.clear();
621                    break;
622                }
623                case '<':
624                    cur += wxT("&lt;");
625                    break;
626                case '>':
627                    cur += wxT("&gt;");
628                    break;
629                case '&':
630                    cur += wxT("&amp;");
631                    break;
632                case '"':
633                    cur += wxT("&#22;");
634                    continue;
635                default:
636                    if (ch >= 128) {
637                        cur += wxString::Format(wxT("&#%u;"), ch);
638                    } else {
639                        cur += (char)ch;
640                    }
641            }
642        }
643
644        size_t left = end - p;
645        end = buf + left;
646        if (left) memmove(buf, p, left);
647        Update();
648        return;
649    }
650
651    if (e.len <= 0 && buf != end) {
652        // Truncated UTF-8 sequence.
653        cur += badutf8_html;
654    }
655
656    /* TRANSLATORS: Label for button in aven’s cavern log window which
657     * allows the user to save the log to a file. */
658    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
659                                  (int)LOG_SAVE,
660                                  wmsg(/*Save Log*/446).c_str()));
661    wxEndBusyCursor();
662    delete cavern_out;
663    cavern_out = NULL;
664    if (e.len < 0) {
665        /* Negative length indicates non-zero exit status from cavern. */
666        /* TRANSLATORS: Label for button in aven’s cavern log window which
667         * causes the survey data to be reprocessed. */
668        AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
669                                      (int)LOG_REPROCESS,
670                                      wmsg(/*Reprocess*/184).c_str()));
671        return;
672    }
673    AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
674                                  (int)LOG_REPROCESS,
675                                  wmsg(/*Reprocess*/184).c_str()));
676    AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
677    Update();
678    init_done = false;
679
680    {
681        wxString file3d(filename, 0, filename.length() - 3);
682        file3d.append(wxT("3d"));
683        if (!mainfrm->LoadData(file3d, survey)) {
684            return;
685        }
686    }
687
688    if (link_count == 0) {
689        wxCommandEvent dummy;
690        OnOK(dummy);
691    }
692}
693
694void
695CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
696{
697    CavernOutputEvent * e = new CavernOutputEvent();
698    // Zero length indicates successful exit, negative length unsuccessful exit.
699    e->len = (evt.GetExitCode() == 0 ? 0 : -1);
700    QueueEvent(e);
701}
702
703void
704CavernLogWindow::OnReprocess(wxCommandEvent &)
705{
706    process(filename);
707}
708
709void
710CavernLogWindow::OnSave(wxCommandEvent &)
711{
712    wxString filelog(filename, 0, filename.length() - 3);
713    filelog += wxT("log");
714    AvenAllowOnTop ontop(mainfrm);
715#ifdef __WXMOTIF__
716    wxString ext(wxT("*.log"));
717#else
718    /* TRANSLATORS: Log files from running cavern (extension .log) */
719    wxString ext = wmsg(/*Log files*/447);
720    ext += wxT("|*.log");
721#endif
722    wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
723                     wxString(), filelog, ext,
724                     wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
725    if (dlg.ShowModal() != wxID_OK) return;
726    filelog = dlg.GetPath();
727    FILE * fh_log = wxFopen(filelog, wxT("w"));
728    if (!fh_log) {
729        wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
730        return;
731    }
732    fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
733    fclose(fh_log);
734}
735
736void
737CavernLogWindow::OnOK(wxCommandEvent &)
738{
739    if (init_done) {
740        mainfrm->HideLog(this);
741    } else {
742        mainfrm->InitialiseAfterLoad(filename, survey);
743        init_done = true;
744    }
745}
Note: See TracBrowser for help on using the repository browser.