source: git/src/aven.cc

Last change on this file was 0b99107, checked in by Olly Betts <olly@…>, 12 days 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.