source: git/src/cavernlog.cc@ 9fcc81a

RELEASE/1.2 debug-ci debug-ci-sanitisers faster-cavernlog log-select main stereo-2025 walls-data walls-data-hanging-as-warning warn-only-for-hanging-survey
Last change on this file since 9fcc81a was e840570, checked in by Olly Betts <olly@…>, 7 years ago

Drop support for wxWidgets < 3.0

3.0.0 was released over 5 years ago and should be easily available
everywhere by now.

I'm no longer easily able to test with wxWidgets 2.8, and this allows
a significant amount of cruft to be removed.

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