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