1 /**
2  * DFArc main frame
3 
4  * Copyright (C) 2005, 2006  Dan Walma
5  * Copyright (C) 2008, 2010, 2014, 2017  Sylvain Beucler
6 
7  * This file is part of GNU FreeDink
8 
9  * GNU FreeDink is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 3 of the
12  * License, or (at your option) any later version.
13 
14  * GNU FreeDink is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * 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  * <http://www.gnu.org/licenses/>.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include "DFArcFrame.hpp"
29 
30 #include <stdio.h>
31 
32 #include <wx/file.h>
33 #include <wx/filename.h>
34 #include <wx/filefn.h>
35 #include <wx/image.h>
36 
37 #include <wx/msgdlg.h>
38 #include <wx/filedlg.h>
39 
40 #include <wx/listbox.h>
41 #include <wx/clntdata.h>
42 
43 #include <wx/process.h>
44 
45 #include "IOUtils.hpp"
46 #include "InstallVerifyFrame.hpp"
47 #include "Options.hpp"
48 
49 #include "Config.hpp"
50 #include "RecursiveDelete.hpp"
51 #include "Package.hpp"
52 #include "DMod.hpp"
53 #include "icon_xpm.hpp"
54 
55 #define LOGO_WIDTH 160
56 #define LOGO_HEIGHT 120
57 
58 
59 class MonitorDinkExit : public wxProcess
60 {
61 public:
MonitorDinkExit(Config * mConfig)62   MonitorDinkExit(Config *mConfig) : wxProcess(), mConfig(mConfig) {}
63   virtual void OnTerminate(int pid, int status);
64 private:
65   Config *mConfig;
66 };
67 
OnTerminate(int pid,int status)68 void MonitorDinkExit::OnTerminate(int pid, int status)
69 {
70   if (status == -1)
71     ::wxMessageBox(wxString::Format(_("Dink Smallwood ('%s') was not found on your computer."
72 				      " Please configure the Dink program name in the Options menu."),
73 				    mConfig->mDinkExe.c_str(), wxICON_ERROR, this),
74 		   _("Error"));
75   else if (status != 0)
76     ::wxMessageBox(wxString::Format(_("Dink Smallwood failed! Error code %d."),
77   				    status, wxICON_EXCLAMATION, this),
78   		   _("Error"));
79 }
80 
81 
82 class MonitorEditorExit : public wxProcess
83 {
84 public:
MonitorEditorExit(Config * mConfig)85   MonitorEditorExit(Config *mConfig) : wxProcess(), mConfig(mConfig) {}
86   virtual void OnTerminate(int pid, int status);
87 private:
88   Config *mConfig;
89 };
90 
OnTerminate(int pid,int status)91 void MonitorEditorExit::OnTerminate(int pid, int status)
92 {
93   if (status == -1)
94     ::wxMessageBox(wxString::Format(_("The editor ('%s') was not found on your computer."
95 				      " Please configure the editor program name in the Options menu."),
96 				    mConfig->mEditorExe.c_str()),
97 		   _("Error"));
98   else if (status != 0)
99     ::wxMessageBox(_("Error while running the editor"), _("Error"));
100 }
101 
102 
103 /**
104  * Custom paint method to display the animation efficiently.
105  * Cf. doc/animation.txt for details.
106  */
107 #define TIMER_ID 1  // arbitrary, maybe there's a better way
108 #define FPS 50
109 class DFAnimationPanel : public wxPanel
110 {
111 private:
112   double pos;
113   double dpos;
114   DFArcFrame* frame;
115 public:
DFAnimationPanel(DFArcFrame * frame,wxWindow * parent)116   DFAnimationPanel(DFArcFrame* frame, wxWindow* parent) :
117     wxPanel(parent, wxID_ANY),
118     frame(frame),
119     pos(.0), dpos(.0)
120   { }
121 
setIndex(int idx)122   void setIndex(int idx)
123   {
124     dpos = (idx+1) * LOGO_HEIGHT;
125   }
126 
OnPaint(wxPaintEvent &)127   void OnPaint(wxPaintEvent &)
128   {
129     /* Note: this->IsDoubleBuffered() == 1 under Gtk.  Under woe it
130        reports 0, but when I clear the surface I don't any flickering,
131        so it must be double-buffered somewhere too. */
132     wxPaintDC dst(this);
133     // Clear surface - unneeded actually
134     //dst.SetBrush(*wxWHITE_BRUSH);
135     //dst.DrawRectangle(0, 0, LOGO_WIDTH, LOGO_HEIGHT);
136     // Draw logo
137     wxMemoryDC src(frame->mAllLogos);
138     dst.Blit(0, 0, LOGO_WIDTH, LOGO_HEIGHT, &src, 0, rint(pos));
139   }
140 
141   /**
142    * Update the animation parameters regularly.
143    */
OnIdle(wxIdleEvent &)144   void OnIdle(wxIdleEvent &)
145   {
146     if (fabs(pos - dpos) < .5)
147       // destination reached, taking rounding into account.
148       return;
149 
150     // Simple easing
151     pos += (dpos - pos) / 7;
152 
153     Refresh(/*eraseBackground=*/false);
154 
155     wxMilliSleep(1000.0/FPS); // ms
156   }
157   DECLARE_EVENT_TABLE()
158 };
BEGIN_EVENT_TABLE(DFAnimationPanel,wxPanel)159 BEGIN_EVENT_TABLE(DFAnimationPanel, wxPanel)
160   EVT_PAINT(DFAnimationPanel::OnPaint)
161   EVT_IDLE(DFAnimationPanel::OnIdle)
162 END_EVENT_TABLE()
163 
164 
165 // FRAME EVENT TABLE
166 BEGIN_EVENT_TABLE(DFArcFrame, wxFrame)
167 EVT_SHOW(DFArcFrame::onShow)
168 
169 EVT_MENU(ID_FileInstall, DFArcFrame::Install)
170 EVT_MENU(ID_Download, DFArcFrame::onDownload)
171 EVT_MENU(wxID_EXIT, DFArcFrame::OnQuit)
172 
173 EVT_MENU(ID_Refresh, DFArcFrame::onRefresh)
174 EVT_MENU(ID_Browse, DFArcFrame::onBrowse)
175 EVT_MENU(ID_Uninstall, DFArcFrame::uninstall)
176 EVT_MENU(ID_Options, DFArcFrame::showOptions)
177 
178 EVT_MENU(ID_IntroductionText, DFArcFrame::showIntroductionText)
179 EVT_MENU(ID_Walkthroughs, DFArcFrame::onWalkthroughs)
180 EVT_MENU(ID_Forums, DFArcFrame::onForums)
181 EVT_MENU(wxID_ABOUT, DFArcFrame::OnAbout)
182 
183 EVT_BUTTON(ID_Play, DFArcFrame::OnPlay)
184 EVT_BUTTON(ID_Edit, DFArcFrame::onEdit)
185 EVT_BUTTON(ID_Package, DFArcFrame::onPackage)
186 
187 EVT_LISTBOX(ID_DmodTitleList, DFArcFrame::OnEvtListBox)
188 
189 EVT_CHECKBOX(ID_Truecolor, DFArcFrame::OnSetPlayOption)
190 EVT_CHECKBOX(ID_Windowed, DFArcFrame::OnSetPlayOption)
191 EVT_CHECKBOX(ID_Sound, DFArcFrame::OnSetPlayOption)
192 EVT_CHECKBOX(ID_Joystick, DFArcFrame::OnSetPlayOption)
193 EVT_CHECKBOX(ID_Debug, DFArcFrame::OnSetPlayOption)
194 EVT_CHECKBOX(ID_V107, DFArcFrame::OnSetPlayOption)
195 END_EVENT_TABLE()
196 
197 DFArcFrame::DFArcFrame() :
198 DFArcFrame_Base(NULL, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE),
199   mNoDmods(false),
200   mSelectedDModIndex(wxNOT_FOUND)
201 {
202   mConfig = Config::GetConfig();
203 
204   PrepareGUI();
205   refreshDmodList();
206   mConfig->Update();
207   return;
208 }
209 
OnQuit(wxCommandEvent & aCommandEvent)210 void DFArcFrame::OnQuit(wxCommandEvent& aCommandEvent)
211 {
212     this->Close();
213 }
214 
OnAbout(wxCommandEvent & aCommandEvent)215 void DFArcFrame::OnAbout(wxCommandEvent& aCommandEvent)
216 {
217   // Use a wxString for concatenation with the date.
218   wxString version = wxT(PACKAGE_VERSION);
219   wxString wxs = wxString::Format(_(
220     "DFArc version %s\n"
221     "Copyright (C) 2004  Andrew Reading (merlin)\n"
222     "Copyright (C) 2005, 2006  Dan Walma (redink1)\n"
223     "Copyright (C) 2008-%04d  Sylvain Beucler (Beuc)\n"
224     "Powered by bzip2 (bzip.org) and wxWidgets (wxwidgets.org)"),
225 				  version.c_str(), 2018);
226   wxMessageBox(wxs, _("About DFArc v3"), wxOK | wxICON_INFORMATION, this);
227 }
228 
229 // Display or hide "Edit" and "Package" buttons
showDeveloperButtons(bool visible)230 void DFArcFrame::showDeveloperButtons(bool visible)
231 {
232   wxSizer *mButtonSizer = mEditButton->GetContainingSizer();
233   wxSizer *mImageSizer = mAnimationPanel->GetContainingSizer();
234   mImageSizer->Show(mButtonSizer, visible);
235   // Re-layout, starting with expanding mDModListBox
236   mDModListBox->GetContainingSizer()->Layout();
237 }
238 
CreateTextLogo(wxString text)239 static wxBitmap CreateTextLogo(wxString text)
240 {
241   wxBitmap lLogo = wxBitmap(LOGO_WIDTH, LOGO_HEIGHT);
242   wxMemoryDC dc;
243   dc.SelectObject(lLogo);
244   dc.SetBackground(*wxBLACK_BRUSH);
245   dc.SetTextForeground(*wxWHITE);
246   dc.Clear();
247   // Center text
248   // (There seem to be a bug in GetTextExtent in wx2.8.10, the
249   // calculated width is smaller than the drawned width)
250   wxSize size = dc.GetTextExtent(text);
251   dc.DrawText(text,
252 	      (LOGO_WIDTH - size.GetWidth()) / 2,
253 	      (LOGO_HEIGHT - size.GetHeight()) / 2);
254   dc.SelectObject(wxNullBitmap);
255   return lLogo;
256 }
257 
258 // GUI for main screen (with no args).
PrepareGUI()259 void DFArcFrame::PrepareGUI()
260 {
261   // Replace wxGlade's plain wxPanel with our custom class -
262   // cf. DFArcFrame_Base.cpp
263   wxSizer *sizer = mAnimationPanel->GetContainingSizer();
264   sizer->Detach(mAnimationPanel);
265   delete mAnimationPanel;
266   mAnimationPanel = new DFAnimationPanel(this, panel_1);
267   mAnimationPanel->SetMinSize(wxSize(160, 120));
268   sizer->Insert(1, mAnimationPanel, 0, wxLEFT|wxALIGN_BOTTOM|wxALIGN_CENTER_HORIZONTAL, 10);
269 
270   // Features not supported by wxGlade:
271   SetIcon(wxIcon(dink_xpm));
272   mSplitter->SetSashGravity(0.5); // proportional outer resize
273   // Avoid 'unsplit' (where one of the pane is hidden)
274   mSplitter->SetMinimumPaneSize(20);
275   // Manual contraints
276   mDModListBox->SetMinSize(wxSize(150, 165));
277   mDmodDescription->SetMinSize(wxSize(-1, 220));
278   this->SetMinSize(this->GetBestSize());
279   mSplitter->SetSashPosition(0); // reset to middle pos (wx3 bug?)
280   this->SetSize(this->GetMinSize());
281   // user can override size restrictions:
282   mDmodDescription->SetMinSize(wxSize(-1, -1));
283 
284   // Optionally show developper buttons
285   showDeveloperButtons(mConfig->mShowDeveloperButtons);
286   // Update the checkboxes
287   mTrueColor->SetValue(mConfig->mTrueColorValue);
288   mWindowed->SetValue(mConfig->mWindowedValue);
289   mSound->SetValue(mConfig->mSoundValue);
290   mJoystick->SetValue(mConfig->mJoystickValue);
291   mDebug->SetValue(mConfig->mDebugValue);
292   mV107->SetValue(mConfig->mV107Value);
293 
294   // Default logo (currently all black with a question mark)
295   /* TRANSLATORS: please make this SHORT, possibly rephrasing as "<
296      Choose!". This is included in the 160x120px logo box in the main
297      window and it doesn't word-wrap. */
298   mDefaultLogoBitmap = CreateTextLogo(_("< Pick a D-Mod"));
299   ((DFAnimationPanel*)mAnimationPanel)->setIndex(-1);
300 }
301 
updateDescription()302 void DFArcFrame::updateDescription()
303 {
304   wxString lDescription;
305 
306   // Set the current D-Mod directory to the currently selected item's client string
307   DMod cur_dmod = mAvailableDModsList.at(mSelectedDModIndex);
308   mConfig->mSelectedDmod = cur_dmod.GetFullPath();
309   mConfig->Update();
310   mDmodDescription->SetValue(cur_dmod.GetDescription());
311   mStatusBar->SetStatusText(mConfig->mSelectedDmod);
312 }
313 
SelectDModFromListBox()314 void DFArcFrame::SelectDModFromListBox()
315 {
316   int lb_index = mDModListBox->GetSelection();
317   if (lb_index != wxNOT_FOUND)
318     {
319       Integer *I = (Integer*)mDModListBox->GetClientObject(lb_index);
320       mSelectedDModIndex = I->i;
321       mConfig->mSelectedDmod = mAvailableDModsList.at(mSelectedDModIndex).GetFullPath();
322       mConfig->Update();
323       updateDescription();
324       mPlayButton->Enable();
325       mEditButton->Enable();
326       mPackageButton->Enable();
327 
328       // Fill-in translations list
329       wxString cur_locale_name = mConfig->mForceLocale;
330       if (mConfig->mForceLocale == wxEmptyString) {
331 	const wxLanguageInfo* li = wxLocale::GetLanguageInfo(wxLocale::GetSystemLanguage());
332 	if (li != NULL)
333 	  cur_locale_name = li->CanonicalName;
334       }
335 
336       mGameLocaleList->Clear();
337       wxArrayString mo_files;
338       wxString mo_dir = mConfig->mSelectedDmod + wxFileName::GetPathSeparator() + wxT("l10n");
339       if (wxDir::Exists(mo_dir)) {
340 	wxDir::GetAllFiles(mo_dir, &mo_files, wxT("*.mo"));
341 	mo_files.Sort();
342 	for (int i = 0; i < mo_files.Count(); i++)
343 	  {
344 	    // dmod/l10n/fr/LC_MESSAGES/dmod.mo
345 	    wxFileName mo(mo_files.Item(i));
346 	    mo.RemoveLastDir();
347 	    wxArrayString dirs = mo.GetDirs();
348 	    wxString dir = dirs.Item(dirs.Count()-1);
349 	    wxString label = dir;
350 	    wxString locale_name = dir;
351 	    const wxLanguageInfo* li = wxLocale::FindLanguageInfo(locale_name);
352 	    if (li != NULL) {
353 	      label += wxT(" - ") + wxString(wxGetTranslation(li->Description));
354 	      locale_name = li->CanonicalName;
355 	    }
356 	    mGameLocaleList->Append(label);
357 	    mGameLocaleList->SetClientObject(i, new ClientDataString(locale_name));
358 	    if (cur_locale_name == locale_name)
359 	      mGameLocaleList->Select(i);
360 	  }
361       }
362       if (mGameLocaleList->GetCount() == 0) {
363 	mGameLocaleList->Append(_("No translations"));
364 	mGameLocaleList->Select(0);
365 	mGameLocaleList->Disable();
366       } else {
367 	// Not displaying the default language explicitely, because it
368 	// makes the user think that a translation is always available.
369 	// mGameLocaleList->Insert(wxString(_("Default language")) + wxT(" (") + cur_locale_name + wxT(")"), 0);
370 	mGameLocaleList->Insert(_("Don't translate"), 0);
371 	mGameLocaleList->SetClientObject(0, new ClientDataString(wxT("C")));
372 	mGameLocaleList->Enable();
373 	if (mGameLocaleList->GetSelection() == wxNOT_FOUND)
374 	  mGameLocaleList->Select(0);
375       }
376 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
377       // work-around wx3 bug -> default height=6 despite BestSize=21
378       mGameLocaleList->SetSize(wxSize(-1, mGameLocaleList->GetBestSize().y+1));
379 #endif
380     }
381   ((DFAnimationPanel*)mAnimationPanel)->setIndex(lb_index);
382 }
383 
OnEvtListBox(wxCommandEvent & Event)384 void DFArcFrame::OnEvtListBox(wxCommandEvent &Event)
385 {
386   SelectDModFromListBox();
387   return;
388 }
389 
390 // Function for Install menu entry
Install(wxCommandEvent & aCommandEvent)391 void DFArcFrame::Install(wxCommandEvent& aCommandEvent)
392 {
393   wxString description = _("D-Mod files (*.dmod)");
394   wxFileDialog FileDlg(0, _("Select a .dmod file"), _T(""), _T(""), description + _T("|*.dmod"),
395         wxFD_OPEN | wxFD_FILE_MUST_EXIST);
396 
397   if (FileDlg.ShowModal() == wxID_OK)
398     {
399       InstallVerifyFrame lTemp(FileDlg.GetPath());
400       lTemp.ShowModal();
401       // Update the D-Mod list
402       refreshDmodList();
403     }
404 }
405 
onPackage(wxCommandEvent & Event)406 void DFArcFrame::onPackage(wxCommandEvent &Event)
407 {
408   if (mSelectedDModIndex != wxNOT_FOUND) {
409     DMod cur_dmod = mAvailableDModsList.at(mSelectedDModIndex);
410     Package* lPackage = new Package(cur_dmod);
411     lPackage->ShowModal();
412     lPackage->Destroy();
413   }
414 }
415 
OnSetPlayOption(wxCommandEvent & Event)416 void DFArcFrame::OnSetPlayOption(wxCommandEvent &Event)
417 {
418   // Set variables
419   mConfig->mTrueColorValue = mTrueColor->IsChecked();
420   mConfig->mWindowedValue = mWindowed->IsChecked();
421   mConfig->mSoundValue = mSound->IsChecked();
422   mConfig->mJoystickValue = mJoystick->IsChecked();
423   mConfig->mDebugValue = mDebug->IsChecked();
424   mConfig->mV107Value = mV107->IsChecked();
425   mConfig->Update();
426   return;
427 }
428 
BuildCommand(enum program progname)429 wxString DFArcFrame::BuildCommand(enum program progname)
430 {
431   wxString lCommand;
432   wxString executable;
433   if (progname == GAME)
434     executable = mConfig->mDinkExe;
435   else
436     executable = mConfig->mEditorExe;
437 
438   // Attempt to use the binary in the Dink directory (important for
439   // disambiguation under woe, where the PATH includes the current
440   // DFArc _binary_ directory (not cwd()) by default).
441   wxString test_dinkref = mConfig->GetDinkrefDir() + wxFileName::GetPathSeparator() + executable;
442   if (::wxFileExists(test_dinkref))
443     lCommand = test_dinkref;
444   else
445     lCommand = executable;
446 
447   if (mConfig->mDebugValue == true)
448     lCommand += _T(" -debug ");
449   if (mConfig->mSoundValue == false)
450     lCommand += _T(" -nosound ");
451   if (mConfig->mTrueColorValue == true)
452     lCommand += _T(" -truecolor ");
453   if (mConfig->mWindowedValue == true)
454     lCommand += _T(" -window ");
455   if (mConfig->mWriteIniValue == false)
456     lCommand += _T(" -noini ");
457   if (mConfig->mJoystickValue == false)
458     lCommand += _T(" -nojoy ");
459   if (mConfig->mV107Value == true)
460     lCommand += _T(" --v1.07 ");
461 
462   /* Specify the directory, as short as possible*/
463   wxString dinkref = mConfig->GetDinkrefDir();
464   wxFileName dmod_dir(mConfig->mSelectedDmod);
465   dmod_dir.MakeRelativeTo(dinkref);
466   if (dmod_dir.GetFullPath().StartsWith(_T("..")))
467     dmod_dir.MakeAbsolute(dinkref);
468   if (dmod_dir.GetFullPath().IsSameAs(_T("dink")))
469     dmod_dir = wxEmptyString;
470   if (!dmod_dir.GetFullName().IsEmpty())
471     {
472       wxString fullpath = dmod_dir.GetFullPath();
473       if (fullpath.Find(wxT(' ')) != wxNOT_FOUND)
474 	// Only do that if necessary, quoting doesn't work with the
475 	// original engine for some reason
476 	fullpath = _T("\"") + dmod_dir.GetFullPath() + _T("\"");
477       lCommand += _T(" -game ") + fullpath;
478     }
479 
480   /* Always specify the dinkref dir, because we don't know what
481      FreeDink's default refdir is. (/usr/local?  /usr?...) */
482   /* Note: this is ignored by Seth and Dan's versions */
483   lCommand += _T(" --refdir \"") + mConfig->GetDinkrefDir() + _T("\"");
484 
485   /* If at a point we need better escaping, it is suggested to replace
486      spaces with '\ ' under GNU/Linux and '" "' under woe -
487      http://trac.wxwidgets.org/ticket/4115#comment:22 . I wish the
488      wxWidgets dev properly fixed it with a wxExecute(wxArrayString)
489      version... */
490 
491   return lCommand;
492 }
493 
OnPlay(wxCommandEvent & Event)494 void DFArcFrame::OnPlay(wxCommandEvent &Event)
495 {
496   wxString lCommand = BuildCommand(GAME);
497 
498 
499   // Configure locale
500   wxString locale_name = mConfig->mForceLocale;
501   int idx = mGameLocaleList->GetSelection();
502   if (idx != wxNOT_FOUND && mGameLocaleList->HasClientObjectData())
503     {
504       wxClientData* wcd = mGameLocaleList->GetClientObject(idx);
505       if (wcd != NULL)
506 	locale_name = ((ClientDataString*)wcd)->s;
507     }
508 
509   wxEnvVariableHashMap env;
510   wxGetEnvMap(&env);
511 
512   if (locale_name != wxEmptyString)
513     {
514       // locale_name is in the form 'xx_YY', e.g. 'da_DK'
515 
516       // (gettext.info.gz)The LANGUAGE variable: LANGUAGE > LC_ALL > LC_* > LANG
517       env.erase("LANGUAGE");
518       env.erase("LC_ALL");
519 
520       // Locales need to be precisely set, including the encoding,
521       // even if in the case of FreeDink, we don't need it.
522       // http://lists.gnu.org/archive/html/bug-gnu-utils/2010-10/msg00018.html
523 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
524       // Under woe, things are pretty sloppy, no worry - except that
525       // you won't get any warning if you lack fonts or whatever is
526       // needed for your language.
527       env["LC_ALL"] = locale_name;
528 #else
529       // Look through $(locale -a) and check if there's a matching
530       // locale, otherwise explain to the user that he needs to
531       // install the locale.  Not sure if there's a similar method
532       // under woe to get the installed locales.
533       wxArrayString installed_locales;
534       long code = ::wxExecute("locale -a", installed_locales, wxEXEC_SYNC);
535       if (code == 0) // good exit code
536 	{
537 	  bool found = false;
538 	  if (!found)
539 	    {
540 	      for (int i = 0; i < installed_locales.Count(); i++)
541 		{
542 		  if (installed_locales.Item(i).StartsWith(locale_name))
543 		    {
544 		      found = true;
545 		      locale_name = installed_locales.Item(i);
546 		      env["LC_ALL"] = locale_name;
547 		      break;
548 		    }
549 		}
550 	    }
551 	  if (!found)
552 	    {
553 	      // Try with the language name  / without the country name
554 	      wxString language_name = locale_name.Mid(0,2);
555 	      for (int i = 0; i < installed_locales.Count(); i++)
556 		{
557 		  if (installed_locales.Item(i).StartsWith(language_name))
558 		    {
559 		      found = true;
560 		      locale_name = installed_locales.Item(i);
561 		      env["LC_ALL"] = locale_name;
562 		      break;
563 		    }
564 		}
565 	    }
566 	  if (!found)
567 	    {
568 	      // Poor-man's / fallback i18n:
569 	      // - LC_CTYPE and LC_MESSAGES to en_US.UTF-8 (if available)
570 	      // - LANGUAGE to the locale
571 	      found = (installed_locales.Index("en_US.UTF-8", false) != wxNOT_FOUND);
572 	      if (!found)
573 		found = (installed_locales.Index("en_US.utf8", false) != wxNOT_FOUND);
574 	      if (found)
575 		{
576 		  env["LC_CTYPE"] = "en_US.UTF-8";
577 		  env["LC_MESSAGES"] = "en_US.UTF-8";
578 		  env["LANGUAGE"] = locale_name;
579 		}
580 	    }
581 	  if (!found)
582 	    ::wxMessageBox(wxString::Format(_("The '%s' locale is not installed on your computer"
583 					      " (locales tells the computer how to manage a language)."
584 					      " You need to install it - check your system documentation."),
585 					    locale_name.c_str()), _("Warning"), wxICON_EXCLAMATION, this);
586 	  // try anyway; this also prevent fallback-ing to the system language
587 	  env["LC_ALL"] = locale_name;
588 	}
589 #endif
590     }
591 
592 
593   // Start the child process.
594   long code = 0;
595 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
596   wxLogNull logNo;  // remove redundant "Execution of command 'xxx' failed" popup
597 #endif
598   wxExecuteEnv exec_env;
599   exec_env.env = env;
600   MonitorDinkExit* process = new MonitorDinkExit(mConfig);
601   if ((code = ::wxExecute(lCommand, wxEXEC_ASYNC, process, &exec_env)) == 0)
602     {
603       // On woe, returns immediately if cannot run the process (not using fork&exec)
604       ::wxMessageBox(wxString::Format(_("Dink Smallwood ('%s') was not found on your computer."
605 					" Please configure the Dink program name in the Options menu."),
606 				      mConfig->mDinkExe.c_str(), wxICON_ERROR, this),
607 		     _("Error"));
608       delete process;
609     }
610   else if (mConfig->mCloseDfarcOnPlay)
611     {
612       // since we're async, DFArc will have exited before we know that e.g. Dink couldn't be found
613       this->Close(true);
614     }
615 }
616 
onEdit(wxCommandEvent & aEvent)617 void DFArcFrame::onEdit( wxCommandEvent& aEvent )
618 {
619   if (mConfig->mWarnOnEdit == true)
620     {
621       if (::wxMessageBox(_("Dinkedit saves all changes automatically."
622 			   " Altering maps can ruin the game."
623 			   " Are you sure you want to continue?"),
624 			 _("Warning"),
625 			 wxOK | wxCANCEL | wxICON_INFORMATION, this) == wxCANCEL)
626         {
627 	  return;
628         }
629       else
630         {
631 	  // Don't display the warning again
632 	  mConfig->mWarnOnEdit = false;
633 	  mConfig->Update();
634         }
635     }
636 
637 
638   wxString lCommand = BuildCommand(EDITOR);
639 
640   // Start the child process.
641   long code = 0;
642 
643 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__ || defined __EMX__ || defined __DJGPP__
644   wxLogNull logNo;  // remove redundant "Execution of command 'xxx' failed" popup
645 #endif
646   MonitorEditorExit* process = new MonitorEditorExit(mConfig);
647   if ((code = ::wxExecute(lCommand, wxEXEC_ASYNC, process)) == 0)
648     ::wxMessageBox(wxString::Format(_("The editor ('%s') was not found on your computer."
649 				      " Please configure the editor program name in the Options menu."),
650 				    mConfig->mEditorExe.c_str()),
651 		   _("Error"));
652 }
653 
onRefresh(wxCommandEvent & aEvent)654 void DFArcFrame::onRefresh( wxCommandEvent& aEvent )
655 {
656   refreshDmodList();
657   return;
658 }
659 
onDownload(wxCommandEvent & aEvent)660 void DFArcFrame::onDownload( wxCommandEvent& aEvent )
661 {
662   ::wxLaunchDefaultBrowser(_T("http://www.dinknetwork.com/"));
663 }
664 
onWalkthroughs(wxCommandEvent & aEvent)665 void DFArcFrame::onWalkthroughs( wxCommandEvent& aEvent )
666 {
667   ::wxLaunchDefaultBrowser(_T("http://solutions.dinknetwork.com/"));
668 }
669 
onForums(wxCommandEvent & aEvent)670 void DFArcFrame::onForums( wxCommandEvent& aEvent )
671 {
672   ::wxLaunchDefaultBrowser(_T("http://www.dinknetwork.com/forum.cgi"));
673 }
674 
onBrowse(wxCommandEvent & aEvent)675 void DFArcFrame::onBrowse( wxCommandEvent& aEvent )
676 {
677   wxString cur_dmod_dir = mConfig->mSelectedDmod;
678   if (mConfig->mPreferredFileBrowserExe.IsEmpty())
679     {
680       // Try default browser(s)
681 #if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
682       // Use '\' to force opening the directory, not dink.exe...
683       ::ShellExecute(0, _T("open"), (cur_dmod_dir + wxT("\\")).c_str(), NULL, NULL, SW_SHOW);
684 #else
685       /* Crudely escape spaces, since wxWidgets 2.8 doesn't have a
686 	 proper way to separate command line arguments */
687       cur_dmod_dir.Replace(wxT(" "), wxT("\\ "), true);
688       if (::wxExecute(_T("xdg-open ") + cur_dmod_dir) < 0) // FreeDesktop
689 	if (::wxExecute(_T("nautilus ") + cur_dmod_dir) < 0) // Gnome
690 	  if (::wxExecute(_T("konqueror ") + cur_dmod_dir) < 0) // KDE
691 	    if (::wxExecute(_T("thunar ") + cur_dmod_dir) < 0) // Xfce
692 	      ::wxMessageBox(_("Could not find a file manager"
693 			       " (tried 'xdg-open', 'nautilus', 'konqueror' and 'thunar')"),
694 			     _("Error"), wxICON_ERROR);
695 #endif
696     }
697   else
698     {
699       if (::wxExecute(mConfig->mPreferredFileBrowserExe + _T(" ") + cur_dmod_dir) < 0)
700 	::wxMessageBox(wxString::Format(_("Cannot start '%s', please check your"
701 					  " configuration in the Options window."),
702 					mConfig->mPreferredFileBrowserExe.c_str()),
703 		       _("Error"), wxICON_ERROR);
704     }
705   return;
706 }
707 
showIntroductionText(wxCommandEvent & aEvent)708 void DFArcFrame::showIntroductionText(wxCommandEvent& aEvent)
709 {
710   ::wxMessageBox(_("Welcome to DFArc, the Dink Smallwood front end!\n"
711 "\n"
712 "You can choose to play the original game (Dink Smallwood) or"
713 " Dink-Modules (D-Mods) which contain new adventures.\n"
714 "\n"
715 "After completing the main game, give some D-Mods a try.\n"
716 "There are hundreds of them, just click File-Download D-Mods."),
717 		 _("Introduction"),
718 		 wxOK | wxICON_INFORMATION, this);
719   return;
720 }
721 
onShow(wxShowEvent & aEvent)722 void DFArcFrame::onShow(wxShowEvent& aEvent)
723 {
724   if (mConfig->mShowIntroductionText == true)
725     {
726       wxCommandEvent se;
727       showIntroductionText(se);
728       mConfig->mShowIntroductionText = false;
729       mConfig->Update();
730     }
731   return;
732 }
733 
refreshDmodList()734 void DFArcFrame::refreshDmodList()
735 {
736   mDModListBox->Clear();
737   populateAvailableDModsList();
738 
739   if (mAvailableDModsList.empty() == false)
740     {
741       mNoDmods = false;
742       for (unsigned int i = 0; i < mAvailableDModsList.size(); i++)
743 	{
744 	  // Using 'wxClientData*' as int for simplicity.
745 	  Integer *I = new Integer(i);
746 	  mDModListBox->Append(mAvailableDModsList.at(i).GetTitle(), I);
747 	}
748     }
749   else
750     {
751       mNoDmods = true;
752     }
753   refreshDmodLogos();
754   RestoreListBoxFromConfig();
755 }
756 
757 /**
758  * Create a big image with all the D-Mods logos, used for the
759  * transition effect that happens when selecting a different D-Mod.
760  */
refreshDmodLogos()761 void DFArcFrame::refreshDmodLogos()
762 {
763   mAllLogos = wxBitmap(LOGO_WIDTH, (mDModListBox->GetCount()+1) * LOGO_HEIGHT);
764   wxMemoryDC lAllDC(mAllLogos);
765   lAllDC.DrawBitmap(mDefaultLogoBitmap, 0,0, false);
766 
767   for (unsigned int i = 0; i < mDModListBox->GetCount(); i++)
768     {
769       Integer *I = (Integer*)mDModListBox->GetClientObject(i);
770       int cur_dmod_index = I->i;
771       DMod cur_dmod = mAvailableDModsList.at(cur_dmod_index);
772 
773       wxBitmap lLogoBitmap;
774 
775       wxString lDmodPreview(cur_dmod.GetFullPath() + _T("/preview.bmp"));
776       IOUtils::ciconvert(lDmodPreview);
777       wxString lDmodTitle(cur_dmod.GetFullPath() + _T("/graphics/title-01.bmp"));
778       IOUtils::ciconvert(lDmodTitle);
779       if (::wxFileExists(lDmodPreview) == true)
780 	{
781 	  lLogoBitmap = wxBitmap(lDmodPreview, wxBITMAP_TYPE_BMP);
782 	}
783       else if (::wxFileExists(lDmodTitle) == true)
784 	{
785 	  wxImage lImage(lDmodTitle);
786 	  lImage.Rescale(LOGO_WIDTH, LOGO_HEIGHT);
787 	  lLogoBitmap = wxBitmap(lImage);
788 	}
789       else
790 	{
791 	  lLogoBitmap = CreateTextLogo(cur_dmod.GetBaseName());
792 	}
793       lLogoBitmap.SetWidth(LOGO_WIDTH);
794       lLogoBitmap.SetHeight(LOGO_HEIGHT);
795 
796       lAllDC.DrawBitmap(lLogoBitmap, 0,(i+1)*LOGO_HEIGHT, false);
797       //mLogoButton->SetBitmap(lLogoBitmap);
798       //wxMemoryDC src(lLogoBitmap);
799       //wxBitmap dst_bitmap = mLogoButton->GetBitmap();
800       //wxMemoryDC dst(dst_bitmap);
801       //dst.Blit(0,0, LOGO_WIDTH, LOGO_HEIGHT, &src, 0,0);
802       //dst.DrawBitmap(lLogoBitmap, 0,0, false);
803       //dst.DrawRectangle(0,0, LOGO_WIDTH,LOGO_HEIGHT);
804       //mLogoButton->SetBitmap(dst_bitmap);
805     }
806 }
807 
808 /**
809  * Select DMod from configuration
810  */
RestoreListBoxFromConfig()811 void DFArcFrame::RestoreListBoxFromConfig()
812 {
813   int lb_index_to_select = wxNOT_FOUND;
814   // Update the selection
815   if (!mConfig->mSelectedDmod.IsEmpty())
816     {
817       for (unsigned int i = 0; i < mDModListBox->GetCount(); i++)
818 	{
819 	  Integer *I = (Integer*)mDModListBox->GetClientObject(i);
820 	  int cur_dmod_index = I->i;
821 	  DMod cur_dmod = mAvailableDModsList.at(cur_dmod_index);
822 
823 	  if (cur_dmod.GetFullPath().IsSameAs(mConfig->mSelectedDmod))
824 	    {
825 	      lb_index_to_select = i;
826 	      break;
827 	    }
828 	}
829       mDModListBox->SetSelection(lb_index_to_select);
830       SelectDModFromListBox();
831     }
832   else
833     {
834       mPlayButton->Disable();
835       mEditButton->Disable();
836       mPackageButton->Disable();
837       ((DFAnimationPanel*)mAnimationPanel)->setIndex(-1);
838     }
839 }
840 
showOptions(wxCommandEvent & aEvent)841 void DFArcFrame::showOptions(wxCommandEvent& aEvent)
842 {
843   Options* lOptions = new Options(mConfig);
844   if (lOptions->ShowModal() == 1)
845     {
846       // Apply configuration changes
847       showDeveloperButtons(mConfig->mShowDeveloperButtons);
848       if (mConfig->mOverrideDinkrefDir)
849 	{
850 	  if (::wxDirExists(mConfig->mSpecifiedDinkrefDir))
851 	    {
852 	      // Get the full path to the directory, in case users type in a non-full path.
853 	      if (::wxSetWorkingDirectory(mConfig->mSpecifiedDinkrefDir))
854 		{
855 		  mConfig->mSpecifiedDinkrefDir = ::wxGetCwd();
856 		}
857 	      else
858 		{
859 		  // If there's no directory, let's not override
860 		  ::wxMessageBox(_("Cannot use the overridden Dink Smallwood directory"
861 				   " - ignoring it. (permission problem?)"),
862 				 _("Configuration error"));
863 		  mConfig->mOverrideDinkrefDir = false;
864 		}
865 	    }
866 	  else
867 	    {
868 	      // If there's no directory, let's not override
869 	      ::wxMessageBox(_("The Dink Smallwood directory you entered does not exist - ignoring it."),
870 			     _("Configuration error"));
871 	      mConfig->mOverrideDinkrefDir = false;
872 	    }
873 	}
874       refreshDmodList();
875     }
876   lOptions->Destroy();
877   return;
878 }
879 
uninstall(wxCommandEvent & aEvent)880 void DFArcFrame::uninstall( wxCommandEvent& aEvent )
881 {
882   if (mConfig->mSelectedDmod == _T("dink"))
883     {
884       wxMessageBox(_("You must select the uninstall option from the start menu to uninstall the main game."),
885 		   _("Uninstall - Error"),
886 		   wxOK | wxICON_INFORMATION, this);
887     }
888   else
889     {
890       int lResult = wxMessageBox(_("Do you want to remove all save game files?"),
891 				 _("Uninstall - Save Game Files"),
892 				 wxYES_NO | wxCANCEL | wxICON_QUESTION, this);
893       if (lResult != wxCANCEL)
894         {
895 	  bool lRemoveSaveGames(false);
896 	  if (lResult == wxYES)
897             {
898 	      lRemoveSaveGames = true;
899             }
900 
901 	  RecursiveDelete lRecursiveDelete(lRemoveSaveGames);
902 	  wxDir* lDir = new wxDir(mConfig->mSelectedDmod);
903 	  lDir->Traverse(lRecursiveDelete);
904 	  delete lDir;
905 
906 	  if (lRecursiveDelete.getError() == false)
907             {
908 	      bool lSuccess(true);
909 	      if (lRemoveSaveGames == true)
910                 {
911 		  if (::wxRmdir(mConfig->mSelectedDmod) == false)
912                     {
913 		      wxLogError(_("Unable to remove D-Mod directory. All other files were removed."));
914 		      lSuccess = false;
915                     }
916                 }
917 	      if (lSuccess == true)
918                 {
919 		  wxMessageBox(_("D-Mod successfully uninstalled"),
920 			       _("Uninstall - Success"),
921 			       wxOK | wxICON_INFORMATION, this);
922                 }
923             }
924 	  mConfig->mSelectedDmod = wxEmptyString;
925 	  refreshDmodList();
926         }
927     }
928   return;
929 }
930 
931 
932 
933 
934 
935 /**
936  * This finds all of the DMods.
937  */
populateAvailableDModsList()938 void DFArcFrame::populateAvailableDModsList()
939 {
940   mAvailableDModsList.clear();
941 
942   wxString* folders = new wxString[2];
943   folders[0] = mConfig->GetDinkrefDir();
944   folders[1] = mConfig->mDModDir;
945 
946   for (int i = 0; i < 2; i++)
947     {
948       if (folders[i].IsEmpty() || !::wxDirExists(folders[i]))
949 	continue;
950       wxDir lDirectory(folders[i]);
951       if (lDirectory.IsOpened() == false)
952 	continue;
953 
954       // Grab dir names.
955       wxString lCurrentFolder;
956       bool cont = lDirectory.GetFirst(&lCurrentFolder, wxEmptyString, wxDIR_DIRS);
957       while (cont)
958 	{
959 	  wxString full_path = folders[i] + wxFileName::GetPathSeparator() + lCurrentFolder;
960 	  if (DMod::IsADModDir(full_path))
961 	      mAvailableDModsList.push_back(DMod(full_path));
962 	  cont = lDirectory.GetNext(&lCurrentFolder);
963 	}
964     }
965 
966   delete[] folders;
967   return;
968 }
969