1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia 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 // Nestopia 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 Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #include "resource/resource.h"
26 #include "NstResourceString.hpp"
27 #include "NstResourceIcon.hpp"
28 #include "NstWindowParam.hpp"
29 #include "NstSystemKeyboard.hpp"
30 #include "NstDialogLauncher.hpp"
31 #include "NstDialogInesHeader.hpp"
32 
33 namespace Nestopia
34 {
35 	namespace Window
36 	{
37 		struct Launcher::Handlers
38 		{
39 			static const MsgHandler::Entry<Launcher> messages[];
40 			static const Menu::CmdHandler::Entry<Launcher> commands[];
41 			static const Control::NotificationHandler::Entry<Launcher> listNotifications[];
42 			static const Control::NotificationHandler::Entry<Launcher> treeNotifications[];
43 		};
44 
45 		const MsgHandler::Entry<Launcher> Launcher::Handlers::messages[] =
46 		{
47 			{ WM_INITDIALOG,  &Launcher::OnInitDialog  },
48 			{ WM_CLOSE,       &Launcher::OnClose       },
49 			{ WM_DESTROY,     &Launcher::OnDestroy     },
50 			{ WM_SIZE,        &Launcher::OnSize        },
51 			{ WM_DROPFILES,   &Launcher::OnDropFiles   }
52 		};
53 
54 		const Menu::CmdHandler::Entry<Launcher> Launcher::Handlers::commands[] =
55 		{
56 			{ IDM_LAUNCHER_FILE_RUN,                   &Launcher::OnCmdFileRun                   },
57 			{ IDM_LAUNCHER_FILE_EDITHEADER,            &Launcher::OnCmdEditHeader                },
58 			{ IDM_LAUNCHER_FILE_REFRESH,               &Launcher::OnCmdFileRefresh               },
59 			{ IDM_LAUNCHER_VIEW_SHOWGRIDS,             &Launcher::OnCmdViewShowGrids             },
60 			{ IDM_LAUNCHER_VIEW_SHOWDATABASECORRECTED, &Launcher::OnCmdViewShowDatabaseCorrected },
61 			{ IDM_LAUNCHER_VIEW_ONTOP,                 &Launcher::OnCmdViewShowOnTop             },
62 			{ IDM_LAUNCHER_OPTIONS_COLORS,             &Launcher::OnCmdOptionsColors             },
63 			{ IDM_LAUNCHER_OPTIONS_PATHS,              &Launcher::OnCmdOptionsPaths              }
64 		};
65 
66 		const Control::NotificationHandler::Entry<Launcher> Launcher::Handlers::listNotifications[] =
67 		{
68 			{ LVN_GETDISPINFO,    &Launcher::OnListGetDisplayInfo    },
69 			{ LVN_KEYDOWN,        &Launcher::OnListKeyDown           },
70 			{ LVN_COLUMNCLICK,    &Launcher::OnListColumnClick       },
71 			{ LVN_ITEMACTIVATE,   &Launcher::OnListItemActivate      },
72 			{ LVN_ITEMCHANGED,    &Launcher::OnListItemChanged       },
73 			{ LVN_INSERTITEM,     &Launcher::OnListInsertItem        },
74 			{ LVN_DELETEALLITEMS, &Launcher::OnListDeleteAllItems    },
75 			{ LVN_DELETEITEM,     &Launcher::OnListDeleteItem        }
76 		};
77 
78 		const Control::NotificationHandler::Entry<Launcher> Launcher::Handlers::treeNotifications[] =
79 		{
80 			{ TVN_SELCHANGING, &Launcher::OnTreeSelectionChanging }
81 		};
82 
Launcher(const Nes::Cartridge::Database & database,const Managers::Paths & p,const Configuration & cfg)83 		Launcher::Launcher(const Nes::Cartridge::Database& database,const Managers::Paths& p,const Configuration& cfg)
84 		:
85 		dialog            ( IDD_LAUNCHER, this, Handlers::messages ),
86 		menu              ( IDR_MENU_LAUNCHER ),
87 		listNotifications ( IDC_LAUNCHER_LIST, dialog.Messages() ),
88 		treeNotifications ( IDC_LAUNCHER_TREE, dialog.Messages() ),
89 		statusBar         ( dialog, STATUSBAR_SECOND_FIELD_WIDTH ),
90 		list              ( dialog, menu.Commands(), p, cfg, database ),
91 		colors            ( cfg )
92 		{
93 			menu.Commands().Add( this, Handlers::commands );
94 			dialog.Commands().Add( CMD_ENTER, this, &Launcher::OnCmdEnter );
95 			listNotifications.Add( this, Handlers::listNotifications );
96 			treeNotifications.Add( this, Handlers::treeNotifications );
97 
98 			{
99 				Configuration::ConstSection launcher( cfg["launcher"] );
100 
101 				menu[IDM_LAUNCHER_VIEW_ONTOP].Check( launcher["view"]["show"]["on-top"].Yes() );
102 
103 				Configuration::ConstSection size( launcher["window-size"] );
104 
105 				initialSize.x = size["x"].Int();
106 				initialSize.y = size["y"].Int();
107 
108 				if (!initialSize.x || !initialSize.y)
109 				{
110 					initialSize.x = 0;
111 					initialSize.y = 0;
112 				}
113 			}
114 
115 			HeapString name;
116 
117 			for (uint i=0; i < 6; ++i)
118 			{
119 				static const ushort keys[6][2] =
120 				{
121 					{ IDM_LAUNCHER_FILE_RUN,        VK_RETURN },
122 					{ IDM_LAUNCHER_FILE_EDITHEADER, VK_F4     },
123 					{ IDM_LAUNCHER_FILE_REFRESH,    VK_F5     },
124 					{ IDM_LAUNCHER_EDIT_FIND,       VK_F3     },
125 					{ IDM_LAUNCHER_EDIT_INSERT,     VK_INSERT },
126 					{ IDM_LAUNCHER_EDIT_REMOVE,     VK_DELETE }
127 				};
128 
129 				menu[keys[i][0]].Text() >> name;
130 				menu[keys[i][0]].Text() << (name << '\t' << System::Keyboard::GetName( keys[i][1] ));
131 			}
132 		}
133 
~Launcher()134 		Launcher::~Launcher()
135 		{
136 			Close();
137 		}
138 
Save(Configuration & cfg,bool saveSize,bool saveFiles)139 		void Launcher::Save(Configuration& cfg,bool saveSize,bool saveFiles)
140 		{
141 			Configuration::Section launcher( cfg["launcher"] );
142 
143 			if (saveSize)
144 			{
145 				Configuration::Section size( cfg["window-size"] );
146 
147 				size["x"].Int() = initialSize.x;
148 				size["y"].Int() = initialSize.y;
149 			}
150 
151 			launcher["view"]["show"]["on-top"].YesNo() = menu[IDM_LAUNCHER_VIEW_ONTOP].Checked();
152 
153 			list.Save( cfg, saveFiles );
154 			colors.Save( cfg );
155 		}
156 
Open(bool child)157 		void Launcher::Open(bool child)
158 		{
159 			menu[IDM_LAUNCHER_VIEW_ONTOP].Enable( !child );
160 			dialog.Open( child ? Dialog::MODELESS_CHILD : Dialog::MODELESS_FREE );
161 		}
162 
Close()163 		void Launcher::Close()
164 		{
165 			dialog.Close();
166 		}
167 
Synchronize(HWND hWnd) const168 		void Launcher::Synchronize(HWND hWnd) const
169 		{
170 			if (dialog.IsOpen() && menu[IDM_LAUNCHER_VIEW_ONTOP].Enabled() && menu[IDM_LAUNCHER_VIEW_ONTOP].Unchecked())
171 				dialog.Position().BringBehind( hWnd );
172 		}
173 
OnInitDialog(Param &)174 		ibool Launcher::OnInitDialog(Param&)
175 		{
176 			menu.Hook( dialog );
177 			menu.Show();
178 
179 			statusBar.Enable();
180 
181 			if (menu[IDM_LAUNCHER_VIEW_ONTOP].Enabled())
182 				dialog.MakeTopMost( menu[IDM_LAUNCHER_VIEW_ONTOP].Checked() );
183 
184 			list = dialog.ListView( IDC_LAUNCHER_LIST );
185 			tree = dialog.TreeView( IDC_LAUNCHER_TREE );
186 
187 			dialog.Send
188 			(
189 				WM_SETICON,
190 				ICON_SMALL,
191 				static_cast<HICON>(Resource::Icon(Application::Instance::GetIconStyle() == Application::Instance::ICONSTYLE_NES ? IDI_PAD : IDI_PAD_J))
192 			);
193 
194 			margin = dialog.Coordinates().Corner() - list.GetWindow().Coordinates().Corner();
195 
196 			list.SetColors( colors.GetBackgroundColor(), colors.GetForegroundColor() );
197 			tree.SetColors( colors.GetBackgroundColor(), colors.GetForegroundColor() );
198 
199 			menu[ IDM_LAUNCHER_VIEW_SHOWGRIDS ].Check( list.GetStyle() & LVS_EX_GRIDLINES );
200 			menu[ IDM_LAUNCHER_VIEW_SHOWDATABASECORRECTED ].Check( list.DatabaseCorrectionEnabled() );
201 			menu[ IDM_LAUNCHER_FILE_REFRESH ].Enable( list.CanRefresh() );
202 
203 			if (initialSize.x && initialSize.y)
204 				dialog.Size() = initialSize;
205 			else
206 				initialSize = dialog.GetPlacement().Size();
207 
208 			return true;
209 		}
210 
OnClose(Param &)211 		ibool Launcher::OnClose(Param&)
212 		{
213 			Close();
214 			return true;
215 		}
216 
OnDestroy(Param &)217 		ibool Launcher::OnDestroy(Param&)
218 		{
219 			tree.Close();
220 			list.Close();
221 
222 			initialSize = dialog.GetPlacement().Size();
223 
224 			return true;
225 		}
226 
OnSize(Param & param)227 		ibool Launcher::OnSize(Param& param)
228 		{
229 			if (param.wParam != SIZE_MINIMIZED)
230 			{
231 				const Point corner( dialog.Coordinates().Corner() - margin - list.GetWindow().Position() );
232 
233 				list.GetWindow().Size() = corner;
234 				tree.GetWindow().Size() = Point( tree.GetWindow().Coordinates().Width(), corner.y );
235 			}
236 
237 			return true;
238 		}
239 
OnCmdEnter(Param &)240 		ibool Launcher::OnCmdEnter(Param&)
241 		{
242 			OnCmdFileRun();
243 			return true;
244 		}
245 
OnDropFiles(Param & param)246 		ibool Launcher::OnDropFiles(Param& param)
247 		{
248 			list.Insert( param );
249 			return true;
250 		}
251 
UpdateItemCount(const uint count) const252 		void Launcher::UpdateItemCount(const uint count) const
253 		{
254 			if (count == 0 || count == 1)
255 			{
256 				menu[IDM_LAUNCHER_EDIT_FIND].Enable( count );
257 				menu[IDM_LAUNCHER_EDIT_CLEAR].Enable( count );
258 			}
259 
260 			static HeapString form( HeapString() << ' ' << Resource::String(IDS_TEXT_FILES) << ": " );
261 
262 			const uint length = form.Length();
263 			statusBar.Text(StatusBar::SECOND_FIELD) << (form << count).Ptr();
264 			form.ShrinkTo( length );
265 		}
266 
OnListGetDisplayInfo(const NMHDR & nmhdr)267 		void Launcher::OnListGetDisplayInfo(const NMHDR& nmhdr)
268 		{
269 			list.OnGetDisplayInfo( reinterpret_cast<LPARAM>(&nmhdr) );
270 		}
271 
OnListKeyDown(const NMHDR & nmhdr)272 		void Launcher::OnListKeyDown(const NMHDR& nmhdr)
273 		{
274 			switch (reinterpret_cast<const NMLVKEYDOWN&>(nmhdr).wVKey)
275 			{
276 				case VK_INSERT: if (menu[ IDM_LAUNCHER_EDIT_INSERT     ].Enabled()) dialog.PostCommand( IDM_LAUNCHER_EDIT_INSERT     ); break;
277 				case VK_DELETE: if (menu[ IDM_LAUNCHER_EDIT_REMOVE     ].Enabled()) dialog.PostCommand( IDM_LAUNCHER_EDIT_REMOVE     ); break;
278 				case VK_F3:     if (menu[ IDM_LAUNCHER_EDIT_FIND       ].Enabled()) dialog.PostCommand( IDM_LAUNCHER_EDIT_FIND       ); break;
279 				case VK_F4:     if (menu[ IDM_LAUNCHER_FILE_EDITHEADER ].Enabled()) dialog.PostCommand( IDM_LAUNCHER_FILE_EDITHEADER ); break;
280 				case VK_F5:     if (menu[ IDM_LAUNCHER_FILE_REFRESH    ].Enabled()) dialog.PostCommand( IDM_LAUNCHER_FILE_REFRESH    ); break;
281 			}
282 		}
283 
OnListColumnClick(const NMHDR & nmhdr)284 		void Launcher::OnListColumnClick(const NMHDR& nmhdr)
285 		{
286 			Application::Instance::Waiter wait;
287 			list.Sort( reinterpret_cast<const NMLISTVIEW&>(nmhdr).iSubItem );
288 		}
289 
OnListItemActivate(const NMHDR &)290 		void Launcher::OnListItemActivate(const NMHDR&)
291 		{
292 			OnCmdFileRun();
293 		}
294 
OnListItemChanged(const NMHDR & nmhdr)295 		void Launcher::OnListItemChanged(const NMHDR& nmhdr)
296 		{
297 			const NMLISTVIEW& nmlv = reinterpret_cast<const NMLISTVIEW&>(nmhdr);
298 
299 			if ((nmlv.uOldState ^ nmlv.uNewState) & LVIS_SELECTED)
300 			{
301 				if (nmlv.uNewState & LVIS_SELECTED)
302 				{
303 					if (const List::Files::Entry* const entry = list[nmlv.iItem])
304 					{
305 						{
306 							Path path( entry->GetPath(list.GetStrings()), entry->GetFile(list.GetStrings()) );
307 
308 							if (path.Length() > MAX_PATH)
309 							{
310 								path.ShrinkTo( MAX_PATH-3 );
311 								path << "...";
312 							}
313 
314 							statusBar.Text(StatusBar::FIRST_FIELD) << path.Ptr();
315 						}
316 
317 						menu[IDM_LAUNCHER_FILE_RUN].Enable();
318 						menu[IDM_LAUNCHER_FILE_EDITHEADER].Enable( entry->GetType() == List::Files::Entry::NES );
319 						menu[IDM_LAUNCHER_EDIT_REMOVE].Enable();
320 					}
321 				}
322 				else
323 				{
324 					OnNoSelection();
325 				}
326 			}
327 		}
328 
OnListInsertItem(const NMHDR &)329 		void Launcher::OnListInsertItem(const NMHDR&)
330 		{
331 			UpdateItemCount( list.Size() );
332 		}
333 
OnListDeleteItem(const NMHDR &)334 		void Launcher::OnListDeleteItem(const NMHDR&)
335 		{
336 			const uint size = list.Size();
337 			UpdateItemCount( size - (size != 0) );
338 		}
339 
OnListDeleteAllItems(const NMHDR &)340 		void Launcher::OnListDeleteAllItems(const NMHDR&)
341 		{
342 			OnNoSelection();
343 			const uint size = list.Size();
344 			UpdateItemCount( size - (size != 0) );
345 		}
346 
OnTreeSelectionChanging(const NMHDR & nmhdr)347 		void Launcher::OnTreeSelectionChanging(const NMHDR& nmhdr)
348 		{
349 			Application::Instance::Events::Signal( Application::Instance::EVENT_SYSTEM_BUSY );
350 			list.Draw( tree.GetType(reinterpret_cast<const NMTREEVIEW&>(nmhdr).itemNew.hItem) );
351 		}
352 
OnNoSelection() const353 		void Launcher::OnNoSelection() const
354 		{
355 			statusBar.Text(StatusBar::FIRST_FIELD).Clear();
356 
357 			menu[IDM_LAUNCHER_FILE_RUN].Disable();
358 			menu[IDM_LAUNCHER_FILE_EDITHEADER].Disable();
359 			menu[IDM_LAUNCHER_EDIT_REMOVE].Disable();
360 		}
361 
OnCmdFileRun(uint)362 		void Launcher::OnCmdFileRun(uint)
363 		{
364 			if (const List::Files::Entry* const entry = list.GetSelection())
365 				Application::Instance::GetMainWindow().Send( Application::Instance::WM_NST_LAUNCH, 0, Path(entry->GetPath(list.GetStrings()),entry->GetFile(list.GetStrings())).Ptr() );
366 		}
367 
OnCmdFileRefresh(uint)368 		void Launcher::OnCmdFileRefresh(uint)
369 		{
370 			list.Refresh();
371 		}
372 
OnCmdEditHeader(uint)373 		void Launcher::OnCmdEditHeader(uint)
374 		{
375 			if (const List::Files::Entry* const entry = list.GetSelection())
376 				Window::InesHeader( list.GetPaths() ).Open( Path(entry->GetPath(list.GetStrings()),entry->GetFile(list.GetStrings())) );
377 		}
378 
OnCmdViewShowGrids(uint)379 		void Launcher::OnCmdViewShowGrids(uint)
380 		{
381 			menu[IDM_LAUNCHER_VIEW_SHOWGRIDS].Check( list.ToggleGrids() );
382 		}
383 
OnCmdViewShowDatabaseCorrected(uint)384 		void Launcher::OnCmdViewShowDatabaseCorrected(uint)
385 		{
386 			menu[IDM_LAUNCHER_VIEW_SHOWDATABASECORRECTED].Check( list.ToggleDatabase() );
387 		}
388 
OnCmdViewShowOnTop(uint)389 		void Launcher::OnCmdViewShowOnTop(uint)
390 		{
391 			dialog.MakeTopMost( menu[IDM_LAUNCHER_VIEW_ONTOP].ToggleCheck() );
392 		}
393 
OnCmdOptionsPaths(uint)394 		void Launcher::OnCmdOptionsPaths(uint)
395 		{
396 			list.OpenPathDialog();
397 			menu[IDM_LAUNCHER_FILE_REFRESH].Enable( list.CanRefresh() );
398 		}
399 
OnCmdOptionsColors(uint)400 		void Launcher::OnCmdOptionsColors(uint)
401 		{
402 			colors.Open();
403 
404 			list.SetColors( colors.GetBackgroundColor(), colors.GetForegroundColor(), List::REPAINT );
405 			tree.SetColors( colors.GetBackgroundColor(), colors.GetForegroundColor(), Tree::REPAINT );
406 		}
407 	}
408 }
409