Reenable selection of text in cavern log window
[survex.git] / src / cavernlog.cc
blob29a60b5ffc6f0eb1173f474918b8e0b213722d6f
1 /* cavernlog.cc
2 * Run cavern inside an Aven window
4 * Copyright (C) 2005,2006,2010,2011,2012,2014,2015,2016 Olly Betts
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.
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.
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
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
25 #include "aven.h"
26 #include "cavernlog.h"
27 #include "filename.h"
28 #include "mainfrm.h"
29 #include "message.h"
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
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>
43 #include <wx/process.h>
45 #define GVIM_COMMAND "gvim +'call cursor($l,$c)' $f"
46 #define VIM_COMMAND "x-terminal-emulator -e vim +'call cursor($l,$c)' $f"
47 #define GEDIT_COMMAND "gedit $f +$l:$c"
48 // Pluma currently ignores the column, but include it assuming some future
49 // version will add support.
50 #define PLUMA_COMMAND "pluma +$l:$c $f"
51 #define EMACS_COMMAND "x-terminal-emulator -e emacs +$l:$c $f"
52 #define NANO_COMMAND "x-terminal-emulator -e nano +$l,$c $f"
53 #define JED_COMMAND "x-terminal-emulator -e jed $f -g $l"
54 #define KATE_COMMAND "kate -l $l -c $c $f"
56 #ifdef __WXMSW__
57 # define DEFAULT_EDITOR_COMMAND "notepad $f"
58 #elif defined __WXMAC__
59 # define DEFAULT_EDITOR_COMMAND "open -t $f"
60 #else
61 # define DEFAULT_EDITOR_COMMAND VIM_COMMAND
62 #endif
64 enum { LOG_REPROCESS = 1234, LOG_SAVE = 1235 };
66 static const wxString badutf8_html(
67 wxT("<span style=\"color:white;background-color:red;\">&#xfffd;</span>"));
68 static const wxString badutf8(wxUniChar(0xfffd));
70 // New event type for passing a chunk of cavern output from the worker thread
71 // to the main thread (or from the idle event handler if we're not using
72 // threads).
73 class CavernOutputEvent;
75 wxDEFINE_EVENT(wxEVT_CAVERN_OUTPUT, CavernOutputEvent);
77 class CavernOutputEvent : public wxEvent {
78 public:
79 char buf[1000];
80 int len;
81 CavernOutputEvent() : wxEvent(0, wxEVT_CAVERN_OUTPUT), len(0) { }
83 wxEvent * Clone() const {
84 CavernOutputEvent * e = new CavernOutputEvent();
85 e->len = len;
86 if (len > 0) memcpy(e->buf, buf, len);
87 return e;
91 #ifdef CAVERNLOG_USE_THREADS
92 class CavernThread : public wxThread {
93 protected:
94 virtual ExitCode Entry();
96 CavernLogWindow *handler;
98 wxInputStream * in;
100 public:
101 CavernThread(CavernLogWindow *handler_, wxInputStream * in_)
102 : wxThread(wxTHREAD_DETACHED), handler(handler_), in(in_) { }
104 ~CavernThread() {
105 wxCriticalSectionLocker enter(handler->thread_lock);
106 handler->thread = NULL;
110 wxThread::ExitCode
111 CavernThread::Entry()
113 while (true) {
114 CavernOutputEvent * e = new CavernOutputEvent();
115 in->Read(e->buf, sizeof(e->buf));
116 size_t n = in->LastRead();
117 if (n == 0 || TestDestroy()) {
118 delete e;
119 return (wxThread::ExitCode)0;
121 if (n == 1 && e->buf[0] == '\n') {
122 // Don't send an event with just a blank line in.
123 in->Read(e->buf + 1, sizeof(e->buf) - 1);
124 n += in->LastRead();
125 if (TestDestroy()) {
126 delete e;
127 return (wxThread::ExitCode)0;
130 e->len = n;
131 handler->QueueEvent(e);
135 #else
137 void
138 CavernLogWindow::OnIdle(wxIdleEvent& event)
140 if (cavern_out == NULL) return;
142 wxInputStream * in = cavern_out->GetInputStream();
144 if (!in->CanRead()) {
145 // Avoid a tight busy-loop on idle events.
146 wxMilliSleep(10);
148 if (in->CanRead()) {
149 CavernOutputEvent * e = new CavernOutputEvent();
150 in->Read(e->buf, sizeof(e->buf));
151 size_t n = in->LastRead();
152 if (n == 0) {
153 delete e;
154 return;
156 e->len = n;
157 QueueEvent(e);
160 event.RequestMore();
162 #endif
164 BEGIN_EVENT_TABLE(CavernLogWindow, wxHtmlWindow)
165 EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
166 EVT_BUTTON(LOG_SAVE, CavernLogWindow::OnSave)
167 EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
168 EVT_COMMAND(wxID_ANY, wxEVT_CAVERN_OUTPUT, CavernLogWindow::OnCavernOutput)
169 #ifdef CAVERNLOG_USE_THREADS
170 EVT_CLOSE(CavernLogWindow::OnClose)
171 #else
172 EVT_IDLE(CavernLogWindow::OnIdle)
173 #endif
174 EVT_END_PROCESS(wxID_ANY, CavernLogWindow::OnEndProcess)
175 END_EVENT_TABLE()
177 wxString escape_for_shell(wxString s, bool protect_dash)
179 #ifdef __WXMSW__
180 // Correct quoting rules are insane:
182 // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
184 // Thankfully wxExecute passes the command string to CreateProcess(), so
185 // at least we don't need to quote for cmd.exe too.
186 if (s.empty() || s.find_first_of(wxT(" \"\t\n\v")) != s.npos) {
187 // Need to quote.
188 s.insert(0, wxT('"'));
189 for (size_t p = 1; p < s.size(); ++p) {
190 size_t backslashes = 0;
191 while (s[p] == wxT('\\')) {
192 ++backslashes;
193 if (++p == s.size()) {
194 // Escape all the backslashes, since they're before
195 // the closing quote we add below.
196 s.append(backslashes, wxT('\\'));
197 goto done;
201 if (s[p] == wxT('"')) {
202 // Escape any preceding backslashes and this quote.
203 s.insert(p, backslashes + 1, wxT('\\'));
204 p += backslashes + 1;
207 done:
208 s.append(wxT('"'));
210 #else
211 size_t p = 0;
212 if (protect_dash && !s.empty() && s[0u] == '-') {
213 // If the filename starts with a '-', protect it from being
214 // treated as an option by prepending "./".
215 s.insert(0, wxT("./"));
216 p = 2;
218 while (p < s.size()) {
219 // Exclude a few safe characters which are common in filenames
220 if (!isalnum(s[p]) && strchr("/._-", s[p]) == NULL) {
221 s.insert(p, 1, wxT('\\'));
222 ++p;
224 ++p;
226 #endif
227 return s;
230 wxString get_command_path(const wxChar * command_name)
232 #ifdef __WXMSW__
233 wxString cmd;
235 DWORD len = 256;
236 wchar_t *buf = NULL;
237 while (1) {
238 DWORD got;
239 buf = (wchar_t*)osrealloc(buf, len * 2);
240 got = GetModuleFileNameW(NULL, buf, len);
241 if (got < len) break;
242 len += len;
244 /* Strange Win32 nastiness - strip prefix "\\?\" if present */
245 wchar_t *start = buf;
246 if (wcsncmp(start, L"\\\\?\\", 4) == 0) start += 4;
247 wchar_t * slash = wcsrchr(start, L'\\');
248 if (slash) {
249 cmd.assign(start, slash - start + 1);
251 osfree(buf);
253 #else
254 wxString cmd = wxString(msg_exepth(), wxConvUTF8);
255 #endif
256 cmd += command_name;
257 return cmd;
260 CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
261 : wxHtmlWindow(parent),
262 mainfrm(mainfrm_), cavern_out(NULL), highlight(NULL),
263 link_count(0), end(buf), init_done(false), survey(survey_)
264 #ifdef CAVERNLOG_USE_THREADS
265 , thread(NULL)
266 #endif
268 int fsize = parent->GetFont().GetPointSize();
269 int sizes[7] = { fsize, fsize, fsize, fsize, fsize, fsize, fsize };
270 SetFonts(wxString(), wxString(), sizes);
273 CavernLogWindow::~CavernLogWindow()
275 #ifdef CAVERNLOG_USE_THREADS
276 if (thread) stop_thread();
277 #endif
278 if (cavern_out) {
279 wxEndBusyCursor();
280 cavern_out->Detach();
284 #ifdef CAVERNLOG_USE_THREADS
285 void
286 CavernLogWindow::stop_thread()
288 // Killing the subprocess by its pid is theoretically racy, but in practice
289 // it's not going to cause issues, and it's all the wxProcess API seems to
290 // allow us to do. If we don't kill the subprocess, we need to wait for it
291 // to write out some output - there seems to be no way to do the equivalent
292 // of select() with a timeout on a a wxInputStream.
294 // The only alternative to this seems to be to do:
296 // while (!s.CanRead()) {
297 // if (TestDestroy()) return (wxThread::ExitCode)0;
298 // wxMilliSleep(N);
299 // }
301 // But that makes the log window update sluggishly, and we're using a
302 // worker thread precisely to try to avoid having to do dumb stuff like
303 // this.
304 wxProcess::Kill(cavern_out->GetPid());
307 wxCriticalSectionLocker enter(thread_lock);
308 if (thread) {
309 wxThreadError res;
310 #if wxCHECK_VERSION(2,9,2)
311 res = thread->Delete(NULL, wxTHREAD_WAIT_BLOCK);
312 #else
313 res = thread->Delete();
314 #endif
315 if (res != wxTHREAD_NO_ERROR) {
316 // FIXME
321 // Wait for thread to complete.
322 while (true) {
324 wxCriticalSectionLocker enter(thread_lock);
325 if (!thread) break;
327 wxMilliSleep(1);
331 void
332 CavernLogWindow::OnClose(wxCloseEvent &)
334 if (thread) stop_thread();
335 Destroy();
337 #endif
339 void
340 CavernLogWindow::OnLinkClicked(const wxHtmlLinkInfo &link)
342 wxString href = link.GetHref();
343 wxString title = link.GetTarget();
344 size_t colon2 = href.rfind(wxT(':'));
345 if (colon2 == wxString::npos)
346 return;
347 size_t colon = href.rfind(wxT(':'), colon2 - 1);
348 if (colon == wxString::npos)
349 return;
350 wxString cmd;
351 wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
352 if (p) {
353 cmd = p;
354 if (!cmd.find(wxT("$f"))) {
355 cmd += wxT(" $f");
357 } else {
358 p = wxGetenv(wxT("VISUAL"));
359 if (!p) p = wxGetenv(wxT("EDITOR"));
360 if (!p) {
361 cmd = wxT(DEFAULT_EDITOR_COMMAND);
362 } else {
363 cmd = p;
364 if (cmd == "gvim") {
365 cmd = wxT(GVIM_COMMAND);
366 } else if (cmd == "vim") {
367 cmd = wxT(VIM_COMMAND);
368 } else if (cmd == "gedit") {
369 cmd = wxT(GEDIT_COMMAND);
370 } else if (cmd == "pluma") {
371 cmd = wxT(PLUMA_COMMAND);
372 } else if (cmd == "emacs") {
373 cmd = wxT(EMACS_COMMAND);
374 } else if (cmd == "nano") {
375 cmd = wxT(NANO_COMMAND);
376 } else if (cmd == "jed") {
377 cmd = wxT(JED_COMMAND);
378 } else if (cmd == "kate") {
379 cmd = wxT(KATE_COMMAND);
380 } else {
381 // Escape any $.
382 cmd.Replace(wxT("$"), wxT("$$"));
383 cmd += wxT(" $f");
387 size_t i = 0;
388 while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
389 if (++i >= cmd.size()) break;
390 switch ((int)cmd[i]) {
391 case wxT('$'):
392 cmd.erase(i, 1);
393 break;
394 case wxT('f'): {
395 wxString f = escape_for_shell(href.substr(0, colon), true);
396 cmd.replace(i - 1, 2, f);
397 i += f.size() - 1;
398 break;
400 case wxT('t'): {
401 wxString t = escape_for_shell(title);
402 cmd.replace(i - 1, 2, t);
403 i += t.size() - 1;
404 break;
406 case wxT('l'): {
407 wxString l = escape_for_shell(href.substr(colon + 1, colon2 - colon - 1));
408 cmd.replace(i - 1, 2, l);
409 i += l.size() - 1;
410 break;
412 case wxT('c'): {
413 wxString l;
414 if (colon2 >= href.size() - 1)
415 l = wxT("0");
416 else
417 l = escape_for_shell(href.substr(colon2 + 1));
418 cmd.replace(i - 1, 2, l);
419 i += l.size() - 1;
420 break;
422 default:
423 ++i;
427 if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
428 return;
430 wxString m;
431 // TRANSLATORS: %s is replaced by the command we attempted to run.
432 m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
433 m += wxT(" (");
434 m += wxString(strerror(errno), wxConvUTF8);
435 m += wxT(')');
436 wxGetApp().ReportError(m);
439 void
440 CavernLogWindow::process(const wxString &file)
442 SetPage(wxString());
443 #ifdef CAVERNLOG_USE_THREADS
444 if (thread) stop_thread();
445 #endif
446 if (cavern_out) {
447 cavern_out->Detach();
448 cavern_out = NULL;
449 } else {
450 wxBeginBusyCursor();
453 SetFocus();
454 filename = file;
456 link_count = 0;
457 cur.resize(0);
458 log_txt.resize(0);
460 #ifdef __WXMSW__
461 SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
462 #else
463 setenv("SURVEX_UTF8", "1", 1);
464 #endif
466 wxString escaped_file = escape_for_shell(file, true);
467 wxString cmd = get_command_path(L"cavern");
468 cmd = escape_for_shell(cmd, false);
469 cmd += wxT(" -o ");
470 cmd += escaped_file;
471 cmd += wxT(' ');
472 cmd += escaped_file;
474 cavern_out = wxProcess::Open(cmd);
475 if (!cavern_out) {
476 wxString m;
477 m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
478 m += wxT(" (");
479 m += wxString(strerror(errno), wxConvUTF8);
480 m += wxT(')');
481 wxGetApp().ReportError(m);
482 return;
485 // We want to receive the wxProcessEvent when cavern exits.
486 cavern_out->SetNextHandler(this);
488 #ifdef CAVERNLOG_USE_THREADS
489 thread = new CavernThread(this, cavern_out->GetInputStream());
490 if (thread->Run() != wxTHREAD_NO_ERROR) {
491 wxGetApp().ReportError(wxT("Thread failed to start"));
492 delete thread;
493 thread = NULL;
495 #endif
498 void
499 CavernLogWindow::OnCavernOutput(wxCommandEvent & e_)
501 CavernOutputEvent & e = (CavernOutputEvent&)e_;
503 if (e.len > 0) {
504 ssize_t n = e.len;
505 if (size_t(n) > sizeof(buf) - (end - buf)) abort();
506 memcpy(end, e.buf, n);
507 log_txt.append((const char *)end, n);
508 end += n;
510 const unsigned char * p = buf;
512 while (p != end) {
513 int ch = *p++;
514 if (ch >= 0x80) {
515 // Decode multi-byte UTF-8 sequence.
516 if (ch < 0xc0) {
517 // Invalid UTF-8 sequence.
518 goto bad_utf8;
519 } else if (ch < 0xe0) {
520 /* 2 byte sequence */
521 if (p == end) {
522 // Incomplete UTF-8 sequence - try to read more.
523 break;
525 int ch1 = *p++;
526 if ((ch1 & 0xc0) != 0x80) {
527 // Invalid UTF-8 sequence.
528 goto bad_utf8;
530 ch = ((ch & 0x1f) << 6) | (ch1 & 0x3f);
531 } else if (ch < 0xf0) {
532 /* 3 byte sequence */
533 if (end - p <= 1) {
534 // Incomplete UTF-8 sequence - try to read more.
535 break;
537 int ch1 = *p++;
538 ch = ((ch & 0x1f) << 12) | ((ch1 & 0x3f) << 6);
539 if ((ch1 & 0xc0) != 0x80) {
540 // Invalid UTF-8 sequence.
541 goto bad_utf8;
543 int ch2 = *p++;
544 if ((ch2 & 0xc0) != 0x80) {
545 // Invalid UTF-8 sequence.
546 goto bad_utf8;
548 ch |= (ch2 & 0x3f);
549 } else {
550 // Overlong UTF-8 sequence.
551 goto bad_utf8;
555 if (false) {
556 bad_utf8:
557 // Resync to next byte which starts a UTF-8 sequence.
558 while (p != end) {
559 if (*p < 0x80 || (*p >= 0xc0 && *p < 0xf0)) break;
560 ++p;
562 cur += badutf8_html;
563 continue;
566 switch (ch) {
567 case '\r':
568 // Ignore.
569 break;
570 case '\n': {
571 if (cur.empty()) continue;
572 if (cur[0] == ' ') {
573 if (source_line.empty()) {
574 // Source line shown for context. Store it so we
575 // can use the caret line to highlight it.
576 swap(source_line, cur);
577 } else {
578 size_t caret = cur.rfind('^');
579 if (caret != wxString::npos) {
580 size_t tilde = cur.rfind('~');
581 if (tilde == wxString::npos || tilde < caret) {
582 tilde = caret;
584 cur = "&nbsp;";
585 // FIXME: Need to count each & entity as one character...
586 cur.append(source_line, 1, caret - 1);
587 cur.append("<b>");
588 cur.append(highlight ? highlight : wxT("<span \"color:green\">"));
589 cur.append(source_line, caret, tilde + 1 - caret);
590 cur.append("</span></b>");
591 cur.append(source_line, tilde + 1, wxString::npos);
592 } else {
593 // No caret in second line - just output both.
594 source_line.replace(0, 1, "&nbsp;");
595 source_line += "<br>\n&nbsp;";
596 source_line.append(cur, 1, wxString::npos);
597 swap(cur, source_line);
599 cur += "<br>\n";
600 AppendToPage(cur);
601 cur.clear();
602 source_line.clear();
604 continue;
607 if (!source_line.empty()) {
608 // Previous line was a source line without column info
609 // so just show it.
610 source_line.replace(0, 1, "&nbsp;");
611 source_line += "<br>\n";
612 AppendToPage(source_line);
613 source_line.clear();
615 #ifndef __WXMSW__
616 size_t colon = cur.find(':');
617 #else
618 // If the path is "C:\path\to\file.svx" then don't split at the
619 // : after the drive letter! FIXME: better to look for ": "?
620 size_t colon = cur.find(':', 2);
621 #endif
622 if (colon != wxString::npos && colon < cur.size() - 2) {
623 ++colon;
624 size_t i = colon;
625 while (i < cur.size() - 2 &&
626 cur[i] >= wxT('0') && cur[i] <= wxT('9')) {
627 ++i;
629 if (i > colon && cur[i] == wxT(':') ) {
630 colon = i;
631 // Check for column number.
632 while (++i < cur.size() - 2 &&
633 cur[i] >= wxT('0') && cur[i] <= wxT('9')) { }
634 bool have_column = (i > colon + 1 && cur[i] == wxT(':'));
635 if (have_column) {
636 colon = i;
637 } else {
638 // If there's no colon, include a trailing ':'
639 // so that we can unambiguously split the href
640 // value up into filename, line and column.
641 ++colon;
643 wxString tag = wxT("<a href=\"");
644 tag.append(cur, 0, colon);
645 while (cur[++i] == wxT(' ')) { }
646 tag += wxT("\" target=\"");
647 wxString target(cur, i, wxString::npos);
648 target.Replace(badutf8_html, badutf8);
649 tag += target;
650 tag += wxT("\">");
651 cur.insert(0, tag);
652 size_t offset = colon + tag.size();
653 cur.insert(offset, wxT("</a>"));
654 offset += 4 + 2;
656 if (!have_column) --offset;
658 static const wxString & error_marker = wmsg(/*error*/93) + ":";
659 static const wxString & warning_marker = wmsg(/*warning*/4) + ":";
661 if (cur.substr(offset, error_marker.size()) == error_marker) {
662 // Show "error" marker in red.
663 highlight = wxT("<span style=\"color:red\">");
664 cur.insert(offset, highlight);
665 offset += 24 + error_marker.size() - 1;
666 cur.insert(offset, wxT("</span>"));
667 } else if (cur.substr(offset, warning_marker.size()) == warning_marker) {
668 // Show "warning" marker in orange.
669 highlight = wxT("<span style=\"color:orange\">");
670 cur.insert(offset, highlight);
671 offset += 27 + warning_marker.size() - 1;
672 cur.insert(offset, wxT("</span>"));
673 } else {
674 highlight = NULL;
677 ++link_count;
681 // Save the scrollbar positions.
682 int scroll_x = 0, scroll_y = 0;
683 GetViewStart(&scroll_x, &scroll_y);
685 cur += wxT("<br>\n");
686 AppendToPage(cur);
688 if (!link_count) {
689 // Auto-scroll the window until we've reported a
690 // warning or error.
691 int x, y;
692 GetVirtualSize(&x, &y);
693 int xs, ys;
694 GetClientSize(&xs, &ys);
695 y -= ys;
696 int xu, yu;
697 GetScrollPixelsPerUnit(&xu, &yu);
698 Scroll(scroll_x, y / yu);
699 } else {
700 // Restore the scrollbar positions.
701 Scroll(scroll_x, scroll_y);
704 cur.clear();
705 break;
707 case '<':
708 cur += wxT("&lt;");
709 break;
710 case '>':
711 cur += wxT("&gt;");
712 break;
713 case '&':
714 cur += wxT("&amp;");
715 break;
716 case '"':
717 cur += wxT("&#22;");
718 continue;
719 default:
720 #ifdef wxUSE_UNICODE
721 cur += wxChar(ch);
722 #else
723 // This approach means that highlighting of "error" or
724 // "warning" won't work in translations where they contain
725 // non-ASCII characters, but wxWidgets >= 3.0 in always
726 // Unicode, so this corner case is already very uncommon,
727 // and will become irrelevant with time.
728 if (ch >= 128) {
729 cur += wxString::Format(wxT("&#%u;"), ch);
730 } else {
731 cur += (char)ch;
733 #endif
737 size_t left = end - p;
738 end = buf + left;
739 if (left) memmove(buf, p, left);
740 Update();
741 return;
744 if (!source_line.empty()) {
745 // Previous line was a source line without column info
746 // so just show it.
747 source_line.replace(0, 1, "&nbsp;");
748 source_line += "<br>\n";
749 AppendToPage(source_line);
750 source_line.clear();
753 if (e.len <= 0 && buf != end) {
754 // Truncated UTF-8 sequence.
755 cur += badutf8_html;
757 if (!cur.empty()) {
758 cur += "<br>\n";
759 AppendToPage("<hr>" + cur);
762 /* TRANSLATORS: Label for button in aven’s cavern log window which
763 * allows the user to save the log to a file. */
764 AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
765 (int)LOG_SAVE,
766 wmsg(/*Save Log*/446).c_str()));
767 wxEndBusyCursor();
768 delete cavern_out;
769 cavern_out = NULL;
770 if (e.len < 0) {
771 /* Negative length indicates non-zero exit status from cavern. */
772 /* TRANSLATORS: Label for button in aven’s cavern log window which
773 * causes the survey data to be reprocessed. */
774 AppendToPage(wxString::Format(wxT("<avenbutton default id=%d name=\"%s\">"),
775 (int)LOG_REPROCESS,
776 wmsg(/*Reprocess*/184).c_str()));
777 return;
779 AppendToPage(wxString::Format(wxT("<avenbutton id=%d name=\"%s\">"),
780 (int)LOG_REPROCESS,
781 wmsg(/*Reprocess*/184).c_str()));
782 AppendToPage(wxString::Format(wxT("<avenbutton default id=%d>"), (int)wxID_OK));
783 Update();
784 init_done = false;
787 wxString file3d(filename, 0, filename.length() - 3);
788 file3d.append(wxT("3d"));
789 if (!mainfrm->LoadData(file3d, survey)) {
790 return;
794 if (link_count == 0) {
795 wxCommandEvent dummy;
796 OnOK(dummy);
800 void
801 CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
803 CavernOutputEvent * e = new CavernOutputEvent();
804 // Zero length indicates successful exit, negative length unsuccessful exit.
805 e->len = (evt.GetExitCode() == 0 ? 0 : -1);
806 QueueEvent(e);
809 void
810 CavernLogWindow::OnReprocess(wxCommandEvent &)
812 process(filename);
815 void
816 CavernLogWindow::OnSave(wxCommandEvent &)
818 wxString filelog(filename, 0, filename.length() - 3);
819 filelog += wxT("log");
820 AvenAllowOnTop ontop(mainfrm);
821 #ifdef __WXMOTIF__
822 wxString ext(wxT("*.log"));
823 #else
824 /* TRANSLATORS: Log files from running cavern (extension .log) */
825 wxString ext = wmsg(/*Log files*/447);
826 ext += wxT("|*.log");
827 #endif
828 wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
829 wxString(), filelog, ext,
830 wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
831 if (dlg.ShowModal() != wxID_OK) return;
832 filelog = dlg.GetPath();
833 FILE * fh_log = wxFopen(filelog, wxT("w"));
834 if (!fh_log) {
835 wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/110), filelog.c_str()));
836 return;
838 fwrite(log_txt.data(), log_txt.size(), 1, fh_log);
839 fclose(fh_log);
842 void
843 CavernLogWindow::OnOK(wxCommandEvent &)
845 if (init_done) {
846 mainfrm->HideLog(this);
847 } else {
848 mainfrm->InitialiseAfterLoad(filename, survey);
849 init_done = true;