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