source: git/src/aven.cc

main
Last change on this file was 0b99107, checked in by Olly Betts <olly@…>, 3 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: 13.8 KB
Line 
1//
2// aven.cc
3//
4// Main class for Aven.
5//
6// Copyright (C) 2001 Mark R. Shinwell.
7// Copyright (C) 2002-2025 Olly Betts
8//
9// This program is free software; you can redistribute it and/or modify
10// it under the terms of the GNU General Public License as published by
11// the Free Software Foundation; either version 2 of the License, or
12// (at your option) any later version.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17// GNU General Public License for more details.
18//
19// You should have received a copy of the GNU General Public License
20// along with this program; if not, see
21// <https://www.gnu.org/licenses/>.
22//
23
24#include <config.h>
25
26#define MSG_SETUP_PROJ_SEARCH_PATH 1
27
28#include "aven.h"
29#include "log.h"
30#include "gla.h"
31#include "mainfrm.h"
32
33#include "cmdline.h"
34#include "message.h"
35#include "useful.h"
36
37#include <algorithm>
38
39#include <assert.h>
40#include <stdio.h>
41
42#include <wx/confbase.h>
43#if wxUSE_DISPLAY
44// wxDisplay was added in wx 2.5; but it may not be built for mingw (because
45// the header seems to be missing).
46#include <wx/display.h>
47#endif
48
49#ifdef __WXMSW__
50#include <windows.h>
51#endif
52
53static const struct option long_opts[] = {
54 /* const char *name; int has_arg (0 no_argument, 1 required_*, 2 optional_*); int *flag; int val; */
55 {"survey", required_argument, 0, 's'},
56 {"print", no_argument, 0, 'p'},
57 {"help", no_argument, 0, HLP_HELP},
58 {"version", no_argument, 0, HLP_VERSION},
59 {0, 0, 0, 0}
60};
61
62#define short_opts "s:p"
63
64static struct help_msg help[] = {
65 /* <-- */
66 /* TRANSLATORS: --help output for --survey option.
67 *
68 * "this" has been added to English translation */
69 {HLP_ENCODELONG(0), /*only load the sub-survey with this prefix*/199, 0, 0},
70 /* TRANSLATORS: --help output for aven --print option */
71 {HLP_ENCODELONG(1), /*print and exit (requires a 3d file)*/119, 0, 0},
72 {0, 0, 0, 0}
73};
74
75#ifdef __WXMSW__
76IMPLEMENT_APP(Aven)
77#else
78IMPLEMENT_APP_NO_MAIN(Aven)
79IMPLEMENT_WX_THEME_SUPPORT
80#endif
81
82Aven::Aven()
83{
84 wxFont::SetDefaultEncoding(wxFONTENCODING_UTF8);
85}
86
87Aven::~Aven()
88{
89 delete m_pageSetupData;
90}
91
92static int getopt_first_response = 0;
93
94static char ** utf8_argv;
95
96#ifdef __WXMSW__
97bool Aven::Initialize(int& my_argc, wxChar **my_argv)
98{
99 const wxChar * cmd_line = GetCommandLineW();
100
101 // Horrible bodge to handle therion's assumptions about the "Process"
102 // file association.
103 if (cmd_line) {
104 // None of these are valid aven command line options, so this is not
105 // going to be triggered accidentally.
106 const wxChar * p = wxStrstr(cmd_line,
107 wxT("aven.exe\" --quiet --log --output="));
108 if (p) {
109 // Just change the command name in the command line string - that
110 // way the quoting should match what the C runtime expects.
111 wxString cmd(cmd_line, p - cmd_line);
112 cmd += "cavern";
113 cmd += p + 4;
114 exit(wxExecute(cmd, wxEXEC_SYNC));
115 }
116 }
117
118 int utf8_argc;
119 {
120 // wxWidgets doesn't split up the command line in the standard way, so
121 // redo it ourselves using the standard API function.
122 //
123 // Warning: The returned array from this has no terminating NULL
124 // element.
125 wxChar ** new_argv = NULL;
126 if (cmd_line)
127 new_argv = CommandLineToArgvW(cmd_line, &utf8_argc);
128 bool failed = (new_argv == NULL);
129 if (failed) {
130 wxChar * p;
131 FormatMessage(
132 FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
133 NULL,
134 GetLastError(),
135 0,
136 (LPWSTR)&p,
137 4096,
138 NULL);
139 wxString m = "CommandLineToArgvW failed: ";
140 m += p;
141 wxMessageBox(m, APP_NAME, wxOK | wxCENTRE | wxICON_EXCLAMATION);
142 LocalFree(p);
143 utf8_argc = my_argc;
144 new_argv = my_argv;
145 }
146
147 // Convert wide characters to UTF-8.
148 utf8_argv = new char * [utf8_argc + 1];
149 for (int i = 0; i < utf8_argc; ++i){
150 // We can't use osstrdup() before msg_init() but this is
151 // platform-specific code so we can assume strdup() is
152 // available.
153 utf8_argv[i] = strdup(wxString(new_argv[i]).utf8_str());
154 }
155 utf8_argv[utf8_argc] = NULL;
156
157 if (!failed) LocalFree(new_argv);
158 }
159
160 msg_init(utf8_argv);
161 /* Want --version and decent --help output, which cmdline does for us.
162 * wxCmdLine is much less good.
163 */
164 /* TRANSLATORS: Here "survey" is a "cave map" rather than list of questions
165 * - it should be translated to the terminology that cavers using the
166 * language would use.
167 *
168 * Part of aven --help */
169 cmdline_set_syntax_message(/*[SURVEY_FILE]*/269, 0, NULL);
170 cmdline_init(utf8_argc, utf8_argv, short_opts, long_opts, NULL, help, 0, 1);
171 getopt_first_response = cmdline_getopt();
172
173 // We're done writing to the terminal so switch to UTF-8 messages.
174 select_charset(CHARSET_UTF8);
175
176 // The argc and argv arguments don't actually get used here.
177 int dummy_argc = 0;
178 return wxApp::Initialize(dummy_argc, NULL);
179}
180#else
181int main(int argc, char **argv)
182{
183#ifdef __WXGTK3__
184# if !(wxUSE_GLCANVAS_EGL-0)
185 // The GLX-based wxGLCanvas doesn't work under Wayland, and the code
186 // segfaults: https://github.com/wxWidgets/wxWidgets/issues/17702
187 //
188 // Therefore we force X11 unless we're using the EGL-based wxGLCanvas.
189 //
190 // Setting GDK_BACKEND=x11 is the recommended workaround, and it seems to
191 // work to set it here. GTK2 doesn't support Wayland, so doesn't need
192 // this.
193 setenv("GDK_BACKEND", "x11", 1);
194# endif
195#endif
196
197#ifdef __WXMAC__
198 // MacOS passes a magic -psn_XXXX command line argument in argv[1] which
199 // wx ignores for us, but in wxApp::Initialize() which hasn't been
200 // called yet. So we need to remove it ourselves.
201 if (argc > 1 && strncmp(argv[1], "-psn_", 5) == 0) {
202 --argc;
203 memmove(argv + 1, argv + 2, argc * sizeof(char *));
204 }
205#endif
206 // Call msg_init() and start processing the command line first so that
207 // we can respond to --help and --version even without an X display.
208 msg_init(argv);
209 /* Want --version and decent --help output, which cmdline does for us.
210 * wxCmdLine is much less good.
211 */
212 cmdline_set_syntax_message(/*[SURVEY_FILE]*/269, 0, NULL);
213 cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 0, 1);
214 getopt_first_response = cmdline_getopt();
215
216 // We're done writing to the terminal so switch to UTF-8 messages.
217 select_charset(CHARSET_UTF8);
218 utf8_argv = argv;
219
220#if wxUSE_UNICODE
221 wxWCharBuffer buf(wxConvFileName->cMB2WX(argv[0]));
222 wxChar * wargv[2];
223 if (buf) {
224 wargv[0] = wxStrdup(buf);
225 } else {
226 // Eep - couldn't convert the executable's name to wide characters!
227 wargv[0] = wxStrdup(APP_NAME);
228 }
229 wargv[1] = NULL;
230 int wargc = 1;
231 return wxEntry(wargc, wargv);
232#else
233 char *dummy_argv[2] = { argv[0], NULL };
234 int dummy_argc = 1;
235 return wxEntry(dummy_argc, dummy_argv);
236#endif
237}
238#endif
239
240bool Aven::OnInit()
241{
242 wxLog::SetActiveTarget(new MyLogWindow());
243
244 {
245 // Suppress message box warnings about messages not found.
246 wxLogNull logNo;
247 wxLocale *loc = new wxLocale();
248 loc->AddCatalogLookupPathPrefix(wmsg_cfgpth());
249 wxString msg_lang_str(msg_lang, wxConvUTF8);
250 const char *lang = msg_lang2 ? msg_lang2 : msg_lang;
251 wxString lang_str(lang, wxConvUTF8);
252 loc->Init(msg_lang_str, lang_str, msg_lang_str);
253 // The existence of the wxLocale object is enough - no need to keep a
254 // pointer to it!
255 }
256
257 const char* opt_survey = NULL;
258 bool print_and_exit = false;
259
260 while (true) {
261 int opt;
262 if (getopt_first_response) {
263 opt = getopt_first_response;
264 getopt_first_response = 0;
265 } else {
266 opt = cmdline_getopt();
267 }
268 if (opt == EOF) break;
269 if (opt == 's') {
270 if (opt_survey != NULL) {
271 // FIXME: Not a helpful error, but this is temporary until
272 // we actually hook up support for specifying multiple
273 // --survey options properly here.
274 cmdline_syntax();
275 exit(1);
276 }
277 opt_survey = optarg;
278 }
279 if (opt == 'p') {
280 print_and_exit = true;
281 }
282 }
283
284 if (print_and_exit && !utf8_argv[optind]) {
285 cmdline_syntax(); // FIXME : not a helpful error...
286 exit(1);
287 }
288
289 wxString fnm;
290 if (utf8_argv[optind]) {
291 fnm = wxString(utf8_argv[optind], wxConvUTF8);
292 if (fnm.empty() && *(utf8_argv[optind])) {
293 ReportError(wxT("File argument's filename has bad encoding"));
294 return false;
295 }
296 }
297
298 if (!GLACanvas::check_visual()) {
299 wxString m;
300 /* TRANSLATORS: %s will be replaced with "Aven" currently (and
301 * perhaps by "Survex" or other things in future). */
302 m.Printf(wmsg(/*This version of %s requires OpenGL to work, but it isn’t available.*/405), APP_NAME);
303 wxMessageBox(m, APP_NAME, wxOK | wxCENTRE | wxICON_EXCLAMATION);
304 exit(1);
305 }
306
307 wxImage::AddHandler(new wxPNGHandler);
308
309 // Obtain the screen geometry.
310#if wxUSE_DISPLAY
311 wxRect geom = wxDisplay().GetGeometry();
312#else
313 wxRect geom;
314 wxClientDisplayRect(&geom.x, &geom.y, &geom.width, &geom.height);
315#endif
316
317 wxPoint pos(wxDefaultPosition);
318 int width, height;
319 wxConfigBase::Get()->Read(wxT("width"), &width, 0);
320 if (width > 0) wxConfigBase::Get()->Read(wxT("height"), &height, 0);
321 // We used to persist full screen mode (-1 was maximized,
322 // -2 full screen), but people would get stuck in full
323 // screen mode, unsure how to exit.
324 bool maximized = (width <= -1);
325 if (width <= 0 || height <= 0) {
326 pos.x = geom.x;
327 pos.y = geom.y;
328 width = geom.width;
329 height = geom.height;
330
331 // Calculate a reasonable size for our window.
332 pos.x += width / 8;
333 pos.y += height / 8;
334 width = width * 3 / 4;
335 height = height * 3 / 4;
336 } else {
337 // Impose a minimum size for sanity, and make sure the window fits on
338 // the display (in case the current display is smaller than the one
339 // in use when the window size was saved). (480x320) is about the
340 // smallest usable size for aven's window.
341 const int min_width = min(geom.width, 480);
342 const int min_height = min(geom.height, 320);
343 if (width < min_width || height < min_height) {
344 if (width < min_width) {
345 width = min_width;
346 }
347 if (height < min_height) {
348 height = min_height;
349 }
350 pos.x = geom.x + (geom.width - width) / 4;
351 pos.y = geom.y + (geom.height - height) / 4;
352 }
353 }
354
355 // Create the main window.
356 m_Frame = new MainFrm(APP_NAME, pos, wxSize(width, height));
357
358 // Select maximised if that's the saved state.
359 if (maximized) {
360 m_Frame->Maximize();
361 }
362
363 if (utf8_argv[optind]) {
364 if (!opt_survey) opt_survey = "";
365 m_Frame->OpenFile(fnm, wxString(opt_survey, wxConvUTF8));
366 }
367
368 if (print_and_exit) {
369 m_Frame->PrintAndExit();
370 return true;
371 }
372
373 m_Frame->Show(true);
374#ifdef _WIN32
375 m_Frame->SetFocus();
376#endif
377 return true;
378}
379
380wxPageSetupDialogData *
381Aven::GetPageSetupDialogData()
382{
383 if (!m_pageSetupData) m_pageSetupData = new wxPageSetupDialogData;
384#ifdef __WXGTK__
385 // Fetch paper margins stored on disk.
386 int left, right, top, bottom;
387 wxConfigBase * cfg = wxConfigBase::Get();
388 // These default margins were chosen by looking at all the .ppd files
389 // on my machine.
390 cfg->Read(wxT("paper_margin_left"), &left, 7);
391 cfg->Read(wxT("paper_margin_right"), &right, 7);
392 cfg->Read(wxT("paper_margin_top"), &top, 14);
393 cfg->Read(wxT("paper_margin_bottom"), &bottom, 14);
394 m_pageSetupData->SetMarginTopLeft(wxPoint(left, top));
395 m_pageSetupData->SetMarginBottomRight(wxPoint(right, bottom));
396#endif
397 return m_pageSetupData;
398}
399
400void
401Aven::SetPageSetupDialogData(const wxPageSetupDialogData & psdd)
402{
403 if (!m_pageSetupData) m_pageSetupData = new wxPageSetupDialogData;
404 *m_pageSetupData = psdd;
405#ifdef __WXGTK__
406 wxPoint topleft = psdd.GetMarginTopLeft();
407 wxPoint bottomright = psdd.GetMarginBottomRight();
408
409 // Store user specified paper margins on disk/in registry.
410 wxConfigBase * cfg = wxConfigBase::Get();
411 cfg->Write(wxT("paper_margin_left"), topleft.x);
412 cfg->Write(wxT("paper_margin_right"), bottomright.x);
413 cfg->Write(wxT("paper_margin_top"), topleft.y);
414 cfg->Write(wxT("paper_margin_bottom"), bottomright.y);
415 cfg->Flush();
416#endif
417}
418
419#ifdef __WXMAC__
420void
421Aven::MacOpenFiles(const wxArrayString & filenames)
422{
423 if (filenames.size() != 1) {
424 ReportError(wxT("Aven can only load one file at a time"));
425 return;
426 }
427 m_Frame->OpenFile(filenames[0], wxString());
428}
429
430void
431Aven::MacPrintFiles(const wxArrayString & filenames)
432{
433 if (filenames.size() != 1) {
434 ReportError(wxT("Aven can only print one file at a time"));
435 return;
436 }
437 m_Frame->OpenFile(filenames[0], wxString());
438 m_Frame->PrintAndExit();
439}
440#endif
441
442void Aven::ReportError(const wxString& msg)
443{
444 if (!m_Frame) {
445 wxMessageBox(msg, APP_NAME, wxOK | wxICON_ERROR);
446 return;
447 }
448 wxMessageDialog dlg(m_Frame, msg, APP_NAME, wxOK | wxICON_ERROR);
449 dlg.ShowModal();
450}
451
452const wxString &
453wmsg_cfgpth()
454{
455 static wxString path;
456 if (path.empty())
457 path = wxString(msg_cfgpth(), wxConvUTF8);
458 return path;
459}
460
461// called to report errors by message.c
462extern "C" void
463aven_v_report(int severity, const char *fnm, int line, int en, va_list ap)
464{
465 wxString m;
466 if (fnm) {
467 m = wxString(fnm, wxConvUTF8);
468 if (line) m += wxString::Format(wxT(":%d"), line);
469 m += wxT(": ");
470 }
471
472 if (severity == DIAG_WARN) {
473 m += wmsg(/*warning*/106);
474 m += wxT(": ");
475 }
476
477 char buf[1024];
478#ifdef HAVE__VSPRINTF_P
479 // Microsoft's vsnprintf() doesn't support positional argument specifiers,
480 // so we need to use the Microsoft-specific _vsprintf_p() which (despite
481 // its name) takes a buffer size like vsnprintf() does.
482 _vsprintf_p(buf, sizeof(buf), msg(en), ap);
483#else
484 vsnprintf(buf, sizeof(buf), msg(en), ap);
485#endif
486 m += wxString(buf, wxConvUTF8);
487 if (wxTheApp == NULL) {
488 // We haven't initialised the Aven app object yet.
489 if (!wxInitialize()) {
490 fputs(buf, stderr);
491 PUTC('\n', stderr);
492 exit(1);
493 }
494 wxMessageBox(m, APP_NAME, wxOK | wxICON_ERROR);
495 wxUninitialize();
496 } else {
497 wxGetApp().ReportError(m);
498 }
499}
Note: See TracBrowser for help on using the repository browser.