source: git/src/aven.cc

Last change on this file was fd0e32a, checked in by Olly Betts <olly@…>, 3 months ago

Raise wxWidgets requirement to >= 3.2.0

Previously it was >= 3.0.0 but it is starting to become hard to
keep everything working with 3.0.0 and the time and effort seems
better directed to other things.

The last 3.0.x release was 3.0.5.1 released 2020-05-02.

wx3.2.0 was released 2022-07-06 and it seems everywhere with wxWidgets
packages upgraded to 3.2.x some time ago.

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