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 "NstWindowUser.hpp"
26 #include "NstWindowDropFiles.hpp"
27 #include "NstResourceString.hpp"
28 #include "NstDialogFind.hpp"
29 #include "NstManagerPaths.hpp"
30 #include "NstDialogLauncher.hpp"
31 #include <Shlwapi.h>
32 
33 namespace Nestopia
34 {
35 	namespace Window
36 	{
37 		#ifdef NST_MSVC_OPTIMIZE
38 		#pragma optimize("t", on)
39 		#endif
40 
GetMapper(const uint value)41 		wcstring Launcher::List::Strings::GetMapper(const uint value)
42 		{
43 			if (value-1 < 4095)
44 			{
45 				uint count = mappers.Size();
46 
47 				if (count <= value)
48 				{
49 					const uint size = NST_MAX(256,count+value+1);
50 					mappers.Resize( size );
51 
52 					do
53 					{
54 						_itow( count, mappers[count].string, 10 );
55 					}
56 					while (++count != size);
57 				}
58 
59 				return mappers[value].string;
60 			}
61 			else
62 			{
63 				return L"-";
64 			}
65 		}
66 
GetSize(uint value)67 		wcstring Launcher::List::Strings::GetSize(uint value)
68 		{
69 			if (value)
70 			{
71 				SizeString& string = sizes[value];
72 
73 				if (string.Empty())
74 					string << value << 'k';
75 
76 				return string.Ptr();
77 			}
78 
79 			return L"-";
80 		}
81 
82 		#ifdef NST_MSVC_OPTIMIZE
83 		#pragma optimize("", on)
84 		#endif
85 
Flush()86 		void Launcher::List::Strings::Flush()
87 		{
88 			sizes.clear();
89 			mappers.Destroy();
90 		}
91 
List(Dialog & dialog,Menu::CmdHandler & cmdHandler,const Managers::Paths & p,const Configuration & cfg,const Nes::Cartridge::Database & database)92 		Launcher::List::List
93 		(
94 			Dialog& dialog,
95 			Menu::CmdHandler& cmdHandler,
96 			const Managers::Paths& p,
97 			const Configuration& cfg,
98 			const Nes::Cartridge::Database& database
99 		)
100 		:
101 		imageDatabase    ( database ),
102 		useImageDatabase ( NULL ),
103 		typeFilter       ( 0 ),
104 		style            ( STYLE ),
105 		finder           ( dialog ),
106 		paths            ( cfg ),
107 		columns          ( cfg ),
108 		pathManager      ( p )
109 		{
110 			static const Menu::CmdHandler::Entry<Launcher::List> commands[] =
111 			{
112 				{ IDM_LAUNCHER_EDIT_FIND,         &List::OnCmdEditFind         },
113 				{ IDM_LAUNCHER_EDIT_INSERT,       &List::OnCmdEditInsert       },
114 				{ IDM_LAUNCHER_EDIT_REMOVE,       &List::OnCmdEditDelete       },
115 				{ IDM_LAUNCHER_EDIT_CLEAR,        &List::OnCmdEditClear        },
116 				{ IDM_LAUNCHER_VIEW_ALIGNCOLUMNS, &List::OnCmdViewAlignColumns },
117 				{ IDM_LAUNCHER_OPTIONS_COLUMNS,   &List::OnCmdOptionsColumns   }
118 			};
119 
120 			cmdHandler.Add( this, commands );
121 
122 			Configuration::ConstSection show( cfg["launcher"]["view"]["show"] );
123 
124 			if (!show["grid-lines"].No())
125 				style |= LVS_EX_GRIDLINES;
126 
127 			if (!show["image-database-adjusted"].No())
128 				useImageDatabase = &imageDatabase;
129 		}
130 
~List()131 		Launcher::List::~List()
132 		{
133 		}
134 
operator =(const Control::ListView & listView)135 		void Launcher::List::operator = (const Control::ListView& listView)
136 		{
137 			files.Load();
138 
139 			typeFilter = 0;
140 
141 			ctrl = listView;
142 			ctrl.StyleEx() = style;
143 
144 			ReloadListColumns();
145 
146 			ctrl.Reserve( files.Count() );
147 			ctrl.Columns().Align();
148 
149 			InvalidateRect( ctrl.GetWindow(), NULL, false );
150 		}
151 
Close()152 		void Launcher::List::Close()
153 		{
154 			finder.Close();
155 			UpdateColumnOrder();
156 			columns.Update( order );
157 			strings.Flush();
158 		}
159 
Insert(const Param & param)160 		void Launcher::List::Insert(const Param& param)
161 		{
162 			DropFiles dropFiles( param );
163 
164 			if (dropFiles.Inside( ctrl.GetHandle() ))
165 			{
166 				uint anyInserted = false;
167 
168 				for (uint i=0, n=dropFiles.Size(); i < n; ++i)
169 					anyInserted |= uint(files.Insert( imageDatabase, dropFiles[i] ));
170 
171 				if (anyInserted && !Optimize())
172 					Redraw();
173 			}
174 		}
175 
Add(wcstring const fileName)176 		void Launcher::List::Add(wcstring const fileName)
177 		{
178 			if (files.Insert( imageDatabase, fileName ) && !Optimize())
179 				Redraw();
180 		}
181 
Save(Configuration & cfg,bool saveFiles)182 		void Launcher::List::Save(Configuration& cfg,bool saveFiles)
183 		{
184 			paths.Save( cfg );
185 			columns.Save( cfg );
186 
187 			if (saveFiles)
188 				files.Save();
189 
190 			Configuration::Section show( cfg["launcher"]["view"]["show"] );
191 
192 			show["grid-lines"].YesNo() = style & LVS_EX_GRIDLINES;
193 			show["image-database-adjusted"].YesNo() = useImageDatabase;
194 		}
195 
SetColors(const uint bg,const uint fg,const Updater redraw) const196 		void Launcher::List::SetColors(const uint bg,const uint fg,const Updater redraw) const
197 		{
198 			ctrl.SetBkColor( bg );
199 			ctrl.SetTextBkColor( bg );
200 			ctrl.SetTextColor( fg );
201 
202 			if (redraw)
203 				ctrl.Redraw();
204 		}
205 
Redraw()206 		void Launcher::List::Redraw()
207 		{
208 			Application::Instance::Waiter wait;
209 			Generic::LockDraw lock( ctrl.GetHandle() );
210 
211 			ctrl.Clear();
212 
213 			if (const uint count = files.Count())
214 			{
215 				uint size = 0;
216 
217 				for (uint i=0; i < count; ++i)
218 					size += (files[i].GetType() & typeFilter) != 0;
219 
220 				if (size)
221 				{
222 					ctrl.Reserve( size );
223 
224 					for (uint i=0; i < count; ++i)
225 					{
226 						if (files[i].GetType() & typeFilter)
227 							ctrl.Add( GenericString(), &files[i] );
228 					}
229 
230 					Sort();
231 					ctrl.Columns().Align();
232 				}
233 			}
234 		}
235 
Optimize()236 		bool Launcher::List::Optimize()
237 		{
238 			if (files.ShouldDefrag())
239 			{
240 				files.Defrag();
241 				Redraw();
242 				return true;
243 			}
244 
245 			return false;
246 		}
247 
CanRefresh() const248 		bool Launcher::List::CanRefresh() const
249 		{
250 			return
251 			(
252 				(paths.GetSettings().folders.size()) &&
253 				(paths.GetSettings().include.Word() & Paths::Settings::Include::TYPES)
254 			);
255 		}
256 
Refresh()257 		void Launcher::List::Refresh()
258 		{
259 			if (CanRefresh())
260 			{
261 				{
262 					Application::Instance::Waiter wait;
263 					ctrl.Clear();
264 				}
265 
266 				files.Refresh( paths.GetSettings(), imageDatabase );
267 				ctrl.Reserve( files.Count() );
268 				Redraw();
269 			}
270 		}
271 
272 		#ifdef NST_MSVC_OPTIMIZE
273 		#pragma optimize("t", on)
274 		#endif
275 
OnGetDisplayInfo(LPARAM lParam)276 		void Launcher::List::OnGetDisplayInfo(LPARAM lParam)
277 		{
278 			LVITEM& item = reinterpret_cast<NMLVDISPINFO*>(lParam)->item;
279 
280 			if (item.mask & LVIF_TEXT)
281 			{
282 				const Files::Entry& entry = *reinterpret_cast<const Files::Entry*>( item.lParam );
283 
284 				switch (columns.GetType( item.iSubItem ))
285 				{
286 					case Columns::TYPE_FILE:
287 
288 						item.pszText = const_cast<wchar_t*>( entry.GetFile(files.GetStrings()) );
289 						break;
290 
291 					case Columns::TYPE_SYSTEM:
292 					{
293 						NST_COMPILE_ASSERT
294 						(
295 							Files::Entry::SYSTEM_UNKNOWN  == 0 &&
296 							Files::Entry::SYSTEM_PC10     == 1 &&
297 							Files::Entry::SYSTEM_VS       == 2 &&
298 							Files::Entry::SYSTEM_PAL      == 3 &&
299 							Files::Entry::SYSTEM_NTSC     == 4 &&
300 							Files::Entry::SYSTEM_NTSC_PAL == 5
301 						);
302 
303 						static const wchar_t lut[][9] =
304 						{
305 							L"-",
306 							L"pc10",
307 							L"vs",
308 							L"pal",
309 							L"ntsc",
310 							L"ntsc/pal"
311 						};
312 
313 						item.pszText = const_cast<wchar_t*>( lut[entry.GetSystem( useImageDatabase )] );
314 						break;
315 					}
316 
317 					case Columns::TYPE_BATTERY:
318 
319 						item.pszText = const_cast<wchar_t*>
320 						(
321 							(entry.GetType() & (Files::Entry::NES|Files::Entry::UNF)) ?
322 							entry.GetBattery( useImageDatabase ) ? L"yes" : L"no" : L"-"
323 						);
324 						break;
325 
326 					case Columns::TYPE_DUMP:
327 					{
328 						NST_COMPILE_ASSERT
329 						(
330 							Nes::Cartridge::Profile::Dump::OK      == 0 &&
331 							Nes::Cartridge::Profile::Dump::BAD     == 1 &&
332 							Nes::Cartridge::Profile::Dump::UNKNOWN == 2
333 						);
334 
335 						static const wchar_t lut[][4] =
336 						{
337 							L"ok",
338 							L"bad",
339 							L"-"
340 						};
341 
342 						item.pszText = const_cast<wchar_t*>( lut[entry.GetDump( useImageDatabase )] );
343 						break;
344 					}
345 
346 					case Columns::TYPE_NAME:
347 
348 						item.pszText = const_cast<wchar_t*>( entry.GetName( files.GetStrings(), useImageDatabase ) );
349 						break;
350 
351 					case Columns::TYPE_FOLDER:
352 
353 						item.pszText = const_cast<wchar_t*>( entry.GetPath( files.GetStrings() ) );
354 						break;
355 
356 					case Columns::TYPE_PROM:
357 
358 						item.pszText = const_cast<wchar_t*>( strings.GetSize(entry.GetPRom(useImageDatabase)) );
359 						break;
360 
361 					case Columns::TYPE_CROM:
362 
363 						if (const uint cRom = entry.GetCRom( useImageDatabase ))
364 							item.pszText = const_cast<wchar_t*>( strings.GetSize( cRom ) );
365 						else
366 							item.pszText = const_cast<wchar_t*>( L"-" );
367 						break;
368 
369 					case Columns::TYPE_MAPPER:
370 
371 						item.pszText = const_cast<wchar_t*>( strings.GetMapper(entry.GetMapper( useImageDatabase )) );
372 						break;
373 
374 					case Columns::TYPE_WRAM:
375 
376 						if (const uint wRam = entry.GetWRam( useImageDatabase ))
377 							item.pszText = const_cast<wchar_t*>( strings.GetSize( wRam ) );
378 						else
379 							item.pszText = const_cast<wchar_t*>( L"-" );
380 						break;
381 
382 					case Columns::TYPE_VRAM:
383 
384 						if (const uint vRam = entry.GetVRam( useImageDatabase ))
385 							item.pszText = const_cast<wchar_t*>( strings.GetSize( vRam ) );
386 						else
387 							item.pszText = const_cast<wchar_t*>( L"-" );
388 						break;
389 				}
390 			}
391 		}
392 
393 		#ifdef NST_MSVC_OPTIMIZE
394 		#pragma optimize("", on)
395 		#endif
396 
ReloadListColumns() const397 		void Launcher::List::ReloadListColumns() const
398 		{
399 			ctrl.Columns().Clear();
400 
401 			for (uint i=0; i < columns.Count(); ++i)
402 				ctrl.Columns().Insert( i, Resource::String(columns.GetStringId(i)).Ptr() );
403 		}
404 
UpdateColumnOrder()405 		void Launcher::List::UpdateColumnOrder()
406 		{
407 			int array[Columns::NUM_TYPES];
408 			ctrl.Columns().GetOrder( array, columns.Count() );
409 
410 			for (uint i=0; i < columns.Count(); ++i)
411 				order[i] = columns.GetType( array[i] );
412 		}
413 
UpdateSortColumnOrder(const uint firstSortColumn)414 		void Launcher::List::UpdateSortColumnOrder(const uint firstSortColumn)
415 		{
416 			int array[Columns::NUM_TYPES];
417 			ctrl.Columns().GetOrder( array, columns.Count() );
418 
419 			order[0] = columns.GetType( firstSortColumn );
420 
421 			for (uint i=0, j=1; i < columns.Count(); ++i)
422 			{
423 				if (firstSortColumn != array[i])
424 					order[j++] = columns.GetType( array[i] );
425 			}
426 		}
427 
Sort(const uint firstSortColumn)428 		void Launcher::List::Sort(const uint firstSortColumn)
429 		{
430 			if (ctrl.Size() > 1)
431 			{
432 				UpdateSortColumnOrder( firstSortColumn );
433 				ctrl.Sort( this, &List::Sorter );
434 			}
435 		}
436 
Sorter(const void * obj1,const void * obj2)437 		int Launcher::List::Sorter(const void* obj1,const void* obj2)
438 		{
439 			const Files::Entry& a = *static_cast<const Files::Entry*>( obj1 );
440 			const Files::Entry& b = *static_cast<const Files::Entry*>( obj2 );
441 
442 			for (uint i=0, n=columns.Count(); i < n; ++i)
443 			{
444 				switch (order[i])
445 				{
446 					case Columns::TYPE_FILE:
447 
448 						if (const int ret = ::StrCmp( a.GetFile(files.GetStrings()), b.GetFile(files.GetStrings()) ))
449 							return ret;
450 
451 						continue;
452 
453 					case Columns::TYPE_SYSTEM:
454 					{
455 						const uint system[] =
456 						{
457 							a.GetSystem( useImageDatabase ),
458 							b.GetSystem( useImageDatabase )
459 						};
460 
461 						if (system[0] == system[1])
462 							continue;
463 
464 						return system[0] < system[1] ? +1 : -1;
465 					}
466 
467 					case Columns::TYPE_MAPPER:
468 					{
469 						const uint mapper[] =
470 						{
471 							a.GetMapper( useImageDatabase ),
472 							b.GetMapper( useImageDatabase )
473 						};
474 
475 						if (mapper[0] == mapper[1])
476 							continue;
477 
478 						return mapper[0] > mapper[1] ? +1 : -1;
479 					}
480 
481 					case Columns::TYPE_PROM:
482 					{
483 						const uint pRom[] =
484 						{
485 							a.GetPRom( useImageDatabase ),
486 							b.GetPRom( useImageDatabase )
487 						};
488 
489 						if (pRom[0] == pRom[1])
490 							continue;
491 
492 						return pRom[0] > pRom[1] ? +1 : -1;
493 					}
494 
495 					case Columns::TYPE_CROM:
496 					{
497 						const uint cRom[] =
498 						{
499 							a.GetCRom( useImageDatabase ),
500 							b.GetCRom( useImageDatabase )
501 						};
502 
503 						if (cRom[0] == cRom[1])
504 							continue;
505 
506 						return cRom[0] > cRom[1] ? +1 : -1;
507 					}
508 
509 					case Columns::TYPE_WRAM:
510 					{
511 						const uint wRam[] =
512 						{
513 							a.GetWRam( useImageDatabase ),
514 							b.GetWRam( useImageDatabase )
515 						};
516 
517 						if (wRam[0] == wRam[1])
518 							continue;
519 
520 						return wRam[0] > wRam[1] ? +1 : -1;
521 					}
522 
523 					case Columns::TYPE_VRAM:
524 					{
525 						const uint vRam[] =
526 						{
527 							a.GetVRam( useImageDatabase ),
528 							b.GetVRam( useImageDatabase )
529 						};
530 
531 						if (vRam[0] == vRam[1])
532 							continue;
533 
534 						return vRam[0] > vRam[1] ? +1 : -1;
535 					}
536 
537 					case Columns::TYPE_BATTERY:
538 					{
539 						const uint battery[] =
540 						{
541 							a.GetBattery( useImageDatabase ) + bool(a.GetType() & (List::Files::Entry::NES|List::Files::Entry::UNF)),
542 							b.GetBattery( useImageDatabase ) + bool(b.GetType() & (List::Files::Entry::NES|List::Files::Entry::UNF))
543 						};
544 
545 						if (battery[0] == battery[1])
546 							continue;
547 
548 						return battery[0] < battery[1] ? +1 : -1;
549 					}
550 
551 					case Columns::TYPE_DUMP:
552 					{
553 						const uint dump[] =
554 						{
555 							a.GetDump( useImageDatabase ),
556 							b.GetDump( useImageDatabase )
557 						};
558 
559 						if (dump[0] == dump[1])
560 							continue;
561 
562 						return dump[0] > dump[1] ? +1 : -1;
563 					}
564 
565 					case Columns::TYPE_NAME:
566 					{
567 						wcstring const names[] =
568 						{
569 							a.GetName( files.GetStrings(), useImageDatabase ),
570 							b.GetName( files.GetStrings(), useImageDatabase )
571 						};
572 
573 						if (names[0][0] != '-' && names[1][0] == '-') return -1;
574 						if (names[0][0] == '-' && names[1][0] != '-') return +1;
575 
576 						if (const int ret = ::StrCmp( names[0], names[1] ))
577 							return ret;
578 
579 						continue;
580 					}
581 
582 					case Columns::TYPE_FOLDER:
583 
584 						if (const int ret = ::StrCmp( a.GetPath(files.GetStrings()), b.GetPath(files.GetStrings()) ))
585 							return ret;
586 
587 						continue;
588 				}
589 			}
590 
591 			return 0;
592 		}
593 
OnFind(GenericString string,const uint flags)594 		void Launcher::List::OnFind(GenericString string,const uint flags)
595 		{
596 			const uint count = ctrl.Size();
597 
598 			if (count > 1 && string.Length())
599 			{
600 				const uint column = ctrl.Columns().GetIndex(0);
601 				const int selection = ctrl.Selection().GetIndex();
602 				const uint wrap = selection > 0 ? selection : 0;
603 				uint index = wrap;
604 				bool found;
605 
606 				HeapString item;
607 
608 				do
609 				{
610 					if (flags & Finder::DOWN)
611 					{
612 						if (++index == count)
613 							index = 0;
614 					}
615 					else
616 					{
617 						if (--index == ~0U)
618 							index = count - 1;
619 					}
620 
621 					ctrl[index].Text( column ) >> item;
622 
623 					if (flags & Finder::WHOLEWORD)
624 					{
625 						found = item.Length() == string.Length() && ::StrIsIntlEqual( (flags & Finder::MATCHCASE), item.Ptr(), string.Ptr(), string.Length() );
626 					}
627 					else if (flags & Finder::MATCHCASE)
628 					{
629 						found = ::StrStr( item.Ptr(), string.Ptr() );
630 					}
631 					else
632 					{
633 						found = ::StrStrI( item.Ptr(), string.Ptr() );
634 					}
635 				}
636 				while (!found && index != wrap);
637 
638 				if (found)
639 				{
640 					if (selection >= 0)
641 						ctrl[selection].Select( false );
642 
643 					ctrl[index].Select();
644 					ctrl[index].Show();
645 				}
646 				else
647 				{
648 					User::Inform( IDS_TEXT_SEARCH_NOT_FOUND, IDS_TEXT_FIND );
649 				}
650 			}
651 		}
652 
OnCmdEditFind(uint)653 		void Launcher::List::OnCmdEditFind(uint)
654 		{
655 			finder.Open( this, &List::OnFind );
656 		}
657 
OnCmdEditInsert(uint)658 		void Launcher::List::OnCmdEditInsert(uint)
659 		{
660 			enum
661 			{
662 				FILE_TYPES =
663 				(
664 					Managers::Paths::File::IMAGE |
665 					Managers::Paths::File::PATCH |
666 					Managers::Paths::File::ARCHIVE
667 				)
668 			};
669 
670 			Add( pathManager.BrowseLoad( FILE_TYPES, GenericString(), Managers::Paths::DONT_CHECK_FILE ).Ptr() );
671 		}
672 
OnCmdEditDelete(uint)673 		void Launcher::List::OnCmdEditDelete(uint)
674 		{
675 			Application::Instance::Waiter wait;
676 
677 			int last = -1;
678 
679 			for (int index = ctrl.Selection().GetIndex(); index != -1; index = ctrl.Selection().GetIndex())
680 			{
681 				last = index;
682 				void* const entry = ctrl[index].Data();
683 				ctrl[index].Delete();
684 				files.Disable( static_cast<Files::Entry*>(entry) );
685 			}
686 
687 			if (ctrl.Size())
688 			{
689 				if (last != -1)
690 					ctrl[last].Select();
691 			}
692 			else if (typeFilter == Files::Entry::ALL)
693 			{
694 				files.Clear();
695 			}
696 		}
697 
OnCmdEditClear(uint)698 		void Launcher::List::OnCmdEditClear(uint)
699 		{
700 			Application::Instance::Waiter wait;
701 			ctrl.Clear();
702 			files.Clear();
703 		}
704 
OnCmdViewAlignColumns(uint)705 		void Launcher::List::OnCmdViewAlignColumns(uint)
706 		{
707 			Application::Instance::Waiter wait;
708 			ctrl.Columns().Align();
709 		}
710 
OnCmdOptionsColumns(uint)711 		void Launcher::List::OnCmdOptionsColumns(uint)
712 		{
713 			UpdateColumnOrder();
714 
715 			columns.Update( order );
716 			columns.Open();
717 
718 			Application::Instance::Waiter wait;
719 			Generic::LockDraw lock( ctrl.GetHandle() );
720 
721 			ReloadListColumns();
722 			Sort();
723 			ctrl.Columns().Align();
724 		}
725 	}
726 }
727