source: git/src/aven.cc @ 10bde2e

stereo-2025
Last change on this file since 10bde2e was 1698844, checked in by Olly Betts <olly@…>, 4 months ago

Adjust uses of strdup()

Replace one with osstrdup(). The other is called too early to use
osstrdup() but it's in code specific to Microsoft Windows and we
know strdup() is available there so add a comment to document this
assumption.

  • 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, 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    select_charset(CHARSET_UTF8);
163    /* Want --version and decent --help output, which cmdline does for us.
164     * wxCmdLine is much less good.
165     */
166    /* TRANSLATORS: Here "survey" is a "cave map" rather than list of questions
167     * - it should be translated to the terminology that cavers using the
168     * language would use.
169     *
170     * Part of aven --help */
171    cmdline_set_syntax_message(/*[SURVEY_FILE]*/269, 0, NULL);
172    cmdline_init(utf8_argc, utf8_argv, short_opts, long_opts, NULL, help, 0, 1);
173    getopt_first_response = cmdline_getopt();
174
175    // The argc and argv arguments don't actually get used here.
176    int dummy_argc = 0;
177    return wxApp::Initialize(dummy_argc, NULL);
178}
179#else
180int main(int argc, char **argv)
181{
182#ifdef __WXGTK3__
183# if !(wxUSE_GLCANVAS_EGL-0)
184    // The GLX-based wxGLCanvas doesn't work under Wayland, and the code
185    // segfaults: https://github.com/wxWidgets/wxWidgets/issues/17702
186    //
187    // Therefore we force X11 unless we're using the EGL-based wxGLCanvas
188    // (which was added in wxWidgets 3.1.5 and hasn't been backported to
189    // 3.0.x).
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    select_charset(CHARSET_UTF8);
211    /* Want --version and decent --help output, which cmdline does for us.
212     * wxCmdLine is much less good.
213     */
214    cmdline_set_syntax_message(/*[SURVEY_FILE]*/269, 0, NULL);
215    cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 0, 1);
216    getopt_first_response = cmdline_getopt();
217
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.