source: git/src/cavernlog.cc@ 5b764cb

main
Last change on this file since 5b764cb was 0b99107, checked in by Olly Betts <olly@…>, 4 months ago

Eliminate old FSF addresses

Update GPL/LGPL licence files and boilerplate to direct people who
didn't receive the licence text to the FSF website, as the current
versions of the FSF licence texts now do, rather than giving a postal
address.

  • Property mode set to 100644
File size: 18.8 KB
RevLine 
[6bec10c]1/* cavernlog.cc
2 * Run cavern inside an Aven window
3 *
[ac20829b]4 * Copyright (C) 2005-2024 Olly Betts
[6bec10c]5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
[0b99107]17 * along with this program; if not, see
18 * <https://www.gnu.org/licenses/>.
[6bec10c]19 */
20
[4c83f84]21#include <config.h>
[6bec10c]22
23#include "aven.h"
24#include "cavernlog.h"
25#include "filename.h"
[fb5887c]26#include "mainfrm.h"
[6bec10c]27#include "message.h"
[4b40a9c]28#include "osalloc.h"
[6bec10c]29
[7af6fff]30#include <algorithm>
31
[baeae66]32#include <errno.h>
[6bec10c]33#include <stdio.h>
[a90632c]34#include <stdlib.h>
[6bec10c]35
36#include <sys/time.h>
37#include <sys/types.h>
38#include <unistd.h>
39
[15ba0b5]40#include <wx/process.h>
41
[cc2d7ad]42#define GVIM_COMMAND "gvim +'call cursor($l,$c)' $f"
43#define VIM_COMMAND "x-terminal-emulator -e vim +'call cursor($l,$c)' $f"
[81d94a4]44#define NVIM_COMMAND "x-terminal-emulator -e nvim +'call cursor($l,$c)' $f"
[cc2d7ad]45#define GEDIT_COMMAND "gedit $f +$l:$c"
46// Pluma currently ignores the column, but include it assuming some future
47// version will add support.
48#define PLUMA_COMMAND "pluma +$l:$c $f"
49#define EMACS_COMMAND "x-terminal-emulator -e emacs +$l:$c $f"
50#define NANO_COMMAND "x-terminal-emulator -e nano +$l,$c $f"
51#define JED_COMMAND "x-terminal-emulator -e jed $f -g $l"
52#define KATE_COMMAND "kate -l $l -c $c $f"
53
54#ifdef __WXMSW__
55# define DEFAULT_EDITOR_COMMAND "notepad $f"
56#elif defined __WXMAC__
57# define DEFAULT_EDITOR_COMMAND "open -t $f"
58#else
59# define DEFAULT_EDITOR_COMMAND VIM_COMMAND
60#endif
61
[0e81a88]62enum { LOG_REPROCESS = 1234, LOG_SAVE = 1235 };
[fb5887c]63
[ac20829b]64// New event type for signalling cavern output to process.
65wxDEFINE_EVENT(EVT_CAVERN_OUTPUT, wxCommandEvent);
[fc626ae]66
[ac20829b]67void
68CavernLogWindow::CheckForOutput(bool immediate)
69{
70 timer.Stop();
71 if (cavern_out == NULL) return;
[fc626ae]72
[ac20829b]73 wxInputStream * in = cavern_out->GetInputStream();
[fc626ae]74
[ac20829b]75 if (!in->CanRead()) {
76 timer.StartOnce();
77 return;
[fc626ae]78 }
79
[ac20829b]80 size_t real_size = log_txt.size();
81 size_t allow = 1024;
82 log_txt.resize(real_size + allow);
83 in->Read(&log_txt[real_size], allow);
84 size_t n = in->LastRead();
85 log_txt.resize(real_size + n);
86 if (n) {
87 if (immediate) {
88 ProcessCavernOutput();
89 } else {
90 QueueEvent(new wxCommandEvent(EVT_CAVERN_OUTPUT));
91 }
[fc626ae]92 }
[ac20829b]93}
[fc626ae]94
[ac20829b]95int
96CavernLogWindow::OnPaintButton(wxButton* b, int x)
[fc626ae]97{
[ac20829b]98 if (b) {
99 x -= 4;
100 const wxSize& bsize = b->GetSize();
101 x -= bsize.x;
102 b->SetSize(x, 4, bsize.x, bsize.y);
103 x -= 4;
[15ba0b5]104 }
[ac20829b]105 return x;
[15ba0b5]106}
107
108void
[ac20829b]109CavernLogWindow::OnPaint(wxPaintEvent&)
[15ba0b5]110{
[ac20829b]111 wxPaintDC dc(this);
112 wxFont font = dc.GetFont();
113 wxFont bold_font = font.Bold();
114 wxFont underlined_font = font.Underlined();
115 const wxRegion& region = GetUpdateRegion();
116 const wxRect& rect = region.GetBox();
117 int scroll_x = 0, scroll_y = 0;
118 GetViewStart(&scroll_x, &scroll_y);
119 int fsize = dc.GetFont().GetPixelSize().GetHeight();
120 int limit = min((rect.y + rect.height + fsize - 1) / fsize + scroll_y, int(line_info.size()) - 1);
121 for (int i = max(rect.y / fsize, scroll_y); i <= limit ; ++i) {
122 LineInfo& info = line_info[i];
123 // Leave a small margin to the left.
124 int x = fsize / 2 - scroll_x * fsize;
125 int y = (i - scroll_y) * fsize;
126 unsigned offset = info.start_offset;
127 unsigned len = info.len;
128 if (info.link_len) {
129 dc.SetFont(underlined_font);
130 dc.SetTextForeground(wxColour(192, 0, 192));
131 wxString link = wxString::FromUTF8(&log_txt[offset], info.link_len);
132 offset += info.link_len;
133 len -= info.link_len;
134 dc.DrawText(link, x, y);
135 x += info.link_pixel_width;
136 dc.SetFont(font);
[fc626ae]137 }
[ac20829b]138 if (info.colour_len) {
[1b3bd5d]139 dc.SetTextForeground(dark_mode ? *wxWHITE : *wxBLACK);
[ac20829b]140 {
141 size_t s_len = info.start_offset + info.colour_start - offset;
142 wxString s = wxString::FromUTF8(&log_txt[offset], s_len);
143 offset += s_len;
144 len -= s_len;
145 dc.DrawText(s, x, y);
146 x += dc.GetTextExtent(s).GetWidth();
147 }
148 switch (info.colour) {
149 case LOG_ERROR:
150 dc.SetTextForeground(*wxRED);
151 break;
152 case LOG_WARNING:
153 dc.SetTextForeground(wxColour(0xf2, 0x8C, 0x28));
154 break;
155 case LOG_INFO:
156 dc.SetTextForeground(*wxBLUE);
157 break;
158 }
159 dc.SetFont(bold_font);
160 wxString d = wxString::FromUTF8(&log_txt[offset], info.colour_len);
161 offset += info.colour_len;
162 len -= info.colour_len;
163 dc.DrawText(d, x, y);
164 x += dc.GetTextExtent(d).GetWidth();
165 dc.SetFont(font);
166 }
[1b3bd5d]167 dc.SetTextForeground(dark_mode ? *wxWHITE : *wxBLACK);
[ac20829b]168 dc.DrawText(wxString::FromUTF8(&log_txt[offset], len), x, y);
[15ba0b5]169 }
[ac20829b]170 int x = GetClientSize().x;
171 x = OnPaintButton(ok_button, x);
172 x = OnPaintButton(reprocess_button, x);
173 OnPaintButton(save_button, x);
[fc626ae]174}
175
[ac20829b]176BEGIN_EVENT_TABLE(CavernLogWindow, wxScrolledWindow)
[81e1aa4]177 EVT_BUTTON(LOG_REPROCESS, CavernLogWindow::OnReprocess)
[0e81a88]178 EVT_BUTTON(LOG_SAVE, CavernLogWindow::OnSave)
[fb5887c]179 EVT_BUTTON(wxID_OK, CavernLogWindow::OnOK)
[ac20829b]180 EVT_COMMAND(wxID_ANY, EVT_CAVERN_OUTPUT, CavernLogWindow::OnCavernOutput)
[8991d7f]181 EVT_IDLE(CavernLogWindow::OnIdle)
[ac20829b]182 EVT_TIMER(wxID_ANY, CavernLogWindow::OnTimer)
183 EVT_PAINT(CavernLogWindow::OnPaint)
184 EVT_MOTION(CavernLogWindow::OnMouseMove)
185 EVT_LEFT_UP(CavernLogWindow::OnLinkClicked)
[15ba0b5]186 EVT_END_PROCESS(wxID_ANY, CavernLogWindow::OnEndProcess)
[fb5887c]187END_EVENT_TABLE()
188
[549eb37]189wxString escape_for_shell(wxString s, bool protect_dash)
[6bec10c]190{
191#ifdef __WXMSW__
[faf83bee]192 // Correct quoting rules are insane:
193 //
[764fe32]194 // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
[faf83bee]195 //
196 // Thankfully wxExecute passes the command string to CreateProcess(), so
197 // at least we don't need to quote for cmd.exe too.
[8204040]198 if (protect_dash && !s.empty() && s[0u] == '-') {
199 // If the filename starts with a '-', protect it from being
200 // treated as an option by prepending ".\".
201 s.insert(0, wxT(".\\"));
202 }
[faf83bee]203 if (s.empty() || s.find_first_of(wxT(" \"\t\n\v")) != s.npos) {
204 // Need to quote.
205 s.insert(0, wxT('"'));
206 for (size_t p = 1; p < s.size(); ++p) {
207 size_t backslashes = 0;
208 while (s[p] == wxT('\\')) {
209 ++backslashes;
210 if (++p == s.size()) {
211 // Escape all the backslashes, since they're before
212 // the closing quote we add below.
213 s.append(backslashes, wxT('\\'));
214 goto done;
215 }
216 }
217
218 if (s[p] == wxT('"')) {
219 // Escape any preceding backslashes and this quote.
220 s.insert(p, backslashes + 1, wxT('\\'));
221 p += backslashes + 1;
[eff69a7]222 }
[6bec10c]223 }
[faf83bee]224done:
225 s.append(wxT('"'));
[6bec10c]226 }
227#else
[faf83bee]228 size_t p = 0;
[6bec10c]229 if (protect_dash && !s.empty() && s[0u] == '-') {
230 // If the filename starts with a '-', protect it from being
231 // treated as an option by prepending "./".
[5627cbb]232 s.insert(0, wxT("./"));
[6bec10c]233 p = 2;
234 }
235 while (p < s.size()) {
236 // Exclude a few safe characters which are common in filenames
[8adbe49]237 if (!isalnum((unsigned char)s[p]) && strchr("/._-", s[p]) == NULL) {
[6baad4a]238 s.insert(p, 1, wxT('\\'));
[6bec10c]239 ++p;
240 }
241 ++p;
242 }
243#endif
244 return s;
245}
246
[549eb37]247wxString get_command_path(const wxChar * command_name)
248{
249#ifdef __WXMSW__
250 wxString cmd;
251 {
252 DWORD len = 256;
253 wchar_t *buf = NULL;
254 while (1) {
255 DWORD got;
256 buf = (wchar_t*)osrealloc(buf, len * 2);
257 got = GetModuleFileNameW(NULL, buf, len);
258 if (got < len) break;
259 len += len;
260 }
261 /* Strange Win32 nastiness - strip prefix "\\?\" if present */
262 wchar_t *start = buf;
263 if (wcsncmp(start, L"\\\\?\\", 4) == 0) start += 4;
264 wchar_t * slash = wcsrchr(start, L'\\');
265 if (slash) {
266 cmd.assign(start, slash - start + 1);
267 }
[ae917b96]268 free(buf);
[549eb37]269 }
270#else
[ac20829b]271 wxString cmd = wxString::FromUTF8(msg_exepth());
[549eb37]272#endif
273 cmd += command_name;
274 return cmd;
275}
276
[d7b53e3]277CavernLogWindow::CavernLogWindow(MainFrm * mainfrm_, const wxString & survey_, wxWindow * parent)
[ac20829b]278 : wxScrolledWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
279 wxFULL_REPAINT_ON_RESIZE),
[76bc864f]280 mainfrm(mainfrm_),
[ac20829b]281 survey(survey_),
282 timer(this)
[fb5887c]283{
[1b3bd5d]284 if (wxSystemSettings::GetAppearance().IsDark()) {
285 SetOwnBackgroundColour(*wxBLACK);
286 dark_mode = true;
[70c8a8c]287 return;
[1b3bd5d]288 }
[70c8a8c]289
290 SetOwnBackgroundColour(*wxWHITE);
[93ff5cc]291}
292
[8991d7f]293CavernLogWindow::~CavernLogWindow()
294{
[ac20829b]295 timer.Stop();
[c1144fe]296 if (cavern_out) {
297 wxEndBusyCursor();
[15ba0b5]298 cavern_out->Detach();
[c1144fe]299 }
[8991d7f]300}
301
[fc626ae]302void
[ac20829b]303CavernLogWindow::OnMouseMove(wxMouseEvent& e)
[fc626ae]304{
[ac20829b]305 const auto& pos = e.GetPosition();
306 int fsize = GetFont().GetPixelSize().GetHeight();
307 int scroll_x = 0, scroll_y = 0;
308 GetViewStart(&scroll_x, &scroll_y);
309 unsigned line = pos.y / fsize + scroll_y;
310 unsigned x = pos.x + scroll_x * fsize - fsize / 2;
311 if (line < line_info.size() && x <= line_info[line].link_pixel_width) {
312 SetCursor(wxCursor(wxCURSOR_HAND));
313 } else {
314 SetCursor(wxNullCursor);
[fc626ae]315 }
316}
317
318void
[ac20829b]319CavernLogWindow::OnLinkClicked(wxMouseEvent& e)
[6bec10c]320{
[ac20829b]321 const auto& pos = e.GetPosition();
322 int fsize = GetFont().GetPixelSize().GetHeight();
323 int scroll_x = 0, scroll_y = 0;
324 GetViewStart(&scroll_x, &scroll_y);
325 unsigned line = pos.y / fsize + scroll_y;
326 unsigned x = pos.x + scroll_x * fsize - fsize / 2;
327 if (!(line < line_info.size() && x <= line_info[line].link_pixel_width))
[3d3fb6c]328 return;
[ac20829b]329
330 const char* cur = &log_txt[line_info[line].start_offset];
331 size_t link_len = line_info[line].link_len;
332 size_t colon = link_len;
333 while (colon > 1 && (unsigned)(cur[--colon] - '0') <= 9) { }
334 size_t colon2 = colon;
335 while (colon > 1 && (unsigned)(cur[--colon] - '0') <= 9) { }
336 if (cur[colon] != ':') {
337 colon = colon2;
338 colon2 = link_len;
339 }
340
[cc2d7ad]341 wxString cmd;
[b8ba399]342 wxChar * p = wxGetenv(wxT("SURVEXEDITOR"));
[3d3fb6c]343 if (p) {
[b8ba399]344 cmd = p;
[3d3fb6c]345 if (!cmd.find(wxT("$f"))) {
346 cmd += wxT(" $f");
[6bec10c]347 }
[cc2d7ad]348 } else {
349 p = wxGetenv(wxT("VISUAL"));
350 if (!p) p = wxGetenv(wxT("EDITOR"));
351 if (!p) {
352 cmd = wxT(DEFAULT_EDITOR_COMMAND);
353 } else {
354 cmd = p;
355 if (cmd == "gvim") {
356 cmd = wxT(GVIM_COMMAND);
357 } else if (cmd == "vim") {
358 cmd = wxT(VIM_COMMAND);
[81d94a4]359 } else if (cmd == "nvim") {
360 cmd = wxT(NVIM_COMMAND);
[cc2d7ad]361 } else if (cmd == "gedit") {
362 cmd = wxT(GEDIT_COMMAND);
363 } else if (cmd == "pluma") {
364 cmd = wxT(PLUMA_COMMAND);
365 } else if (cmd == "emacs") {
366 cmd = wxT(EMACS_COMMAND);
367 } else if (cmd == "nano") {
368 cmd = wxT(NANO_COMMAND);
369 } else if (cmd == "jed") {
370 cmd = wxT(JED_COMMAND);
371 } else if (cmd == "kate") {
372 cmd = wxT(KATE_COMMAND);
373 } else {
374 // Escape any $.
375 cmd.Replace(wxT("$"), wxT("$$"));
376 cmd += wxT(" $f");
377 }
378 }
[3d3fb6c]379 }
380 size_t i = 0;
381 while ((i = cmd.find(wxT('$'), i)) != wxString::npos) {
382 if (++i >= cmd.size()) break;
383 switch ((int)cmd[i]) {
384 case wxT('$'):
385 cmd.erase(i, 1);
386 break;
387 case wxT('f'): {
[ac20829b]388 wxString f = escape_for_shell(wxString(cur, colon), true);
[3d3fb6c]389 cmd.replace(i - 1, 2, f);
390 i += f.size() - 1;
391 break;
392 }
393 case wxT('l'): {
[ac20829b]394 wxString l = escape_for_shell(wxString(cur + colon + 1, colon2 - colon - 1));
[3d3fb6c]395 cmd.replace(i - 1, 2, l);
396 i += l.size() - 1;
397 break;
[1d71195]398 }
[3d3fb6c]399 case wxT('c'): {
400 wxString l;
[ac20829b]401 if (colon2 == link_len)
[3d3fb6c]402 l = wxT("0");
403 else
[ac20829b]404 l = escape_for_shell(wxString(cur + colon2 + 1, link_len - colon2 - 1));
[3d3fb6c]405 cmd.replace(i - 1, 2, l);
406 i += l.size() - 1;
407 break;
408 }
409 default:
410 ++i;
[6bec10c]411 }
[3d3fb6c]412 }
[faf83bee]413
414 if (wxExecute(cmd, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER) >= 0)
[3d3fb6c]415 return;
[faf83bee]416
[3d3fb6c]417 wxString m;
[736f7df]418 // TRANSLATORS: %s is replaced by the command we attempted to run.
[3d3fb6c]419 m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
420 m += wxT(" (");
[ac20829b]421 m += wxString::FromUTF8(strerror(errno));
[3d3fb6c]422 m += wxT(')');
423 wxGetApp().ReportError(m);
[6bec10c]424}
425
[8991d7f]426void
[6bec10c]427CavernLogWindow::process(const wxString &file)
428{
[ac20829b]429 timer.Stop();
[c1144fe]430 if (cavern_out) {
[15ba0b5]431 cavern_out->Detach();
[15033fd]432 cavern_out = NULL;
[c1144fe]433 } else {
434 wxBeginBusyCursor();
435 }
436
[fb5887c]437 SetFocus();
438 filename = file;
439
[76bc864f]440 info_count = 0;
[8991d7f]441 link_count = 0;
[0e81a88]442 log_txt.resize(0);
[ac20829b]443 line_info.resize(0);
444 // Reserve enough that we won't need to grow the allocations in normal cases.
445 log_txt.reserve(16384);
446 line_info.reserve(256);
447 ptr = 0;
448 save_button = nullptr;
449 reprocess_button = nullptr;
450 ok_button = nullptr;
451 DestroyChildren();
452 SetVirtualSize(0, 0);
[0e81a88]453
[6bec10c]454#ifdef __WXMSW__
[15322f2]455 SetEnvironmentVariable(wxT("SURVEX_UTF8"), wxT("1"));
[6bec10c]456#else
[06b1227]457 setenv("SURVEX_UTF8", "1", 1);
[6bec10c]458#endif
[93ff5cc]459
[6bec10c]460 wxString escaped_file = escape_for_shell(file, true);
[549eb37]461 wxString cmd = get_command_path(L"cavern");
[9e50f755]462 cmd = escape_for_shell(cmd, false);
[5627cbb]463 cmd += wxT(" -o ");
[6bec10c]464 cmd += escaped_file;
[5627cbb]465 cmd += wxT(' ');
[6bec10c]466 cmd += escaped_file;
467
[15ba0b5]468 cavern_out = wxProcess::Open(cmd);
[6bec10c]469 if (!cavern_out) {
[5627cbb]470 wxString m;
[3d3fb6c]471 m.Printf(wmsg(/*Couldn’t run external command: “%s”*/17), cmd.c_str());
[5627cbb]472 m += wxT(" (");
[ac20829b]473 m += wxString::FromUTF8(strerror(errno));
[5627cbb]474 m += wxT(')');
[6bec10c]475 wxGetApp().ReportError(m);
[8991d7f]476 return;
[6bec10c]477 }
478
[15ba0b5]479 // We want to receive the wxProcessEvent when cavern exits.
480 cavern_out->SetNextHandler(this);
[40b02e8]481
[ac20829b]482 // Check for output after 500ms if we don't get an idle event sooner.
483 timer.StartOnce(500);
[fc626ae]484}
485
486void
[ac20829b]487CavernLogWindow::ProcessCavernOutput()
[fc626ae]488{
[ac20829b]489 // ptr gives the start of the first line we've not yet processed.
490
491 size_t nl;
492 while ((nl = log_txt.find('\n', ptr)) != std::string::npos) {
493 if (nl == ptr || (nl - ptr == 1 && log_txt[ptr] == '\r')) {
494 // Don't show empty lines in the window.
495 ptr = nl + 1;
496 continue;
497 }
498 size_t line_len = nl - ptr - (log_txt[nl - 1] == '\r');
499 // FIXME: Avoid copy, use string_view?
500 string cur(log_txt, ptr, line_len);
501 if (log_txt[ptr] == ' ') {
502 if (expecting_caret_line) {
503 // FIXME: Check the line is only space, `^` and `~`?
504 // Otherwise an error without caret info followed
505 // by an error which contains a '^' gets
506 // mishandled...
507 size_t caret = cur.rfind('^');
508 if (caret != wxString::npos) {
509 size_t tilde = cur.rfind('~');
510 if (tilde == wxString::npos || tilde < caret) {
511 tilde = caret;
[b3ee5f5]512 }
[ac20829b]513 line_info.back().colour = line_info[line_info.size() - 2].colour;
514 line_info.back().colour_start = caret;
515 line_info.back().colour_len = tilde - caret + 1;
516 expecting_caret_line = false;
517 ptr = nl + 1;
518 continue;
[6bec10c]519 }
520 }
[ac20829b]521 expecting_caret_line = true;
522 }
523 line_info.emplace_back(ptr);
524 line_info.back().len = line_len;
525 size_t colon = cur.find(": ");
526 if (colon != wxString::npos) {
527 size_t link_len = colon;
528 while (colon > 1 && (unsigned)(cur[--colon] - '0') <= 9) { }
529 if (cur[colon] == ':') {
530 line_info.back().link_len = link_len;
531
532 static string info_marker = string(msg(/*info*/485)) + ':';
[7962c9d]533 static string warning_marker = string(msg(/*warning*/106)) + ':';
[ac20829b]534 static string error_marker = string(msg(/*error*/93)) + ':';
535
536 size_t offset = link_len + 2;
537 if (cur.compare(offset, info_marker.size(), info_marker) == 0) {
538 // Show "info" marker in blue.
539 ++info_count;
540 line_info.back().colour = LOG_INFO;
541 line_info.back().colour_start = offset;
542 line_info.back().colour_len = info_marker.size() - 1;
543 } else if (cur.compare(offset, warning_marker.size(), warning_marker) == 0) {
544 // Show "warning" marker in orange.
545 line_info.back().colour = LOG_WARNING;
546 line_info.back().colour_start = offset;
547 line_info.back().colour_len = warning_marker.size() - 1;
548 } else if (cur.compare(offset, error_marker.size(), error_marker) == 0) {
549 // Show "error" marker in red.
550 line_info.back().colour = LOG_ERROR;
551 line_info.back().colour_start = offset;
552 line_info.back().colour_len = error_marker.size() - 1;
[e768f29]553 }
[ac20829b]554 ++link_count;
[b3ee5f5]555 }
[6bec10c]556 }
[b3ee5f5]557
[ac20829b]558 int fsize = GetFont().GetPixelSize().GetHeight();
559 SetScrollRate(fsize, fsize);
560
561 auto& info = line_info.back();
562 info.link_pixel_width = GetTextExtent(wxString(&log_txt[ptr], info.link_len)).GetWidth();
563 auto rest_pixel_width = GetTextExtent(wxString(&log_txt[ptr + info.link_len], info.len - info.link_len)).GetWidth();
564 int width = max(GetVirtualSize().GetWidth(),
565 int(fsize + info.link_pixel_width + rest_pixel_width));
566 int height = line_info.size();
567 SetVirtualSize(width, height * fsize);
568 if (!link_count) {
569 // Auto-scroll until the first diagnostic.
570 int scroll_x = 0, scroll_y = 0;
571 GetViewStart(&scroll_x, &scroll_y);
572 int xs, ys;
573 GetClientSize(&xs, &ys);
574 Scroll(scroll_x, line_info.size() * fsize - ys);
575 }
576 ptr = nl + 1;
[6bec10c]577 }
[ac20829b]578}
[8991d7f]579
[ac20829b]580void
581CavernLogWindow::OnEndProcess(wxProcessEvent & evt)
582{
583 bool cavern_success = evt.GetExitCode() == 0;
[f207751]584
[ac20829b]585 // Read and process any remaining buffered output.
586 wxInputStream* in = cavern_out->GetInputStream();
587 while (!in->Eof()) {
588 CheckForOutput(true);
[f207751]589 }
[fb5887c]590
[c1144fe]591 wxEndBusyCursor();
[ac20829b]592
[15ba0b5]593 delete cavern_out;
[8991d7f]594 cavern_out = NULL;
[ac20829b]595
596 // Initially place buttons off the right of the window - they get moved to
597 // the desired position by OnPaintButton().
598 wxPoint off_right(GetSize().x, 0);
599 /* TRANSLATORS: Label for button in aven’s cavern log window which
600 * allows the user to save the log to a file. */
601 save_button = new wxButton(this, LOG_SAVE, wmsg(/*&Save Log*/446), off_right);
602
603 /* TRANSLATORS: Label for button in aven’s cavern log window which
604 * causes the survey data to be reprocessed. */
605 reprocess_button = new wxButton(this, LOG_REPROCESS, wmsg(/*&Reprocess*/184), off_right);
606
607 if (cavern_success) {
608 ok_button = new wxButton(this, wxID_OK, wxString(), off_right);
609 ok_button->SetDefault();
610 }
611
612 Refresh();
613 if (!cavern_success) {
[8991d7f]614 return;
[6bec10c]615 }
[ac20829b]616
[d7b53e3]617 init_done = false;
[fb5887c]618
[15ba0b5]619 {
620 wxString file3d(filename, 0, filename.length() - 3);
621 file3d.append(wxT("3d"));
622 if (!mainfrm->LoadData(file3d, survey)) {
623 return;
624 }
[d7b53e3]625 }
[15ba0b5]626
[76bc864f]627 // Don't stay on log if there there are only "info" diagnostics.
628 if (link_count == info_count) {
[8991d7f]629 wxCommandEvent dummy;
630 OnOK(dummy);
[fb5887c]631 }
[15ba0b5]632}
633
[0e81a88]634void
[330cb03]635CavernLogWindow::OnReprocess(wxCommandEvent &)
[8991d7f]636{
637 process(filename);
638}
639
640void
[0e81a88]641CavernLogWindow::OnSave(wxCommandEvent &)
642{
643 wxString filelog(filename, 0, filename.length() - 3);
[ac20829b]644#ifdef __WXMSW__
645 // We need to consistently use `\` here.
646 filelog.Replace("/", "\\");
647#endif
[0e81a88]648 filelog += wxT("log");
649#ifdef __WXMOTIF__
650 wxString ext(wxT("*.log"));
651#else
[bb71423]652 /* TRANSLATORS: Log files from running cavern (extension .log) */
[0e81a88]653 wxString ext = wmsg(/*Log files*/447);
654 ext += wxT("|*.log");
655#endif
656 wxFileDialog dlg(this, wmsg(/*Select an output filename*/319),
657 wxString(), filelog, ext,
658 wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
659 if (dlg.ShowModal() != wxID_OK) return;
660 filelog = dlg.GetPath();
[3206c12]661 FILE * fh_log = wxFopen(filelog, wxT("w"));
[0e81a88]662 if (!fh_log) {
[7962c9d]663 wxGetApp().ReportError(wxString::Format(wmsg(/*Error writing to file “%s”*/7), filelog.c_str()));
[0e81a88]664 return;
665 }
[87c9067]666 FWRITE_(log_txt.data(), log_txt.size(), 1, fh_log);
[0e81a88]667 fclose(fh_log);
668}
669
[fb5887c]670void
671CavernLogWindow::OnOK(wxCommandEvent &)
672{
[d7b53e3]673 if (init_done) {
674 mainfrm->HideLog(this);
675 } else {
[5e0b9f9d]676 mainfrm->InitialiseAfterLoad(filename, survey);
[d7b53e3]677 init_done = true;
678 }
[fb5887c]679}
Note: See TracBrowser for help on using the repository browser.