source: git/src/cavernlog.cc

main
Last change on this file was d63a694, checked in by Olly Betts <olly@…>, 4 weeks ago

Need <io.h> for _commit()

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