1 // -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode: nil -*-
2 //
3 //  Copyright (C) 2004-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
4 //            (C) 2015-2018 Gunter Königsmann <wxMaxima@physikbuch.de>
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 //
17 //  You should have received a copy of the GNU General Public License
18 //  along with this program; if not, write to the Free Software
19 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 //
21 //  SPDX-License-Identifier: GPL-2.0+
22 
23 /*!\file
24   The file that defines the starting point of the program
25  */
26 
27 #include <wx/wx.h>
28 #include <wx/tipdlg.h>
29 #include <wx/config.h>
30 #include <wx/intl.h>
31 #include <wx/fs_zip.h>
32 #include <wx/image.h>
33 #include <wx/utils.h>
34 #include <wx/cmdline.h>
35 #include <wx/fileconf.h>
36 #include <wx/sysopt.h>
37 #include "Dirstructure.h"
38 #include <iostream>
39 
40 #include "../examples/examples.h"
41 #include "wxMaxima.h"
42 #include "Version.h"
43 
44 // On wxGTK2 we support printing only if wxWidgets is compiled with gnome_print.
45 // We have to force gnome_print support to be linked in static builds of wxMaxima.
46 
47 #if defined wxUSE_LIBGNOMEPRINT
48 #if wxUSE_LIBGNOMEPRINT
49 #include "wx/html/forcelnk.h"
50 FORCE_LINK(gnome_print)
51 #endif
52 #endif
53 
54 
55 IMPLEMENT_APP_NO_MAIN(MyApp);
56 IMPLEMENT_WX_THEME_SUPPORT;
57 
58 #ifndef __WXMSW__
main(int argc,char * argv[])59 int main(int argc, char *argv[])
60 {
61   wxEntryStart( argc, argv );
62   wxTheApp->CallOnInit();
63   #pragma omp parallel
64   #pragma omp master
65   wxTheApp->OnRun();
66   return 0;
67 }
68 #else
WinMain(HINSTANCE hI,HINSTANCE hPrevI,LPSTR lpCmdLine,int nCmdShow)69 int WINAPI WinMain( HINSTANCE hI, HINSTANCE hPrevI, LPSTR lpCmdLine, int nCmdShow )
70 {
71   wxEntryStart(hI, hPrevI, lpCmdLine, nCmdShow);
72   wxTheApp->CallOnInit();
73   #pragma omp parallel
74   #pragma omp master
75   wxTheApp->OnRun();
76   return 0;
77 }
78 #endif
79 
80 std::list<wxMaxima *> MyApp::m_topLevelWindows;
81 
82 
OnInit()83 bool MyApp::OnInit()
84 {
85   // On the Mac if any of these commands outputs text to stderr maxima fails to
86   // connect to wxMaxima. We therefore delay all output to the log until there
87   // is a window that can display it on the GUI instead.
88   wxLogBuffer noStdErr;
89   {
90 
91     Connect(wxID_NEW, wxEVT_MENU, wxCommandEventHandler(MyApp::OnFileMenu), NULL, this);
92     Connect(wxMaximaFrame::menu_help_tutorials_start, wxMaximaFrame::menu_help_tutorials_end,
93             wxEVT_MENU, wxCommandEventHandler(MyApp::OnFileMenu), NULL, this);
94 
95     // If supported: Generate symbolic backtraces on crashes.
96     #if wxUSE_ON_FATAL_EXCEPTION
97     wxHandleFatalExceptions(true);
98     #endif
99     int major;
100     int minor;
101     wxGetOsVersion(&major, &minor);
102 
103     // Directdraw should be faster, but crashes on closing on Win7.
104     if((major > 6) || ((major == 6) && (minor >1)))
105       wxSystemOptions::SetOption("msw.display.directdraw","1");
106     else
107       wxSystemOptions::SetOption("msw.display.directdraw","0");
108     // No spell checking in our dialog's input portions on the mac.
109     wxSystemOptions::SetOption("mac.textcontrol-use-spell-checker","0");
110 
111     // Migrate an eventual old config file to the location XDG wants it to be.
112     #ifndef __WXMSW__
113     #if wxCHECK_VERSION(3, 1, 1)
114     wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_Classic);
115     wxString configFileOld = wxStandardPaths::Get().GetUserConfigDir() + wxT("/") +
116       wxStandardPaths::Get().MakeConfigFileName(
117         wxString(wxT("wxMaxima")),
118         wxStandardPaths::ConfigFileConv_Dot);
119     wxStandardPaths::Get().SetFileLayout(wxStandardPaths::FileLayout_XDG);
120     wxString configFileXDG = wxStandardPaths::Get().GetUserConfigDir() + wxT("/") +
121       wxStandardPaths::Get().MakeConfigFileName(
122         wxString(wxT("wxMaxima")),
123         wxStandardPaths::ConfigFileConv_Ext);
124 
125     if(!wxFileExists(configFileXDG))
126     {
127       wxFileName xdgDir(configFileXDG);
128       wxString dirName(xdgDir.GetPath());
129       if(!wxDirExists(dirName))
130         wxMkDir(dirName,0x700);
131       if(wxFileExists(configFileOld))
132         wxCopyFile(configFileOld,configFileXDG);
133     }
134     #endif
135     #endif
136     wxConfig::Set(new wxConfig(wxT("wxMaxima"), wxEmptyString, m_configFileName));
137 
138     m_locale.AddCatalogLookupPathPrefix(m_dirstruct.LocaleDir());
139     m_locale.AddCatalogLookupPathPrefix(m_dirstruct.LocaleDir() + wxT("/wxwin"));
140     m_locale.AddCatalogLookupPathPrefix(wxT("/usr/share/locale"));
141     m_locale.AddCatalogLookupPathPrefix(wxT("/usr/local/share/locale"));
142     wxConfigBase *config = wxConfig::Get();
143     long lang = wxLocale::GetSystemLanguage();
144     config->Read(wxT("language"), &lang);
145     if(lang == wxLANGUAGE_UNKNOWN)
146       lang = wxLANGUAGE_DEFAULT;
147     m_locale.Init(lang);
148 
149     // Do we reckong we improve something if we set maxima's language, as well?
150     if((wxLocale::IsAvailable(lang)) && (lang != wxLANGUAGE_DEFAULT))
151     {
152       // Set maxima's language, as well.
153       wxString localeName = m_locale.GetCanonicalName();
154       if(lang != wxLocale::GetSystemLanguage())
155       {
156         if(m_locale.GetSystemEncoding() == wxFONTENCODING_UTF16)
157           localeName+=wxT(".UTF-16");
158         else
159         {
160           if(m_locale.GetSystemEncoding() == wxFONTENCODING_UTF32)
161             localeName+=wxT(".UTF-32");
162           else
163           {
164             localeName+=wxT(".UTF-8");
165           }
166         }
167         wxSetEnv(wxT("LANG"), localeName);
168       }
169     }
170     m_locale.AddCatalog(wxT("wxMaxima"));
171     m_locale.AddCatalog(wxT("wxMaxima-wxstd"));
172   }
173 
174   bool exitAfterEval = false;
175   bool evalOnStartup = false;
176   wxCmdLineParser cmdLineParser(argc, argv);
177 
178   static const wxCmdLineEntryDesc cmdLineDesc[] =
179     {
180       {wxCMD_LINE_SWITCH, "v", "version", "Output the version info", wxCMD_LINE_VAL_NONE , 0},
181       /* Usually wxCMD_LINE_OPTION_HELP is used with the following option, but that displays a message
182        * using a own window and we want the message on the command line. If a user enters a command
183        * line option, he expects probably a answer just on the command line... */
184       {wxCMD_LINE_SWITCH, "h", "help", "show this help message", wxCMD_LINE_VAL_NONE, 0},
185       {wxCMD_LINE_OPTION, "o", "open", "open a file", wxCMD_LINE_VAL_STRING , 0},
186       {wxCMD_LINE_SWITCH, "e", "eval",
187        "evaluate the file after opening it.", wxCMD_LINE_VAL_NONE , 0},
188       {wxCMD_LINE_SWITCH, "b", "batch",
189        "run the file and exit afterwards. Halts on questions and stops on errors.",  wxCMD_LINE_VAL_NONE, 0},
190                   {wxCMD_LINE_SWITCH, "", "logtostdout",
191                    "Log all \"debug messages\" sidebar messages to stderr, too.",  wxCMD_LINE_VAL_NONE, 0},
192                   {wxCMD_LINE_SWITCH, "", "pipe",
193                    "Pipe messages from Maxima to stdout.",  wxCMD_LINE_VAL_NONE, 0},
194                   {wxCMD_LINE_SWITCH, "", "exit-on-error",
195                    "Close the program on any Maxima error.",  wxCMD_LINE_VAL_NONE, 0},
196                   {wxCMD_LINE_OPTION, "f", "ini", "allows to specify a file to store the configuration in", wxCMD_LINE_VAL_STRING , 0},
197                   {wxCMD_LINE_OPTION, "u", "use-version",
198                    "Use Maxima version <str>.",  wxCMD_LINE_VAL_STRING, 0},
199                   {wxCMD_LINE_OPTION, "l", "lisp",
200                    "Use a Maxima compiled with lisp compiler <str>.",  wxCMD_LINE_VAL_STRING, 0},
201                   {wxCMD_LINE_OPTION, "X", "extra-args",
202                    "Allows to specify extra Maxima arguments",  wxCMD_LINE_VAL_STRING, 0},
203                   { wxCMD_LINE_OPTION, "m", "maxima", "allows to specify the location of the Maxima binary", wxCMD_LINE_VAL_STRING , 0},
204                   {wxCMD_LINE_PARAM, NULL, NULL, "input file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL | wxCMD_LINE_PARAM_MULTIPLE},
205             {wxCMD_LINE_NONE, "", "", "", wxCMD_LINE_VAL_NONE, 0}
206           };
207 
208   cmdLineParser.SetDesc(cmdLineDesc);
209   int cmdLineError = cmdLineParser.Parse();
210 
211   if (cmdLineParser.Found(wxT("h")))
212   {
213     std::cout << "A feature-rich graphical user interface for the computer algebra system Maxima\n";
214     std::cout << cmdLineParser.GetUsageString();
215     exit(0);
216   }
217 
218   if(cmdLineError != 0)
219     exit(-1);
220 
221   wxString ini, file;
222   // Attention: The config file is changed by wxMaximaFrame::wxMaximaFrame::ReReadConfig
223   if (cmdLineParser.Found(wxT("f"),&ini))
224   {
225     wxFileName configFile(ini);
226     configFile.MakeAbsolute();
227     Configuration::m_configfileLocation_override = configFile.GetFullPath();
228     wxConfig::Set(new wxConfig(wxT("wxMaxima"), wxEmptyString,
229                                Configuration::m_configfileLocation_override));
230   }
231   else
232     wxConfig::Set(new wxConfig(wxT("wxMaxima")));
233 
234   if (cmdLineParser.Found(wxT("logtostdout")))
235     ErrorRedirector::LogToStdErr();
236 
237   if (cmdLineParser.Found(wxT("pipe")))
238     wxMaxima::PipeToStdout();
239 
240   if (cmdLineParser.Found(wxT("exit-on-error")))
241     wxMaxima::ExitOnError();
242 
243   wxString extraMaximaArgs;
244   wxString arg;
245   if (cmdLineParser.Found(wxT("l"), &arg))
246     extraMaximaArgs += " -l " +  arg;
247 
248   if (cmdLineParser.Found(wxT("X"), &arg))
249     extraMaximaArgs += " -X " +  arg;
250 
251   if (cmdLineParser.Found(wxT("u"), &arg))
252     extraMaximaArgs += " -u " +  arg;
253 
254   wxMaxima::ExtraMaximaArgs(extraMaximaArgs);
255 
256   wxImage::AddHandler(new wxPNGHandler);
257   wxImage::AddHandler(new wxXPMHandler);
258   wxImage::AddHandler(new wxJPEGHandler);
259   wxImage::AddHandler(new wxGIFHandler);
260 
261   wxFileSystem::AddHandler(new wxZipFSHandler);
262 
263 #ifdef __WXMSW__
264   wxString oldWorkingDir = wxGetCwd();
265   if (!wxGetEnv(wxT("BUILD_DIR"), NULL))
266   {
267     wxString dir = wxPathOnly(wxStandardPaths::Get().GetExecutablePath());
268     if(dir != wxEmptyString)
269       wxSetWorkingDirectory(wxPathOnly(wxStandardPaths::Get().GetExecutablePath()));
270   }
271   /* Add private jsMath fonts, if they exist */
272 #if wxCHECK_VERSION(3, 1, 1)
273   wxString fontPrefix = m_dirstruct.FontDir() + wxT("/");
274   if (wxFileExists(fontPrefix + wxT(CMEX10) + wxT(".ttf"))) wxFont::AddPrivateFont(fontPrefix + wxT(CMEX10) + wxT(".ttf"));
275   if (wxFileExists(fontPrefix + wxT(CMSY10) + wxT(".ttf"))) wxFont::AddPrivateFont(fontPrefix + wxT(CMSY10) + wxT(".ttf"));
276   if (wxFileExists(fontPrefix + wxT(CMR10) + wxT(".ttf")))  wxFont::AddPrivateFont(fontPrefix + wxT(CMR10) + wxT(".ttf"));
277   if (wxFileExists(fontPrefix + wxT(CMMI10) + wxT(".ttf"))) wxFont::AddPrivateFont(fontPrefix + wxT(CMMI10) + wxT(".ttf"));
278   if (wxFileExists(fontPrefix + wxT(CMTI10) + wxT(".ttf"))) wxFont::AddPrivateFont(fontPrefix + wxT(CMTI10) + wxT(".ttf"));
279 
280   /* Add private Libertine fonts, if they exist */
281   if (wxFileExists(fontPrefix + wxT(LIBERTINE1)))
282 	  wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE1));
283   if (wxFileExists(fontPrefix + wxT(LIBERTINE2))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE2));
284   if (wxFileExists(fontPrefix + wxT(LIBERTINE3))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE3));
285   if (wxFileExists(fontPrefix + wxT(LIBERTINE4))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE4));
286   if (wxFileExists(fontPrefix + wxT(LIBERTINE5))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE5));
287   if (wxFileExists(fontPrefix + wxT(LIBERTINE6))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE6));
288   if (wxFileExists(fontPrefix + wxT(LIBERTINE7))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE7));
289   if (wxFileExists(fontPrefix + wxT(LIBERTINE8))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE8));
290   if (wxFileExists(fontPrefix + wxT(LIBERTINE9))) wxFont::AddPrivateFont(fontPrefix + wxT(LIBERTINE9));
291 #endif
292   wxSetWorkingDirectory(oldWorkingDir);
293 
294 #endif
295 
296 #if defined __WXOSX__
297   wxString path;
298   wxGetEnv(wxT("PATH"), &path);
299   wxSetEnv(wxT("PATH"), path << wxT(":/usr/local/bin"));
300 
301   wxApp::SetExitOnFrameDelete(false);
302   wxMenuBar::SetAutoWindowMenu(true);
303   wxMenuBar *menuBar = new wxMenuBar;
304   // Enables the window list on MacOs.
305   menuBar->SetAutoWindowMenu(true);
306   wxMenu *fileMenu = new wxMenu;
307   fileMenu->Append(wxID_NEW, _("&New\tCtrl+N"));
308   fileMenu->Append(wxID_OPEN, _("&Open\tCtrl+O"));
309   menuBar->Append(fileMenu, _("File"));
310   wxMenuBar::MacSetCommonMenuBar(menuBar);
311   Connect(wxID_EXIT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MyApp::OnFileMenu));
312 #endif
313 
314   if (cmdLineParser.Found(wxT("v")))
315   {
316     std::cout << "wxMaxima ";
317     std::cout << GITVERSION;
318 #if defined(WXMAXIMA_GIT_VERSION)
319     std::cout << " (Git version: " << WXMAXIMA_GIT_VERSION << ")";
320 #endif
321     std::cout << "\n";
322     exit(0);
323   }
324 
325   if (cmdLineParser.Found(wxT("b")))
326   {
327     evalOnStartup = true;
328     exitAfterEval = true;
329   }
330 
331   if (cmdLineParser.Found(wxT("e")))
332     evalOnStartup = true;
333 
334   bool windowOpened = false;
335 
336   if (cmdLineParser.Found(wxT("o"), &file))
337   {
338     wxFileName FileName = file;
339     FileName.MakeAbsolute();
340     wxString CanonicalFilename = FileName.GetFullPath();
341     NewWindow(wxString(CanonicalFilename), evalOnStartup, exitAfterEval);
342     windowOpened = true;
343   }
344 
345   if(cmdLineParser.GetParamCount() > 0)
346   {
347     for (unsigned int i=0; i < cmdLineParser.GetParamCount(); i++)
348     {
349       wxFileName FileName = cmdLineParser.GetParam(i);
350       FileName.MakeAbsolute();
351 
352       wxString CanonicalFilename = FileName.GetFullPath();
353       NewWindow(CanonicalFilename, evalOnStartup, exitAfterEval);
354     }
355     windowOpened = true;
356   }
357 
358   if(!windowOpened)
359     NewWindow();
360 
361   // Now we can finally send our debug output to a window without making
362   // std::cerr confusing the mac.
363   wxString logMessagesSoFar = noStdErr.GetBuffer();
364   if(!logMessagesSoFar.IsEmpty())
365     wxLogMessage("Log messages during early startup: " + logMessagesSoFar);
366   return true;
367 }
368 
OnExit()369 int MyApp::OnExit()
370 {
371   return 0;
372 }
373 
OnRun()374 int MyApp::OnRun()
375 {
376   wxApp::OnRun();
377   return 0;
378 }
379 
NewWindow(wxString file,bool evalOnStartup,bool exitAfterEval,unsigned char * wxmData,int wxmLen)380 void MyApp::NewWindow(wxString file, bool evalOnStartup, bool exitAfterEval, unsigned char *wxmData, int wxmLen)
381 {
382   int numberOfWindows = m_topLevelWindows.size();
383 
384   wxString title = _("wxMaxima");
385   if (file.Length() > 0)
386     title = file;
387 
388   if (numberOfWindows > 1)
389     title = wxString::Format(_("wxMaxima %d"), numberOfWindows);
390 
391   wxMaxima *frame = new wxMaxima(NULL, -1, &m_locale, title, file);
392   if (wxmData)
393   {
394     // Unzip the .wxm file
395     wxMemoryInputStream istream(wxmData, wxmLen);
396     wxZlibInputStream zstream(istream);
397     wxTextInputStream textIn(zstream, "\t", wxConvAuto(wxFONTENCODING_UTF8));
398     wxString initialContents;
399     wxString line;
400     wxString block;
401     while(!zstream.Eof())
402     {
403       line = textIn.ReadLine();
404       if((line.StartsWith("/*")) || (line.EndsWith("*/")))
405       {
406         initialContents += _(block);
407         initialContents += line + "\n";
408         block = wxEmptyString;
409       }
410       else
411         block += line + "\n";
412     }
413     initialContents += _(block);
414     frame->SetWXMdata(initialContents);
415   }
416 
417   frame->ExitAfterEval(exitAfterEval);
418   frame->EvalOnStartup(evalOnStartup);
419   m_topLevelWindows.push_back(frame);
420 
421   SetTopWindow(frame);
422   frame->Show(true);
423   frame->ShowTip(false);
424 }
425 
OnFileMenu(wxCommandEvent & ev)426 void MyApp::OnFileMenu(wxCommandEvent &ev)
427 {
428   switch (ev.GetId())
429   {
430   case wxMaxima::menu_help_numberformats:
431     NewWindow(wxEmptyString, false, false,
432               numberFormats_wxm_gz, numberFormats_wxm_gz_len);
433     break;
434   case wxMaxima::menu_help_3d:
435     NewWindow(wxEmptyString, false, false,
436               displaying3DCurves_wxm_gz, displaying3DCurves_wxm_gz_len);
437     break;
438 
439   case wxMaxima::menu_help_varnames:
440     NewWindow(wxEmptyString, false, false,
441               variableNames_wxm_gz, variableNames_wxm_gz_len);
442     break;
443 
444   case wxMaxima::menu_help_listaccess:
445     NewWindow(wxEmptyString, false, false,
446               fastListAccess_wxm_gz, fastListAccess_wxm_gz_len);
447     break;
448 
449   case wxMaxima::menu_help_fittingData:
450     NewWindow(wxEmptyString, false, false,
451               fittingEquations_wxm_gz, fittingEquations_wxm_gz_len);
452     break;
453   case wxMaxima::menu_help_solving:
454     NewWindow(wxEmptyString, false, false,
455               solvingEquations_wxm_gz, solvingEquations_wxm_gz_len);
456     break;
457   case wxMaxima::menu_help_diffequations:
458     NewWindow(wxEmptyString, false, false,
459               diffEquations_wxm_gz, diffEquations_wxm_gz_len);
460     break;
461   case wxMaxima::menu_help_tolerances:
462     NewWindow(wxEmptyString, false, false,
463               toleranceCalculations_wxm_gz, toleranceCalculations_wxm_gz_len);
464     break;
465     case wxID_NEW:
466     {
467       // Mac computers insist that all instances of a new application need to share
468       // the same process. On all other OSes we create a separate process for each
469       // window: This way if one instance of wxMaxima crashes all the other instances
470       // are still alive.
471 #if defined __WXOSX__
472       NewWindow();
473 #else
474 //      NewWindow();
475       wxString args;
476       if(Configuration::m_configfileLocation_override != wxEmptyString)
477         args += " -f \"" + Configuration::m_configfileLocation_override + "\"";
478       if(Configuration::m_maximaLocation_override != wxEmptyString)
479         args += " -m \"" + Configuration::m_maximaLocation_override + "\"";
480 
481       wxExecute(wxT("\"") + wxStandardPaths::Get().GetExecutablePath() + wxT("\"") + args);
482 #endif
483       break;
484     }
485     case wxID_EXIT:
486     {
487       std::list<wxMaxima *>::const_iterator it=m_topLevelWindows.begin();
488       while(it != m_topLevelWindows.end())
489       {
490         if (*it != NULL)
491         {
492           wxCloseEvent *event = new wxCloseEvent(wxEVT_CLOSE_WINDOW);
493           event->SetCanVeto(true);
494           event->SetLoggingOff(false);
495           (*it)->GetEventHandler()->QueueEvent(event);
496         }
497         ++it;
498       }
499     }
500     break;
501   }
502 }
503 
MacNewFile()504 void MyApp::MacNewFile()
505 {
506   wxWindow *frame = GetTopWindow();
507   if (frame == NULL)
508     NewWindow();
509 }
510 
MacOpenFile(const wxString & file)511 void MyApp::MacOpenFile(const wxString &file)
512 {
513   NewWindow(file);
514 }
515