From 126bb8cb6b93240bb4d3a2b816b74c286c3d422b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Sun, 6 Jul 2014 15:20:38 +0200 Subject: Imported Upstream version 1.7.0 --- CHANGELOG | 1306 +++++++ LICENSE | 340 ++ README | 37 + README.fr | 37 + bin/gcstar | 358 ++ install | 788 ++++ lib/gcstar/GCBackend/GCBackendXmlCommon.pm | 305 ++ lib/gcstar/GCBackend/GCBackendXmlParser.pm | 491 +++ lib/gcstar/GCBookmarks.pm | 729 ++++ lib/gcstar/GCBorrowings.pm | 662 ++++ lib/gcstar/GCCommandLine.pm | 395 ++ lib/gcstar/GCData.pm | 970 +++++ lib/gcstar/GCDialogs.pm | 1519 +++++++ lib/gcstar/GCDisplay.pm | 1146 ++++++ lib/gcstar/GCExport.pm | 118 + lib/gcstar/GCExport/GCExportBase.pm | 362 ++ lib/gcstar/GCExport/GCExportCSV.pm | 198 + lib/gcstar/GCExport/GCExportExternal.pm | 182 + lib/gcstar/GCExport/GCExportHTML.pm | 592 +++ lib/gcstar/GCExport/GCExportLatex.pm | 204 + lib/gcstar/GCExport/GCExportPDB.pm | 295 ++ lib/gcstar/GCExport/GCExportSQL.pm | 172 + lib/gcstar/GCExport/GCExportTarGz.pm | 174 + lib/gcstar/GCExport/GCExportTellico.pm | 512 +++ lib/gcstar/GCExport/GCExportXML.pm | 287 ++ lib/gcstar/GCExportImport.pm | 526 +++ lib/gcstar/GCExtract.pm | 150 + lib/gcstar/GCExtract/GCExtractFilms.pm | 476 +++ lib/gcstar/GCExtract/GCExtractMusics.pm | 370 ++ lib/gcstar/GCGenres.pm | 404 ++ lib/gcstar/GCGraphicComponents/GCBaseWidgets.pm | 4023 +++++++++++++++++++ lib/gcstar/GCGraphicComponents/GCDoubleLists.pm | 564 +++ lib/gcstar/GCImport.pm | 139 + lib/gcstar/GCImport/GCImportAMC.pm | 234 ++ lib/gcstar/GCImport/GCImportAlexandria.pm | 205 + lib/gcstar/GCImport/GCImportBase.pm | 217 + lib/gcstar/GCImport/GCImportCSV.pm | 313 ++ lib/gcstar/GCImport/GCImportDVDProfiler.pm | 192 + lib/gcstar/GCImport/GCImportFolder.pm | 510 +++ lib/gcstar/GCImport/GCImportGCfilms.pm | 190 + lib/gcstar/GCImport/GCImportGCstar.pm | 106 + lib/gcstar/GCImport/GCImportList.pm | 202 + lib/gcstar/GCImport/GCImportMyMovies.pm | 211 + lib/gcstar/GCImport/GCImportScanner.pm | 394 ++ lib/gcstar/GCImport/GCImportTarGz.pm | 152 + lib/gcstar/GCImport/GCImportTellico.pm | 496 +++ lib/gcstar/GCItemsLists/GCImageListComponents.pm | 848 ++++ lib/gcstar/GCItemsLists/GCImageLists.pm | 2028 ++++++++++ lib/gcstar/GCItemsLists/GCListOptions.pm | 496 +++ lib/gcstar/GCItemsLists/GCTextLists.pm | 2101 ++++++++++ lib/gcstar/GCLang.pm | 274 ++ lib/gcstar/GCLang/AR/GCExport/GCExportCSV.pm | 52 + lib/gcstar/GCLang/AR/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/AR/GCExport/GCExportHTML.pm | 76 + lib/gcstar/GCLang/AR/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/AR/GCExport/GCExportSQL.pm | 53 + lib/gcstar/GCLang/AR/GCExport/GCExportTarGz.pm | 49 + lib/gcstar/GCLang/AR/GCExport/GCExportXML.pm | 53 + .../GCLang/AR/GCImport/GCImportAlexandria.pm | 51 + lib/gcstar/GCLang/AR/GCImport/GCImportCSV.pm | 55 + lib/gcstar/GCLang/AR/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/AR/GCImport/GCImportGCstar.pm | 49 + lib/gcstar/GCLang/AR/GCImport/GCImportList.pm | 54 + lib/gcstar/GCLang/AR/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/AR/GCImport/GCImportTellico.pm | 49 + lib/gcstar/GCLang/AR/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/AR/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/AR/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/AR/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/AR/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/AR/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/AR/GCModels/GCfilms.pm | 104 + lib/gcstar/GCLang/AR/GCModels/GCgames.pm | 92 + lib/gcstar/GCLang/AR/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/AR/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/AR/GCModels/GCmusics.pm | 78 + lib/gcstar/GCLang/AR/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/AR/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/AR/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/AR/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/AR/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/AR/GCstar.pm | 692 ++++ lib/gcstar/GCLang/BG/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/BG/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/BG/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/BG/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/BG/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/BG/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/BG/GCExport/GCExportXML.pm | 41 + .../GCLang/BG/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/BG/GCImport/GCImportCSV.pm | 43 + lib/gcstar/GCLang/BG/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/BG/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/BG/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/BG/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/BG/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/BG/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/BG/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/BG/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/BG/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/BG/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/BG/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/BG/GCModels/GCfilms.pm | 91 + lib/gcstar/GCLang/BG/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/BG/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/BG/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/BG/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/BG/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/BG/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/BG/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/BG/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/BG/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/BG/GCstar.pm | 674 ++++ lib/gcstar/GCLang/CA/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/CA/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/CA/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/CA/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/CA/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/CA/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/CA/GCExport/GCExportXML.pm | 41 + .../GCLang/CA/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/CA/GCImport/GCImportCSV.pm | 43 + lib/gcstar/GCLang/CA/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/CA/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/CA/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/CA/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/CA/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/CA/GCModels/GCTVepisodes.pm | 45 + lib/gcstar/GCLang/CA/GCModels/GCTVseries.pm | 55 + lib/gcstar/GCLang/CA/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/CA/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/CA/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/CA/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/CA/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/CA/GCModels/GCgames.pm | 83 + lib/gcstar/GCLang/CA/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/CA/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/CA/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/CA/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/CA/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/CA/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/CA/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/CA/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/CA/GCstar.pm | 674 ++++ lib/gcstar/GCLang/CS/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/CS/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/CS/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/CS/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/CS/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/CS/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/CS/GCExport/GCExportXML.pm | 41 + .../GCLang/CS/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/CS/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/CS/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/CS/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/CS/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/CS/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/CS/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/CS/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/CS/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/CS/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/CS/GCModels/GCbooks.pm | 73 + lib/gcstar/GCLang/CS/GCModels/GCcoins.pm | 107 + lib/gcstar/GCLang/CS/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/CS/GCModels/GCfilms.pm | 96 + lib/gcstar/GCLang/CS/GCModels/GCgames.pm | 85 + lib/gcstar/GCLang/CS/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/CS/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/CS/GCModels/GCmusics.pm | 71 + lib/gcstar/GCLang/CS/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/CS/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/CS/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/CS/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/CS/GCModels/GCwines.pm | 68 + lib/gcstar/GCLang/CS/GCstar.pm | 672 ++++ lib/gcstar/GCLang/DE/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/DE/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/DE/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/DE/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/DE/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/DE/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/DE/GCExport/GCExportXML.pm | 41 + .../GCLang/DE/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/DE/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/DE/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/DE/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/DE/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/DE/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/DE/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/DE/GCModels/GCTVepisodes.pm | 50 + lib/gcstar/GCLang/DE/GCModels/GCTVseries.pm | 53 + lib/gcstar/GCLang/DE/GCModels/GCboardgames.pm | 87 + lib/gcstar/GCLang/DE/GCModels/GCbooks.pm | 71 + lib/gcstar/GCLang/DE/GCModels/GCcoins.pm | 108 + lib/gcstar/GCLang/DE/GCModels/GCcomics.pm | 74 + lib/gcstar/GCLang/DE/GCModels/GCfilms.pm | 94 + lib/gcstar/GCLang/DE/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/DE/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/DE/GCModels/GCminicars.pm | 179 + lib/gcstar/GCLang/DE/GCModels/GCmusics.pm | 69 + lib/gcstar/GCLang/DE/GCModels/GCperiodicals.pm | 56 + lib/gcstar/GCLang/DE/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/DE/GCModels/GCsoftware.pm | 82 + lib/gcstar/GCLang/DE/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/DE/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/DE/GCstar.pm | 686 ++++ lib/gcstar/GCLang/EL/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/EL/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/EL/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/EL/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/EL/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/EL/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/EL/GCExport/GCExportXML.pm | 41 + .../GCLang/EL/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/EL/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/EL/GCImport/GCImportFolder.pm | 67 + lib/gcstar/GCLang/EL/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/EL/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/EL/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/EL/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/EL/GCModels/GCTVepisodes.pm | 45 + lib/gcstar/GCLang/EL/GCModels/GCTVseries.pm | 55 + lib/gcstar/GCLang/EL/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/EL/GCModels/GCbooks.pm | 70 + lib/gcstar/GCLang/EL/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/EL/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/EL/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/EL/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/EL/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/EL/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/EL/GCModels/GCmusics.pm | 68 + lib/gcstar/GCLang/EL/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/EL/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/EL/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/EL/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/EL/GCModels/GCwines.pm | 84 + lib/gcstar/GCLang/EL/GCstar.pm | 674 ++++ lib/gcstar/GCLang/EN/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/EN/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/EN/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/EN/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/EN/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/EN/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/EN/GCExport/GCExportXML.pm | 41 + .../GCLang/EN/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/EN/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/EN/GCImport/GCImportFolder.pm | 71 + lib/gcstar/GCLang/EN/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/EN/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/EN/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/EN/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/EN/GCModels/GCTVepisodes.pm | 51 + lib/gcstar/GCLang/EN/GCModels/GCTVseries.pm | 55 + lib/gcstar/GCLang/EN/GCModels/GCboardgames.pm | 89 + lib/gcstar/GCLang/EN/GCModels/GCbooks.pm | 73 + lib/gcstar/GCLang/EN/GCModels/GCcoins.pm | 107 + lib/gcstar/GCLang/EN/GCModels/GCcomics.pm | 76 + lib/gcstar/GCLang/EN/GCModels/GCfilms.pm | 96 + lib/gcstar/GCLang/EN/GCModels/GCgames.pm | 85 + lib/gcstar/GCLang/EN/GCModels/GCgeneric.pm | 44 + lib/gcstar/GCLang/EN/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/EN/GCModels/GCmusics.pm | 71 + lib/gcstar/GCLang/EN/GCModels/GCperiodicals.pm | 55 + lib/gcstar/GCLang/EN/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/EN/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/EN/GCModels/GCstamps.pm | 192 + lib/gcstar/GCLang/EN/GCModels/GCwines.pm | 68 + lib/gcstar/GCLang/EN/GCstar.pm | 715 ++++ lib/gcstar/GCLang/ES/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/ES/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/ES/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/ES/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/ES/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/ES/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/ES/GCExport/GCExportXML.pm | 41 + .../GCLang/ES/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/ES/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/ES/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/ES/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/ES/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/ES/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/ES/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/ES/GCModels/GCTVepisodes.pm | 65 + lib/gcstar/GCLang/ES/GCModels/GCTVseries.pm | 69 + lib/gcstar/GCLang/ES/GCModels/GCboardgames.pm | 103 + lib/gcstar/GCLang/ES/GCModels/GCbooks.pm | 88 + lib/gcstar/GCLang/ES/GCModels/GCcoins.pm | 126 + lib/gcstar/GCLang/ES/GCModels/GCcomics.pm | 90 + lib/gcstar/GCLang/ES/GCModels/GCfilms.pm | 112 + lib/gcstar/GCLang/ES/GCModels/GCgames.pm | 99 + lib/gcstar/GCLang/ES/GCModels/GCgeneric.pm | 59 + lib/gcstar/GCLang/ES/GCModels/GCminicars.pm | 195 + lib/gcstar/GCLang/ES/GCModels/GCmusics.pm | 85 + lib/gcstar/GCLang/ES/GCModels/GCperiodicals.pm | 70 + lib/gcstar/GCLang/ES/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/ES/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/ES/GCModels/GCstamps.pm | 206 + lib/gcstar/GCLang/ES/GCModels/GCwines.pm | 82 + lib/gcstar/GCLang/ES/GCstar.pm | 673 ++++ lib/gcstar/GCLang/FR/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/FR/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/FR/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/FR/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/FR/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/FR/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/FR/GCExport/GCExportXML.pm | 41 + .../GCLang/FR/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/FR/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/FR/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/FR/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/FR/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/FR/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/FR/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/FR/GCModels/GCTVepisodes.pm | 60 + lib/gcstar/GCLang/FR/GCModels/GCTVseries.pm | 60 + lib/gcstar/GCLang/FR/GCModels/GCboardgames.pm | 94 + lib/gcstar/GCLang/FR/GCModels/GCbooks.pm | 78 + lib/gcstar/GCLang/FR/GCModels/GCcoins.pm | 112 + lib/gcstar/GCLang/FR/GCModels/GCcomics.pm | 81 + lib/gcstar/GCLang/FR/GCModels/GCfilms.pm | 101 + lib/gcstar/GCLang/FR/GCModels/GCgames.pm | 92 + lib/gcstar/GCLang/FR/GCModels/GCgeneric.pm | 50 + lib/gcstar/GCLang/FR/GCModels/GCminicars.pm | 190 + lib/gcstar/GCLang/FR/GCModels/GCmusics.pm | 76 + lib/gcstar/GCLang/FR/GCModels/GCperiodicals.pm | 60 + lib/gcstar/GCLang/FR/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/FR/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/FR/GCModels/GCstamps.pm | 198 + lib/gcstar/GCLang/FR/GCModels/GCwines.pm | 72 + lib/gcstar/GCLang/FR/GCstar.pm | 673 ++++ lib/gcstar/GCLang/GCLangUtils.pm | 56 + lib/gcstar/GCLang/GL/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/GL/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/GL/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/GL/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/GL/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/GL/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/GL/GCExport/GCExportXML.pm | 41 + .../GCLang/GL/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/GL/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/GL/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/GL/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/GL/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/GL/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/GL/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/GL/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/GL/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/GL/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/GL/GCModels/GCbooks.pm | 70 + lib/gcstar/GCLang/GL/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/GL/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/GL/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/GL/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/GL/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/GL/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/GL/GCModels/GCmusics.pm | 68 + lib/gcstar/GCLang/GL/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/GL/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/GL/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/GL/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/GL/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/GL/GCstar.pm | 674 ++++ lib/gcstar/GCLang/HU/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/HU/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/HU/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/HU/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/HU/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/HU/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/HU/GCExport/GCExportXML.pm | 41 + .../GCLang/HU/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/HU/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/HU/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/HU/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/HU/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/HU/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/HU/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/HU/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/HU/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/HU/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/HU/GCModels/GCbooks.pm | 70 + lib/gcstar/GCLang/HU/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/HU/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/HU/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/HU/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/HU/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/HU/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/HU/GCModels/GCmusics.pm | 68 + lib/gcstar/GCLang/HU/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/HU/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/HU/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/HU/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/HU/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/HU/GCstar.pm | 673 ++++ lib/gcstar/GCLang/ID/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/ID/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/ID/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/ID/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/ID/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/ID/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/ID/GCExport/GCExportXML.pm | 41 + .../GCLang/ID/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/ID/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/ID/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/ID/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/ID/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/ID/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/ID/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/ID/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/ID/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/ID/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/ID/GCModels/GCbooks.pm | 69 + lib/gcstar/GCLang/ID/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/ID/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/ID/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/ID/GCModels/GCgames.pm | 81 + lib/gcstar/GCLang/ID/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/ID/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/ID/GCModels/GCmusics.pm | 67 + lib/gcstar/GCLang/ID/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/ID/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/ID/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/ID/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/ID/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/ID/GCstar.pm | 673 ++++ lib/gcstar/GCLang/IT/GCExport/GCExportCSV.pm | 47 + lib/gcstar/GCLang/IT/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/IT/GCExport/GCExportHTML.pm | 71 + lib/gcstar/GCLang/IT/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/IT/GCExport/GCExportSQL.pm | 47 + lib/gcstar/GCLang/IT/GCExport/GCExportTarGz.pm | 44 + lib/gcstar/GCLang/IT/GCExport/GCExportXML.pm | 48 + .../GCLang/IT/GCImport/GCImportAlexandria.pm | 46 + lib/gcstar/GCLang/IT/GCImport/GCImportCSV.pm | 49 + lib/gcstar/GCLang/IT/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/IT/GCImport/GCImportGCstar.pm | 44 + lib/gcstar/GCLang/IT/GCImport/GCImportList.pm | 49 + lib/gcstar/GCLang/IT/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/IT/GCImport/GCImportTellico.pm | 44 + lib/gcstar/GCLang/IT/GCModels/GCTVepisodes.pm | 48 + lib/gcstar/GCLang/IT/GCModels/GCTVseries.pm | 52 + lib/gcstar/GCLang/IT/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/IT/GCModels/GCbooks.pm | 77 + lib/gcstar/GCLang/IT/GCModels/GCcoins.pm | 110 + lib/gcstar/GCLang/IT/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/IT/GCModels/GCfilms.pm | 99 + lib/gcstar/GCLang/IT/GCModels/GCgames.pm | 89 + lib/gcstar/GCLang/IT/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/IT/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/IT/GCModels/GCmusics.pm | 75 + lib/gcstar/GCLang/IT/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/IT/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/IT/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/IT/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/IT/GCModels/GCwines.pm | 73 + lib/gcstar/GCLang/IT/GCstar.pm | 678 ++++ lib/gcstar/GCLang/IT/README.txt | 9 + lib/gcstar/GCLang/NL/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/NL/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/NL/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/NL/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/NL/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/NL/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/NL/GCExport/GCExportXML.pm | 41 + .../GCLang/NL/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/NL/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/NL/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/NL/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/NL/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/NL/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/NL/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/NL/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/NL/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/NL/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/NL/GCModels/GCbooks.pm | 70 + lib/gcstar/GCLang/NL/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/NL/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/NL/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/NL/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/NL/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/NL/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/NL/GCModels/GCmusics.pm | 68 + lib/gcstar/GCLang/NL/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/NL/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/NL/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/NL/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/NL/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/NL/GCstar.pm | 671 ++++ lib/gcstar/GCLang/PL/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/PL/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/PL/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/PL/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/PL/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/PL/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/PL/GCExport/GCExportXML.pm | 41 + .../GCLang/PL/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/PL/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/PL/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/PL/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/PL/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/PL/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/PL/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/PL/GCModels/GCTVepisodes.pm | 51 + lib/gcstar/GCLang/PL/GCModels/GCTVseries.pm | 55 + lib/gcstar/GCLang/PL/GCModels/GCboardgames.pm | 89 + lib/gcstar/GCLang/PL/GCModels/GCbooks.pm | 73 + lib/gcstar/GCLang/PL/GCModels/GCcoins.pm | 107 + lib/gcstar/GCLang/PL/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/PL/GCModels/GCfilms.pm | 96 + lib/gcstar/GCLang/PL/GCModels/GCgames.pm | 85 + lib/gcstar/GCLang/PL/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/PL/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/PL/GCModels/GCmusics.pm | 72 + lib/gcstar/GCLang/PL/GCModels/GCperiodicals.pm | 55 + lib/gcstar/GCLang/PL/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/PL/GCModels/GCsoftware.pm | 86 + lib/gcstar/GCLang/PL/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/PL/GCModels/GCwines.pm | 87 + lib/gcstar/GCLang/PL/GCstar.pm | 673 ++++ lib/gcstar/GCLang/PT/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/PT/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/PT/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/PT/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/PT/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/PT/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/PT/GCExport/GCExportXML.pm | 41 + .../GCLang/PT/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/PT/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/PT/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/PT/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/PT/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/PT/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/PT/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/PT/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/PT/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/PT/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/PT/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/PT/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/PT/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/PT/GCModels/GCfilms.pm | 92 + lib/gcstar/GCLang/PT/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/PT/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/PT/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/PT/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/PT/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/PT/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/PT/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/PT/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/PT/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/PT/GCstar.pm | 673 ++++ lib/gcstar/GCLang/README | 42 + lib/gcstar/GCLang/RO/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/RO/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/RO/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/RO/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/RO/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/RO/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/RO/GCExport/GCExportXML.pm | 41 + .../GCLang/RO/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/RO/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/RO/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/RO/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/RO/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/RO/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/RO/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/RO/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/RO/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/RO/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/RO/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/RO/GCModels/GCcoins.pm | 101 + lib/gcstar/GCLang/RO/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/RO/GCModels/GCfilms.pm | 91 + lib/gcstar/GCLang/RO/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/RO/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/RO/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/RO/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/RO/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/RO/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/RO/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/RO/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/RO/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/RO/GCstar.pm | 672 ++++ lib/gcstar/GCLang/RU/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/RU/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/RU/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/RU/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/RU/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/RU/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/RU/GCExport/GCExportXML.pm | 41 + .../GCLang/RU/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/RU/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/RU/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/RU/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/RU/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/RU/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/RU/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/RU/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/RU/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/RU/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/RU/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/RU/GCModels/GCcoins.pm | 101 + lib/gcstar/GCLang/RU/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/RU/GCModels/GCfilms.pm | 92 + lib/gcstar/GCLang/RU/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/RU/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/RU/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/RU/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/RU/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/RU/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/RU/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/RU/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/RU/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/RU/GCstar.pm | 672 ++++ lib/gcstar/GCLang/SR/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/SR/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/SR/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/SR/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/SR/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/SR/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/SR/GCExport/GCExportXML.pm | 41 + .../GCLang/SR/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/SR/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/SR/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/SR/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/SR/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/SR/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/SR/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/SR/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/SR/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/SR/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/SR/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/SR/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/SR/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/SR/GCModels/GCfilms.pm | 91 + lib/gcstar/GCLang/SR/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/SR/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/SR/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/SR/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/SR/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/SR/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/SR/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/SR/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/SR/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/SR/GCstar.pm | 669 ++++ lib/gcstar/GCLang/SV/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/SV/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/SV/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/SV/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/SV/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/SV/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/SV/GCExport/GCExportXML.pm | 41 + .../GCLang/SV/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/SV/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/SV/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/SV/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/SV/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/SV/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/SV/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/SV/GCModels/GCTVepisodes.pm | 45 + lib/gcstar/GCLang/SV/GCModels/GCTVseries.pm | 49 + lib/gcstar/GCLang/SV/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/SV/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/SV/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/SV/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/SV/GCModels/GCfilms.pm | 91 + lib/gcstar/GCLang/SV/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/SV/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/SV/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/SV/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/SV/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/SV/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/SV/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/SV/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/SV/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/SV/GCstar.pm | 670 ++++ lib/gcstar/GCLang/TR/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/TR/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/TR/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/TR/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/TR/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/TR/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/TR/GCExport/GCExportXML.pm | 41 + .../GCLang/TR/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/TR/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/TR/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/TR/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/TR/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/TR/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/TR/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/TR/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/TR/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/TR/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/TR/GCModels/GCbooks.pm | 68 + lib/gcstar/GCLang/TR/GCModels/GCcoins.pm | 101 + lib/gcstar/GCLang/TR/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/TR/GCModels/GCfilms.pm | 91 + lib/gcstar/GCLang/TR/GCModels/GCgames.pm | 80 + lib/gcstar/GCLang/TR/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/TR/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/TR/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/TR/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/TR/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/TR/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/TR/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/TR/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/TR/GCstar.pm | 677 ++++ lib/gcstar/GCLang/UK/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/UK/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/UK/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/UK/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/UK/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/UK/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/UK/GCExport/GCExportXML.pm | 41 + .../GCLang/UK/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/UK/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/UK/GCImport/GCImportFolder.pm | 68 + lib/gcstar/GCLang/UK/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/UK/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/UK/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/UK/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/UK/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/UK/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/UK/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/UK/GCModels/GCbooks.pm | 69 + lib/gcstar/GCLang/UK/GCModels/GCcoins.pm | 102 + lib/gcstar/GCLang/UK/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/UK/GCModels/GCfilms.pm | 92 + lib/gcstar/GCLang/UK/GCModels/GCgames.pm | 81 + lib/gcstar/GCLang/UK/GCModels/GCgeneric.pm | 42 + lib/gcstar/GCLang/UK/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/UK/GCModels/GCmusics.pm | 66 + lib/gcstar/GCLang/UK/GCModels/GCperiodicals.pm | 52 + lib/gcstar/GCLang/UK/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/UK/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/UK/GCModels/GCstamps.pm | 189 + lib/gcstar/GCLang/UK/GCModels/GCwines.pm | 63 + lib/gcstar/GCLang/UK/GCstar.pm | 674 ++++ lib/gcstar/GCLang/ZH/GCExport/GCExportCSV.pm | 40 + lib/gcstar/GCLang/ZH/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/ZH/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/ZH/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/ZH/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/ZH/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/ZH/GCExport/GCExportXML.pm | 41 + .../GCLang/ZH/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/ZH/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/ZH/GCImport/GCImportFolder.pm | 70 + lib/gcstar/GCLang/ZH/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/ZH/GCImport/GCImportList.pm | 42 + lib/gcstar/GCLang/ZH/GCImport/GCImportScanner.pm | 50 + lib/gcstar/GCLang/ZH/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/ZH/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/ZH/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/ZH/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/ZH/GCModels/GCbooks.pm | 70 + lib/gcstar/GCLang/ZH/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/ZH/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/ZH/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/ZH/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/ZH/GCModels/GCgeneric.pm | 44 + lib/gcstar/GCLang/ZH/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/ZH/GCModels/GCmusics.pm | 68 + lib/gcstar/GCLang/ZH/GCModels/GCperiodicals.pm | 55 + lib/gcstar/GCLang/ZH/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/ZH/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/ZH/GCModels/GCstamps.pm | 192 + lib/gcstar/GCLang/ZH/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/ZH/GCstar.pm | 654 ++++ lib/gcstar/GCLang/ZH_CN/GCExport/GCExportCSV.pm | 40 + .../GCLang/ZH_CN/GCExport/GCExportExternal.pm | 38 + lib/gcstar/GCLang/ZH_CN/GCExport/GCExportHTML.pm | 64 + lib/gcstar/GCLang/ZH_CN/GCExport/GCExportPDB.pm | 38 + lib/gcstar/GCLang/ZH_CN/GCExport/GCExportSQL.pm | 40 + lib/gcstar/GCLang/ZH_CN/GCExport/GCExportTarGz.pm | 37 + lib/gcstar/GCLang/ZH_CN/GCExport/GCExportXML.pm | 41 + .../GCLang/ZH_CN/GCImport/GCImportAlexandria.pm | 39 + lib/gcstar/GCLang/ZH_CN/GCImport/GCImportCSV.pm | 42 + lib/gcstar/GCLang/ZH_CN/GCImport/GCImportFolder.pm | 70 + lib/gcstar/GCLang/ZH_CN/GCImport/GCImportGCstar.pm | 37 + lib/gcstar/GCLang/ZH_CN/GCImport/GCImportList.pm | 42 + .../GCLang/ZH_CN/GCImport/GCImportScanner.pm | 50 + .../GCLang/ZH_CN/GCImport/GCImportTellico.pm | 37 + lib/gcstar/GCLang/ZH_CN/GCModels/GCTVepisodes.pm | 46 + lib/gcstar/GCLang/ZH_CN/GCModels/GCTVseries.pm | 50 + lib/gcstar/GCLang/ZH_CN/GCModels/GCboardgames.pm | 86 + lib/gcstar/GCLang/ZH_CN/GCModels/GCbooks.pm | 70 + lib/gcstar/GCLang/ZH_CN/GCModels/GCcoins.pm | 104 + lib/gcstar/GCLang/ZH_CN/GCModels/GCcomics.pm | 73 + lib/gcstar/GCLang/ZH_CN/GCModels/GCfilms.pm | 93 + lib/gcstar/GCLang/ZH_CN/GCModels/GCgames.pm | 82 + lib/gcstar/GCLang/ZH_CN/GCModels/GCgeneric.pm | 44 + lib/gcstar/GCLang/ZH_CN/GCModels/GCminicars.pm | 182 + lib/gcstar/GCLang/ZH_CN/GCModels/GCmusics.pm | 68 + lib/gcstar/GCLang/ZH_CN/GCModels/GCperiodicals.pm | 55 + lib/gcstar/GCLang/ZH_CN/GCModels/GCsmartcards.pm | 108 + lib/gcstar/GCLang/ZH_CN/GCModels/GCsoftware.pm | 84 + lib/gcstar/GCLang/ZH_CN/GCModels/GCstamps.pm | 192 + lib/gcstar/GCLang/ZH_CN/GCModels/GCwines.pm | 65 + lib/gcstar/GCLang/ZH_CN/GCstar.pm | 653 ++++ lib/gcstar/GCMail.pm | 474 +++ lib/gcstar/GCMainWindow.pm | 4124 ++++++++++++++++++++ lib/gcstar/GCMenu.pm | 1840 +++++++++ lib/gcstar/GCModel.pm | 2896 ++++++++++++++ lib/gcstar/GCModels/GCTVepisodes.gcm | 390 ++ lib/gcstar/GCModels/GCTVseries.gcm | 417 ++ lib/gcstar/GCModels/GCboardgames.gcm | 478 +++ lib/gcstar/GCModels/GCbooks.gcm | 278 ++ lib/gcstar/GCModels/GCcoins.gcm | 275 ++ lib/gcstar/GCModels/GCcomics.gcm | 353 ++ lib/gcstar/GCModels/GCfilms.gcm | 604 +++ lib/gcstar/GCModels/GCgames.gcm | 492 +++ lib/gcstar/GCModels/GCminicars.gcm | 892 +++++ lib/gcstar/GCModels/GCmusics.gcm | 250 ++ lib/gcstar/GCModels/GCperiodicals.gcm | 105 + lib/gcstar/GCModels/GCsmartcards.gcm | 379 ++ lib/gcstar/GCModels/GCsoftware.gcm | 438 +++ lib/gcstar/GCModels/GCstamps.gcm | 702 ++++ lib/gcstar/GCModels/GCwines.gcm | 356 ++ lib/gcstar/GCOptions.pm | 1670 ++++++++ lib/gcstar/GCPanel.pm | 1540 ++++++++ lib/gcstar/GCPlugins.pm | 1783 +++++++++ lib/gcstar/GCPlugins/GCPluginsBase.pm | 396 ++ .../GCPlugins/GCTVepisodes/GCTVepisodesCommon.pm | 67 + lib/gcstar/GCPlugins/GCTVepisodes/GCTvdb.pm | 360 ++ lib/gcstar/GCPlugins/GCTVepisodes/GCTvdbES.pm | 61 + lib/gcstar/GCPlugins/GCTVepisodes/GCTvdbFR.pm | 61 + lib/gcstar/GCPlugins/GCTVepisodes/GCTvdbIT.pm | 60 + .../GCPlugins/GCTVseries/GCTVseriesCommon.pm | 53 + lib/gcstar/GCPlugins/GCTVseries/GCThemoviedb.pm | 340 ++ lib/gcstar/GCPlugins/GCTVseries/GCTvdb.pm | 466 +++ lib/gcstar/GCPlugins/GCTVseries/GCTvdbES.pm | 61 + lib/gcstar/GCPlugins/GCTVseries/GCTvdbFR.pm | 61 + lib/gcstar/GCPlugins/GCTVseries/GCTvdbIT.pm | 60 + .../GCPlugins/GCboardgames/GCReservoirJeux.pm | 418 ++ .../GCPlugins/GCboardgames/GCboardgamegeek.pm | 278 ++ .../GCPlugins/GCboardgames/GCboardgamesCommon.pm | 58 + lib/gcstar/GCPlugins/GCboardgames/GCtrictrac.pm | 462 +++ lib/gcstar/GCPlugins/GCbooks/GCAdlibrisFI.pm | 59 + lib/gcstar/GCPlugins/GCbooks/GCAdlibrisSV.pm | 59 + lib/gcstar/GCPlugins/GCbooks/GCAlapage.pm | 391 ++ lib/gcstar/GCPlugins/GCbooks/GCAmazon.pm | 352 ++ lib/gcstar/GCPlugins/GCbooks/GCAmazonCA.pm | 61 + lib/gcstar/GCPlugins/GCbooks/GCAmazonDE.pm | 56 + lib/gcstar/GCPlugins/GCbooks/GCAmazonFR.pm | 57 + lib/gcstar/GCPlugins/GCbooks/GCAmazonUK.pm | 61 + lib/gcstar/GCPlugins/GCbooks/GCBDGest.pm | 477 +++ .../GCPlugins/GCbooks/GCBibliotekaNarodowa.pm | 374 ++ lib/gcstar/GCPlugins/GCbooks/GCBokkilden.pm | 295 ++ lib/gcstar/GCPlugins/GCbooks/GCBol.pm | 485 +++ lib/gcstar/GCPlugins/GCbooks/GCBuscape.pm | 479 +++ lib/gcstar/GCPlugins/GCbooks/GCCasadelibro.pm | 420 ++ lib/gcstar/GCPlugins/GCbooks/GCChapitre.pm | 430 ++ lib/gcstar/GCPlugins/GCbooks/GCDoubanbook.pm | 238 ++ lib/gcstar/GCPlugins/GCbooks/GCFnac.pm | 462 +++ lib/gcstar/GCPlugins/GCbooks/GCFnacPT.pm | 390 ++ lib/gcstar/GCPlugins/GCbooks/GCISBNdb.pm | 370 ++ .../GCPlugins/GCbooks/GCInternetBokHandeln.pm | 464 +++ lib/gcstar/GCPlugins/GCbooks/GCInternetBookShop.pm | 376 ++ lib/gcstar/GCPlugins/GCbooks/GCLeLivre.pm | 334 ++ lib/gcstar/GCPlugins/GCbooks/GCLiberOnWeb.pm | 418 ++ lib/gcstar/GCPlugins/GCbooks/GCMareno.pm | 365 ++ lib/gcstar/GCPlugins/GCbooks/GCMediabooks.pm | 333 ++ lib/gcstar/GCPlugins/GCbooks/GCMerlin.pm | 389 ++ lib/gcstar/GCPlugins/GCbooks/GCNUKat.pm | 447 +++ lib/gcstar/GCPlugins/GCbooks/GCNooSFere.pm | 462 +++ lib/gcstar/GCPlugins/GCbooks/GCSaraiva.pm | 303 ++ .../GCPlugins/GCbooks/GCbooksAdlibrisCommon.pm | 331 ++ .../GCPlugins/GCbooks/GCbooksAmazonCommon.pm | 65 + lib/gcstar/GCPlugins/GCbooks/GCbooksCommon.pm | 61 + lib/gcstar/GCPlugins/GCcomics/GCbedetheque.pm | 398 ++ lib/gcstar/GCPlugins/GCcomics/GCcomicbookdb.pm | 546 +++ lib/gcstar/GCPlugins/GCcomics/GCcomicsCommon.pm | 49 + lib/gcstar/GCPlugins/GCcomics/GCmangasanctuary.pm | 503 +++ lib/gcstar/GCPlugins/GCfilms/GCAlapage.pm | 267 ++ lib/gcstar/GCPlugins/GCfilms/GCAllmovie.pm | 431 ++ lib/gcstar/GCPlugins/GCfilms/GCAllocine.pm | 403 ++ lib/gcstar/GCPlugins/GCfilms/GCAlpacineES.pm | 435 +++ lib/gcstar/GCPlugins/GCfilms/GCAmazon.pm | 281 ++ lib/gcstar/GCPlugins/GCfilms/GCAmazonDE.pm | 291 ++ lib/gcstar/GCPlugins/GCfilms/GCAmazonFR.pm | 304 ++ lib/gcstar/GCPlugins/GCfilms/GCAmazonUK.pm | 264 ++ lib/gcstar/GCPlugins/GCfilms/GCAniDB.pm | 279 ++ lib/gcstar/GCPlugins/GCfilms/GCAnimator.pm | 236 ++ lib/gcstar/GCPlugins/GCfilms/GCAnimeNfoA.pm | 266 ++ lib/gcstar/GCPlugins/GCfilms/GCAnimeka.pm | 295 ++ lib/gcstar/GCPlugins/GCfilms/GCBeyazPerde.pm | 340 ++ .../GCPlugins/GCfilms/GCCartelesPeliculasES.pm | 351 ++ lib/gcstar/GCPlugins/GCfilms/GCCinemaClock.pm | 271 ++ lib/gcstar/GCPlugins/GCfilms/GCCinemotions.pm | 284 ++ lib/gcstar/GCPlugins/GCfilms/GCCsfd.pm | 699 ++++ lib/gcstar/GCPlugins/GCfilms/GCCulturalia.pm | 241 ++ lib/gcstar/GCPlugins/GCfilms/GCDVDEmpire.pm | 427 ++ lib/gcstar/GCPlugins/GCfilms/GCDVDFr.pm | 374 ++ lib/gcstar/GCPlugins/GCfilms/GCDVDPost.pm | 269 ++ lib/gcstar/GCPlugins/GCfilms/GCDicshop.pm | 343 ++ lib/gcstar/GCPlugins/GCfilms/GCDoubanfilm.pm | 255 ++ lib/gcstar/GCPlugins/GCfilms/GCFilmAffinityEN.pm | 334 ++ lib/gcstar/GCPlugins/GCfilms/GCFilmAffinityES.pm | 334 ++ lib/gcstar/GCPlugins/GCfilms/GCFilmUP.pm | 252 ++ lib/gcstar/GCPlugins/GCfilms/GCFilmWeb.pm | 369 ++ lib/gcstar/GCPlugins/GCfilms/GCIbs.pm | 409 ++ lib/gcstar/GCPlugins/GCfilms/GCImdb.pm | 439 +++ lib/gcstar/GCPlugins/GCfilms/GCKinopoisk.pm | 386 ++ lib/gcstar/GCPlugins/GCfilms/GCMediadis.pm | 316 ++ lib/gcstar/GCPlugins/GCfilms/GCMetropoliES.pm | 382 ++ lib/gcstar/GCPlugins/GCfilms/GCMonsieurCinema.pm | 272 ++ lib/gcstar/GCPlugins/GCfilms/GCMovieMeter.pm | 429 ++ lib/gcstar/GCPlugins/GCfilms/GCMoviecovers.pm | 246 ++ lib/gcstar/GCPlugins/GCfilms/GCNasheKino.pm | 222 ++ lib/gcstar/GCPlugins/GCfilms/GCOFDb.pm | 304 ++ lib/gcstar/GCPlugins/GCfilms/GCOdeonHU.pm | 305 ++ lib/gcstar/GCPlugins/GCfilms/GCOnet.pm | 327 ++ lib/gcstar/GCPlugins/GCfilms/GCPortHU.pm | 343 ++ lib/gcstar/GCPlugins/GCfilms/GCStopklatka.pm | 355 ++ lib/gcstar/GCPlugins/GCfilms/GCThemoviedb.pm | 337 ++ lib/gcstar/GCPlugins/GCfilms/GCThemoviedbDE.pm | 56 + lib/gcstar/GCPlugins/GCfilms/GCThemoviedbES.pm | 56 + lib/gcstar/GCPlugins/GCfilms/GCThemoviedbFR.pm | 56 + .../GCPlugins/GCfilms/GCfilmsAmazonCommon.pm | 59 + lib/gcstar/GCPlugins/GCfilms/GCfilmsCommon.pm | 70 + lib/gcstar/GCPlugins/GCgames/GCAlapage.pm | 262 ++ lib/gcstar/GCPlugins/GCgames/GCAmazon.pm | 115 + lib/gcstar/GCPlugins/GCgames/GCAmazonCA.pm | 115 + lib/gcstar/GCPlugins/GCgames/GCAmazonDE.pm | 114 + lib/gcstar/GCPlugins/GCgames/GCAmazonFR.pm | 118 + lib/gcstar/GCPlugins/GCgames/GCAmazonJP.pm | 120 + lib/gcstar/GCPlugins/GCgames/GCAmazonUK.pm | 115 + lib/gcstar/GCPlugins/GCgames/GCDicoDuNet.pm | 291 ++ lib/gcstar/GCPlugins/GCgames/GCGameSpot.pm | 490 +++ lib/gcstar/GCPlugins/GCgames/GCJeuxVideoCom.pm | 447 +++ lib/gcstar/GCPlugins/GCgames/GCJeuxVideoFr.pm | 425 ++ lib/gcstar/GCPlugins/GCgames/GCLudus.pm | 367 ++ lib/gcstar/GCPlugins/GCgames/GCMobyGames.pm | 541 +++ lib/gcstar/GCPlugins/GCgames/GCNextGame.pm | 480 +++ lib/gcstar/GCPlugins/GCgames/GCTheLegacy.pm | 316 ++ .../GCPlugins/GCgames/GCgamesAmazonCommon.pm | 314 ++ lib/gcstar/GCPlugins/GCgames/GCgamesCommon.pm | 87 + lib/gcstar/GCPlugins/GCmusics/GCDiscogs.pm | 333 ++ lib/gcstar/GCPlugins/GCmusics/GCDoubanmusic.pm | 238 ++ lib/gcstar/GCPlugins/GCmusics/GCMusicBrainz.pm | 309 ++ lib/gcstar/GCPlugins/GCmusics/GCmusicsCommon.pm | 62 + lib/gcstar/GCPlugins/GCstar/GCAmazonCommon.pm | 132 + lib/gcstar/GCSplash.pm | 241 ++ lib/gcstar/GCStats.pm | 464 +++ lib/gcstar/GCStyle.pm | 67 + lib/gcstar/GCUpdater.pm | 174 + lib/gcstar/GCUtils.pm | 640 +++ lib/gcstar/GCWidgets.pm | 429 ++ man/gcstar.1 | 92 + packages/GCstar_Packaging_Policy.txt | 33 + packages/debian/changelog | 5 + packages/debian/compat | 1 + packages/debian/control | 25 + packages/debian/copyright | 26 + packages/debian/dirs | 1 + packages/debian/docs | 3 + packages/debian/gcstar.install | 2 + packages/debian/gcstar_logo.xpm | 291 ++ packages/debian/lintian/gcstar | 2 + packages/debian/menu | 1 + packages/debian/patches/00list | 5 + packages/debian/patches/01-set_usr_lib.dpatch | 26 + packages/debian/patches/02gzip-manpage.dpatch | 19 + .../patches/03_change_default_browser.dpatch | 19 + .../debian/patches/04-install-set_usr_lib.dpatch | 21 + packages/debian/patches/07_fix_manpath.dpatch | 19 + packages/debian/postinst | 8 + packages/debian/postrm | 8 + packages/debian/rules | 112 + packages/fedora/gcstar.spec | 111 + packages/rpm/gcstar.spec | 41 + packages/win32/createExe.bat | 1 + packages/win32/gcs_lang.nsh | 65 + packages/win32/gcstar.bat | 3 + packages/win32/gcstar.nsi | 362 ++ packages/win32/img/banner_left.bmp | Bin 0 -> 154542 bytes packages/win32/img/banner_top.bmp | Bin 0 -> 34256 bytes packages/win32/img/checks.bmp | Bin 0 -> 2614 bytes packages/win32/img/icon_install.ico | Bin 0 -> 16958 bytes packages/win32/img/icon_uninstall.ico | Bin 0 -> 16958 bytes packages/win32/img/uninstall_top.bmp | Bin 0 -> 34256 bytes packages/win32/img/website.ico | Bin 0 -> 9662 bytes packages/win32/langs/gcs_Bulgarian.nsh | 70 + packages/win32/langs/gcs_Czech.nsh | 70 + packages/win32/langs/gcs_English.nsh | 70 + packages/win32/langs/gcs_French.nsh | 68 + packages/win32/langs/gcs_German.nsh | 70 + packages/win32/langs/gcs_Italian.nsh | 70 + packages/win32/langs/gcs_Polish.nsh | 70 + packages/win32/langs/gcs_Romanian.nsh | 70 + packages/win32/langs/gcs_Russian.nsh | 70 + packages/win32/langs/gcs_SerbianLatin.nsh | 70 + packages/win32/langs/gcs_Spanish.nsh | 70 + packages/win32/langs/gcs_Turkish.nsh | 70 + packages/win32/update.bat | 4 + share/applications/gcstar-thumbnailer | 260 ++ share/applications/gcstar.desktop | 18 + share/applications/gcstar.xml | 7 + share/gcstar/LICENSE | 340 ++ share/gcstar/fonts/AUTHORS | 8 + share/gcstar/fonts/COPYING | 340 ++ share/gcstar/fonts/ChangeLog | 176 + share/gcstar/fonts/LiberationSans-Regular.ttf | Bin 0 -> 140600 bytes share/gcstar/fonts/License.txt | 19 + share/gcstar/fonts/README | 90 + share/gcstar/genres/EN.genres | 12 + share/gcstar/genres/ES.genres | 12 + share/gcstar/genres/FR.genres | 4 + share/gcstar/helpers/xdg-open | 469 +++ share/gcstar/html_models/GCboardgames/piwi | 275 ++ share/gcstar/html_models/GCboardgames/piwi.png | Bin 0 -> 50926 bytes share/gcstar/html_models/GCbooks/FloFred | 73 + share/gcstar/html_models/GCbooks/FloFred.png | Bin 0 -> 69747 bytes share/gcstar/html_models/GCbooks/NellistosDark | 67 + share/gcstar/html_models/GCbooks/NellistosDark.png | Bin 0 -> 26138 bytes share/gcstar/html_models/GCbooks/NellistosLight | 66 + .../gcstar/html_models/GCbooks/NellistosLight.png | Bin 0 -> 30503 bytes share/gcstar/html_models/GCbooks/Shelf | 12 + share/gcstar/html_models/GCbooks/Shelf.png | Bin 0 -> 62375 bytes share/gcstar/html_models/GCbooks/Simple | 10 + share/gcstar/html_models/GCbooks/Simple.png | Bin 0 -> 29817 bytes share/gcstar/html_models/GCcoins/Simple | 11 + share/gcstar/html_models/GCcoins/Simple.png | Bin 0 -> 20252 bytes share/gcstar/html_models/GCfilms/Flat | 109 + share/gcstar/html_models/GCfilms/Flat.png | Bin 0 -> 77945 bytes share/gcstar/html_models/GCfilms/Shelf | 284 ++ share/gcstar/html_models/GCfilms/Shelf.png | Bin 0 -> 86073 bytes share/gcstar/html_models/GCfilms/Simple | 100 + share/gcstar/html_models/GCfilms/Simple.png | Bin 0 -> 28419 bytes share/gcstar/html_models/GCfilms/Tabs | 245 ++ share/gcstar/html_models/GCfilms/Tabs.png | Bin 0 -> 51681 bytes share/gcstar/html_models/GCfilms/Tian | 276 ++ share/gcstar/html_models/GCfilms/Tian-Mario | 276 ++ share/gcstar/html_models/GCfilms/Tian-Mario-Kim | 281 ++ share/gcstar/html_models/GCfilms/Tian-Mario.png | Bin 0 -> 30879 bytes share/gcstar/html_models/GCfilms/Tian.png | Bin 0 -> 43774 bytes share/gcstar/html_models/GCfilms/float | 120 + share/gcstar/html_models/GCfilms/float.png | Bin 0 -> 67698 bytes share/gcstar/html_models/GCfilms/rootII_design | 118 + share/gcstar/html_models/GCfilms/rootII_design.png | Bin 0 -> 49787 bytes share/gcstar/html_models/GCgames/Flat | 111 + share/gcstar/html_models/GCgames/Flat.png | Bin 0 -> 91347 bytes share/gcstar/html_models/GCgames/Simple | 10 + share/gcstar/html_models/GCgames/Simple.png | Bin 0 -> 28909 bytes share/gcstar/html_models/GCgames/Tabs | 284 ++ share/gcstar/html_models/GCgames/Tabs.png | Bin 0 -> 46938 bytes share/gcstar/html_models/GCminicars/Tian-Jim | 279 ++ share/gcstar/html_models/GCminicars/Tian-Jim.png | Bin 0 -> 39085 bytes share/gcstar/html_models/GCmusics/Shelf | 298 ++ share/gcstar/html_models/GCmusics/Shelf.png | Bin 0 -> 62357 bytes share/gcstar/html_models/GCmusics/Simple | 10 + share/gcstar/html_models/GCmusics/Simple.png | Bin 0 -> 24463 bytes share/gcstar/html_models/GCstar/Shelf | 282 ++ share/gcstar/html_models/GCstar/Shelf.png | Bin 0 -> 86073 bytes share/gcstar/html_models/GCstar/Simple | 96 + share/gcstar/html_models/GCstar/Simple.png | Bin 0 -> 28419 bytes share/gcstar/icons/GCstar.ico | Bin 0 -> 25214 bytes share/gcstar/icons/gcstar_128x128.png | Bin 0 -> 13072 bytes share/gcstar/icons/gcstar_16x16.png | Bin 0 -> 838 bytes share/gcstar/icons/gcstar_192x192.png | Bin 0 -> 21943 bytes share/gcstar/icons/gcstar_22x22.png | Bin 0 -> 1266 bytes share/gcstar/icons/gcstar_24x24.png | Bin 0 -> 1419 bytes share/gcstar/icons/gcstar_256x256.png | Bin 0 -> 32260 bytes share/gcstar/icons/gcstar_32x32.png | Bin 0 -> 2018 bytes share/gcstar/icons/gcstar_36x36.png | Bin 0 -> 2398 bytes share/gcstar/icons/gcstar_48x48.png | Bin 0 -> 3528 bytes share/gcstar/icons/gcstar_64x64.png | Bin 0 -> 5092 bytes share/gcstar/icons/gcstar_72x72.png | Bin 0 -> 6158 bytes share/gcstar/icons/gcstar_96x96.png | Bin 0 -> 9134 bytes share/gcstar/icons/gcstar_scalable.svg | 354 ++ share/gcstar/icons/icon_install.ico | Bin 0 -> 16958 bytes share/gcstar/icons/star.png | Bin 0 -> 982 bytes share/gcstar/icons/star_hover.png | Bin 0 -> 1515 bytes share/gcstar/icons/stardark.png | Bin 0 -> 1056 bytes share/gcstar/icons/stardark_hover.png | Bin 0 -> 1553 bytes share/gcstar/icons/web.ico | Bin 0 -> 9662 bytes share/gcstar/list_bg/Box/group.png | Bin 0 -> 14419 bytes share/gcstar/list_bg/Box/list_bg.png | Bin 0 -> 34117 bytes share/gcstar/list_bg/Box/style | 2 + share/gcstar/list_bg/Brick_and_Glass/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Brick_and_Glass/list_bg.png | Bin 0 -> 103747 bytes share/gcstar/list_bg/Brick_and_Glass/list_fg.png | Bin 0 -> 18630 bytes share/gcstar/list_bg/Brick_and_Glass/style | 3 + share/gcstar/list_bg/Dark_Glass/group.png | Bin 0 -> 269 bytes share/gcstar/list_bg/Dark_Glass/list_bg.png | Bin 0 -> 8186 bytes share/gcstar/list_bg/Dark_Glass/list_fg.png | Bin 0 -> 10759 bytes share/gcstar/list_bg/Dark_Glass/style | 4 + share/gcstar/list_bg/Glass/group.png | Bin 0 -> 1161 bytes share/gcstar/list_bg/Glass/list_bg.png | Bin 0 -> 1862 bytes share/gcstar/list_bg/Glass/list_fg.png | Bin 0 -> 3170 bytes share/gcstar/list_bg/Glass/style | 3 + share/gcstar/list_bg/Green_Glass/group.png | Bin 0 -> 3279 bytes share/gcstar/list_bg/Green_Glass/list_bg.png | Bin 0 -> 887 bytes share/gcstar/list_bg/Green_Glass/list_fg.png | Bin 0 -> 2456 bytes share/gcstar/list_bg/Green_Glass/style | 3 + share/gcstar/list_bg/Luxury_Green_Glass/group.png | Bin 0 -> 13799 bytes .../gcstar/list_bg/Luxury_Green_Glass/list_bg.png | Bin 0 -> 69106 bytes .../gcstar/list_bg/Luxury_Green_Glass/list_fg.png | Bin 0 -> 13544 bytes share/gcstar/list_bg/Luxury_Green_Glass/style | 3 + share/gcstar/list_bg/Luxury_Green_Wood/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Luxury_Green_Wood/list_bg.png | Bin 0 -> 65944 bytes share/gcstar/list_bg/Luxury_Green_Wood/list_fg.png | Bin 0 -> 50584 bytes share/gcstar/list_bg/Luxury_Green_Wood/style | 3 + share/gcstar/list_bg/Luxury_Grey_Glass/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Luxury_Grey_Glass/list_bg.png | Bin 0 -> 57581 bytes share/gcstar/list_bg/Luxury_Grey_Glass/list_fg.png | Bin 0 -> 15128 bytes share/gcstar/list_bg/Luxury_Grey_Glass/style | 3 + share/gcstar/list_bg/Luxury_Grey_Wood/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Luxury_Grey_Wood/list_bg.png | Bin 0 -> 52404 bytes share/gcstar/list_bg/Luxury_Grey_Wood/list_fg.png | Bin 0 -> 50584 bytes share/gcstar/list_bg/Luxury_Grey_Wood/style | 3 + share/gcstar/list_bg/Luxury_Purple_Glass/group.png | Bin 0 -> 13799 bytes .../gcstar/list_bg/Luxury_Purple_Glass/list_bg.png | Bin 0 -> 69863 bytes .../gcstar/list_bg/Luxury_Purple_Glass/list_fg.png | Bin 0 -> 13837 bytes share/gcstar/list_bg/Luxury_Purple_Glass/style | 3 + share/gcstar/list_bg/Luxury_Purple_Wood/group.png | Bin 0 -> 13799 bytes .../gcstar/list_bg/Luxury_Purple_Wood/list_bg.png | Bin 0 -> 66207 bytes .../gcstar/list_bg/Luxury_Purple_Wood/list_fg.png | Bin 0 -> 50584 bytes share/gcstar/list_bg/Luxury_Purple_Wood/style | 3 + share/gcstar/list_bg/Luxury_Red_Glass/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Luxury_Red_Glass/list_bg.png | Bin 0 -> 72062 bytes share/gcstar/list_bg/Luxury_Red_Glass/list_fg.png | Bin 0 -> 14708 bytes share/gcstar/list_bg/Luxury_Red_Glass/style | 3 + share/gcstar/list_bg/Luxury_Red_Wood/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Luxury_Red_Wood/list_bg.png | Bin 0 -> 66125 bytes share/gcstar/list_bg/Luxury_Red_Wood/list_fg.png | Bin 0 -> 50584 bytes share/gcstar/list_bg/Luxury_Red_Wood/style | 3 + share/gcstar/list_bg/Marble/group.png | Bin 0 -> 18033 bytes share/gcstar/list_bg/Marble/list_bg.png | Bin 0 -> 38610 bytes share/gcstar/list_bg/Marble/style | 2 + share/gcstar/list_bg/Wood/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Wood/list_bg.png | Bin 0 -> 48015 bytes share/gcstar/list_bg/Wood/list_fg.png | Bin 0 -> 48914 bytes share/gcstar/list_bg/Wood/style | 3 + share/gcstar/list_bg/Wood2/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Wood2/list_bg.png | Bin 0 -> 49505 bytes share/gcstar/list_bg/Wood2/list_fg.png | Bin 0 -> 50584 bytes share/gcstar/list_bg/Wood2/style | 3 + share/gcstar/list_bg/Wood_and_Glass/group.png | Bin 0 -> 13799 bytes share/gcstar/list_bg/Wood_and_Glass/list_bg.png | Bin 0 -> 53072 bytes share/gcstar/list_bg/Wood_and_Glass/list_fg.png | Bin 0 -> 9708 bytes share/gcstar/list_bg/Wood_and_Glass/style | 3 + share/gcstar/logos/Peri.png | Bin 0 -> 11580 bytes share/gcstar/logos/Peri_main_logo.png | Bin 0 -> 18759 bytes share/gcstar/logos/Peri_main_logo.svg | 3436 ++++++++++++++++ share/gcstar/logos/about.png | Bin 0 -> 13470 bytes share/gcstar/logos/bg_no.png | Bin 0 -> 7541 bytes share/gcstar/logos/book_no.png | Bin 0 -> 7141 bytes share/gcstar/logos/button.png | Bin 0 -> 3528 bytes share/gcstar/logos/cd_no.png | Bin 0 -> 5234 bytes share/gcstar/logos/film_no.png | Bin 0 -> 9744 bytes share/gcstar/logos/find.png | Bin 0 -> 1258 bytes share/gcstar/logos/install.png | Bin 0 -> 4087 bytes share/gcstar/logos/no.png | Bin 0 -> 10747 bytes share/gcstar/logos/no_minicars.png | Bin 0 -> 34678 bytes share/gcstar/logos/no_smartcards.png | Bin 0 -> 30332 bytes share/gcstar/logos/no_stamp.png | Bin 0 -> 14581 bytes share/gcstar/logos/periscope_main_logo.svg | 1230 ++++++ share/gcstar/logos/splash.png | Bin 0 -> 7061 bytes share/gcstar/overlays/canevas-timbre.png | Bin 0 -> 28427 bytes share/gcstar/overlays/cd.png | Bin 0 -> 12950 bytes share/gcstar/overlays/dvd.png | Bin 0 -> 9206 bytes share/gcstar/overlays/favourite_large.png | Bin 0 -> 2209 bytes share/gcstar/overlays/favourite_med.png | Bin 0 -> 1480 bytes share/gcstar/overlays/favourite_small.png | Bin 0 -> 1156 bytes share/gcstar/overlays/favourite_verysmall.png | Bin 0 -> 868 bytes share/gcstar/overlays/favourite_xlarge.png | Bin 0 -> 3713 bytes share/gcstar/overlays/film.png | Bin 0 -> 34689 bytes share/gcstar/overlays/flip.png | Bin 0 -> 1734 bytes share/gcstar/overlays/flip2.png | Bin 0 -> 1745 bytes share/gcstar/overlays/lend_large.png | Bin 0 -> 1743 bytes share/gcstar/overlays/lend_med.png | Bin 0 -> 1250 bytes share/gcstar/overlays/lend_small.png | Bin 0 -> 1003 bytes share/gcstar/overlays/lend_verysmall.png | Bin 0 -> 744 bytes share/gcstar/overlays/lend_xlarge.png | Bin 0 -> 2956 bytes share/gcstar/overlays/minicars.png | Bin 0 -> 1554 bytes share/gcstar/overlays/subtle.png | Bin 0 -> 8322 bytes share/gcstar/panels/Classic | 29 + share/gcstar/panels/Dark | 29 + share/gcstar/panels/WebSite | 29 + share/gcstar/schemas/gcm.xsd | 291 ++ share/gcstar/style/GCstar/gtkrc | 277 ++ share/gcstar/style/GCstar/icons/about/16x16.png | Bin 0 -> 903 bytes share/gcstar/style/GCstar/icons/about/64x64.png | Bin 0 -> 5254 bytes share/gcstar/style/GCstar/icons/add/16x16.png | Bin 0 -> 700 bytes share/gcstar/style/GCstar/icons/add/24x24.png | Bin 0 -> 1404 bytes share/gcstar/style/GCstar/icons/add/32x32.png | Bin 0 -> 1782 bytes share/gcstar/style/GCstar/icons/cancel/24x24.png | Bin 0 -> 1469 bytes share/gcstar/style/GCstar/icons/cancel/32x32.png | Bin 0 -> 2582 bytes share/gcstar/style/GCstar/icons/clear/24x24.png | Bin 0 -> 1644 bytes share/gcstar/style/GCstar/icons/clear/32x32.png | Bin 0 -> 2509 bytes share/gcstar/style/GCstar/icons/convert/16x16.png | Bin 0 -> 750 bytes share/gcstar/style/GCstar/icons/convert/24x24.png | Bin 0 -> 1547 bytes share/gcstar/style/GCstar/icons/convert/32x32.png | Bin 0 -> 2330 bytes share/gcstar/style/GCstar/icons/delete/16x16.png | Bin 0 -> 946 bytes share/gcstar/style/GCstar/icons/delete/24x24.png | Bin 0 -> 1855 bytes share/gcstar/style/GCstar/icons/delete/32x32.png | Bin 0 -> 2815 bytes .../gcstar/style/GCstar/icons/directory/32x32.png | Bin 0 -> 2052 bytes share/gcstar/style/GCstar/icons/dnd/16x16.png | Bin 0 -> 818 bytes share/gcstar/style/GCstar/icons/error/64x64.png | Bin 0 -> 4918 bytes share/gcstar/style/GCstar/icons/execute/16x16.png | Bin 0 -> 936 bytes share/gcstar/style/GCstar/icons/execute/24x24.png | Bin 0 -> 1772 bytes share/gcstar/style/GCstar/icons/execute/32x32.png | Bin 0 -> 2729 bytes share/gcstar/style/GCstar/icons/find/16x16.png | Bin 0 -> 871 bytes share/gcstar/style/GCstar/icons/find/24x24.png | Bin 0 -> 1700 bytes share/gcstar/style/GCstar/icons/find/32x32.png | Bin 0 -> 2489 bytes share/gcstar/style/GCstar/icons/go-back/16x16.png | Bin 0 -> 387 bytes share/gcstar/style/GCstar/icons/go-back/24x24.png | Bin 0 -> 572 bytes share/gcstar/style/GCstar/icons/go-down/16x16.png | Bin 0 -> 402 bytes share/gcstar/style/GCstar/icons/go-down/24x24.png | Bin 0 -> 616 bytes .../gcstar/style/GCstar/icons/go-forward/16x16.png | Bin 0 -> 380 bytes .../gcstar/style/GCstar/icons/go-forward/24x24.png | Bin 0 -> 593 bytes share/gcstar/style/GCstar/icons/go-up/16x16.png | Bin 0 -> 308 bytes share/gcstar/style/GCstar/icons/go-up/24x24.png | Bin 0 -> 571 bytes share/gcstar/style/GCstar/icons/help/16x16.png | Bin 0 -> 923 bytes share/gcstar/style/GCstar/icons/help/32x32.png | Bin 0 -> 2326 bytes share/gcstar/style/GCstar/icons/help/64x64.png | Bin 0 -> 5393 bytes share/gcstar/style/GCstar/icons/home/32x32.png | Bin 0 -> 2771 bytes share/gcstar/style/GCstar/icons/jump-to/24x24.png | Bin 0 -> 1850 bytes share/gcstar/style/GCstar/icons/jump-to/32x32.png | Bin 0 -> 2835 bytes .../gcstar/style/GCstar/icons/media-next/24x24.png | Bin 0 -> 1195 bytes .../gcstar/style/GCstar/icons/media-next/32x32.png | Bin 0 -> 1751 bytes .../gcstar/style/GCstar/icons/media-play/16x16.png | Bin 0 -> 704 bytes .../gcstar/style/GCstar/icons/media-play/24x24.png | Bin 0 -> 1050 bytes .../gcstar/style/GCstar/icons/media-play/32x32.png | Bin 0 -> 1401 bytes share/gcstar/style/GCstar/icons/network/32x32.png | Bin 0 -> 2631 bytes share/gcstar/style/GCstar/icons/new/16x16.png | Bin 0 -> 475 bytes share/gcstar/style/GCstar/icons/new/24x24.png | Bin 0 -> 775 bytes share/gcstar/style/GCstar/icons/new/32x32.png | Bin 0 -> 999 bytes share/gcstar/style/GCstar/icons/ok/24x24.png | Bin 0 -> 1589 bytes share/gcstar/style/GCstar/icons/ok/32x32.png | Bin 0 -> 2365 bytes share/gcstar/style/GCstar/icons/open/16x16.png | Bin 0 -> 562 bytes share/gcstar/style/GCstar/icons/open/24x24.png | Bin 0 -> 1546 bytes share/gcstar/style/GCstar/icons/open/32x32.png | Bin 0 -> 2311 bytes .../style/GCstar/icons/preferences/16x16.png | Bin 0 -> 3535 bytes .../style/GCstar/icons/preferences/24x24.png | Bin 0 -> 1553 bytes .../style/GCstar/icons/preferences/32x32.png | Bin 0 -> 2764 bytes .../gcstar/style/GCstar/icons/properties/24x24.png | Bin 0 -> 1893 bytes .../gcstar/style/GCstar/icons/properties/32x32.png | Bin 0 -> 3048 bytes share/gcstar/style/GCstar/icons/quit/16x16.png | Bin 0 -> 1039 bytes share/gcstar/style/GCstar/icons/quit/32x32.png | Bin 0 -> 3118 bytes share/gcstar/style/GCstar/icons/refresh/16x16.png | Bin 0 -> 983 bytes share/gcstar/style/GCstar/icons/refresh/24x24.png | Bin 0 -> 1760 bytes share/gcstar/style/GCstar/icons/refresh/32x32.png | Bin 0 -> 2846 bytes share/gcstar/style/GCstar/icons/remove/24x24.png | Bin 0 -> 480 bytes share/gcstar/style/GCstar/icons/remove/32x32.png | Bin 0 -> 625 bytes .../style/GCstar/icons/revert-to-saved/16x16.png | Bin 0 -> 736 bytes .../style/GCstar/icons/revert-to-saved/24x24.png | Bin 0 -> 1473 bytes .../style/GCstar/icons/revert-to-saved/32x32.png | Bin 0 -> 2247 bytes share/gcstar/style/GCstar/icons/save-as/16x16.png | Bin 0 -> 674 bytes share/gcstar/style/GCstar/icons/save-as/24x24.png | Bin 0 -> 1410 bytes share/gcstar/style/GCstar/icons/save-as/32x32.png | Bin 0 -> 1957 bytes share/gcstar/style/GCstar/icons/save/16x16.png | Bin 0 -> 515 bytes share/gcstar/style/GCstar/icons/save/24x24.png | Bin 0 -> 1166 bytes share/gcstar/style/GCstar/icons/save/32x32.png | Bin 0 -> 1512 bytes .../style/GCstar/icons/select-color/32x32.png | Bin 0 -> 1904 bytes share/gcstar/style/GCstar/lend.png | Bin 0 -> 2910 bytes share/gcstar/style/Gtk/gtkrc | 1 + share/gcstar/style/Gtk/lend.png | Bin 0 -> 2910 bytes share/gcstar/style/kde/active.png | Bin 0 -> 676 bytes share/gcstar/style/kde/active2.png | Bin 0 -> 176 bytes share/gcstar/style/kde/add.png | Bin 0 -> 1165 bytes share/gcstar/style/kde/arrowdown.png | Bin 0 -> 234 bytes share/gcstar/style/kde/arrowleft.png | Bin 0 -> 203 bytes share/gcstar/style/kde/arrowright.png | Bin 0 -> 204 bytes share/gcstar/style/kde/arrowup.png | Bin 0 -> 235 bytes share/gcstar/style/kde/bghonrizontalscroll.png | Bin 0 -> 212 bytes share/gcstar/style/kde/bgverticalscroll.png | Bin 0 -> 209 bytes share/gcstar/style/kde/box.png | Bin 0 -> 4412 bytes share/gcstar/style/kde/box2.png | Bin 0 -> 935 bytes share/gcstar/style/kde/box3.png | Bin 0 -> 893 bytes share/gcstar/style/kde/cancel.png | Bin 0 -> 1468 bytes share/gcstar/style/kde/cdrom.png | Bin 0 -> 5400 bytes share/gcstar/style/kde/checked.png | Bin 0 -> 638 bytes share/gcstar/style/kde/clear.png | Bin 0 -> 929 bytes share/gcstar/style/kde/delete.png | Bin 0 -> 1591 bytes share/gcstar/style/kde/display.png | Bin 0 -> 4134 bytes share/gcstar/style/kde/exec.png | Bin 0 -> 6440 bytes share/gcstar/style/kde/export.png | Bin 0 -> 2683 bytes share/gcstar/style/kde/find.png | Bin 0 -> 7340 bytes share/gcstar/style/kde/gtkrc | 746 ++++ share/gcstar/style/kde/gtkrcold | 253 ++ share/gcstar/style/kde/harddisk.png | Bin 0 -> 1902 bytes share/gcstar/style/kde/help.png | Bin 0 -> 1587 bytes share/gcstar/style/kde/home.png | Bin 0 -> 5039 bytes share/gcstar/style/kde/horizontal.png | Bin 0 -> 388 bytes share/gcstar/style/kde/horizontal_hover.png | Bin 0 -> 420 bytes share/gcstar/style/kde/import.png | Bin 0 -> 5185 bytes share/gcstar/style/kde/internet.png | Bin 0 -> 7129 bytes share/gcstar/style/kde/khelpcenter.png | Bin 0 -> 4107 bytes share/gcstar/style/kde/lend.png | Bin 0 -> 1841 bytes share/gcstar/style/kde/new.png | Bin 0 -> 902 bytes share/gcstar/style/kde/ok.png | Bin 0 -> 1393 bytes share/gcstar/style/kde/open.png | Bin 0 -> 2232 bytes share/gcstar/style/kde/paths.png | Bin 0 -> 1944 bytes share/gcstar/style/kde/preferences.png | Bin 0 -> 5104 bytes share/gcstar/style/kde/properties.png | Bin 0 -> 4711 bytes share/gcstar/style/kde/quit.png | Bin 0 -> 2238 bytes share/gcstar/style/kde/radiochecked.png | Bin 0 -> 512 bytes share/gcstar/style/kde/radiounchecked.png | Bin 0 -> 476 bytes share/gcstar/style/kde/refresh.png | Bin 0 -> 1545 bytes share/gcstar/style/kde/remove.png | Bin 0 -> 267 bytes share/gcstar/style/kde/save.png | Bin 0 -> 1348 bytes share/gcstar/style/kde/saveas.png | Bin 0 -> 2069 bytes share/gcstar/style/kde/sortdown.png | Bin 0 -> 1604 bytes share/gcstar/style/kde/sortup.png | Bin 0 -> 1595 bytes share/gcstar/style/kde/spindown.png | Bin 0 -> 173 bytes share/gcstar/style/kde/spinup.png | Bin 0 -> 173 bytes share/gcstar/style/kde/tab_corner.png | Bin 0 -> 177 bytes share/gcstar/style/kde/tonight.png | Bin 0 -> 1509 bytes share/gcstar/style/kde/unchecked.png | Bin 0 -> 639 bytes share/gcstar/style/kde/vertical.png | Bin 0 -> 414 bytes share/gcstar/style/kde/vertical_hover.png | Bin 0 -> 376 bytes share/gcstar/xml_models/GCfilms/Ant_Movie_Catalog | 31 + share/gcstar/xml_models/GCfilms/DVDProfiler | 151 + share/gcstar/xslt/applyXSLT.pl | 13 + share/gcstar/xslt/createGCSValidator.xsl | 165 + templates/GCExportTemplate.pm | 264 ++ templates/GCImportTemplate.pm | 212 + templates/GCSiteTemplate.pm | 271 ++ 1319 files changed, 166023 insertions(+) create mode 100644 CHANGELOG create mode 100644 LICENSE create mode 100644 README create mode 100644 README.fr create mode 100644 bin/gcstar create mode 100755 install create mode 100644 lib/gcstar/GCBackend/GCBackendXmlCommon.pm create mode 100644 lib/gcstar/GCBackend/GCBackendXmlParser.pm create mode 100644 lib/gcstar/GCBookmarks.pm create mode 100644 lib/gcstar/GCBorrowings.pm create mode 100644 lib/gcstar/GCCommandLine.pm create mode 100644 lib/gcstar/GCData.pm create mode 100644 lib/gcstar/GCDialogs.pm create mode 100644 lib/gcstar/GCDisplay.pm create mode 100644 lib/gcstar/GCExport.pm create mode 100644 lib/gcstar/GCExport/GCExportBase.pm create mode 100644 lib/gcstar/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCExport/GCExportLatex.pm create mode 100644 lib/gcstar/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCExport/GCExportTellico.pm create mode 100644 lib/gcstar/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCExportImport.pm create mode 100644 lib/gcstar/GCExtract.pm create mode 100644 lib/gcstar/GCExtract/GCExtractFilms.pm create mode 100644 lib/gcstar/GCExtract/GCExtractMusics.pm create mode 100644 lib/gcstar/GCGenres.pm create mode 100644 lib/gcstar/GCGraphicComponents/GCBaseWidgets.pm create mode 100644 lib/gcstar/GCGraphicComponents/GCDoubleLists.pm create mode 100644 lib/gcstar/GCImport.pm create mode 100644 lib/gcstar/GCImport/GCImportAMC.pm create mode 100644 lib/gcstar/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCImport/GCImportBase.pm create mode 100644 lib/gcstar/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCImport/GCImportDVDProfiler.pm create mode 100644 lib/gcstar/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCImport/GCImportGCfilms.pm create mode 100644 lib/gcstar/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCImport/GCImportMyMovies.pm create mode 100644 lib/gcstar/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCImport/GCImportTarGz.pm create mode 100644 lib/gcstar/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCItemsLists/GCImageListComponents.pm create mode 100644 lib/gcstar/GCItemsLists/GCImageLists.pm create mode 100644 lib/gcstar/GCItemsLists/GCListOptions.pm create mode 100644 lib/gcstar/GCItemsLists/GCTextLists.pm create mode 100644 lib/gcstar/GCLang.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/AR/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/AR/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/AR/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/AR/GCstar.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/BG/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/BG/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/BG/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/BG/GCstar.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/CA/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/CA/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/CA/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/CA/GCstar.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/CS/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/CS/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/CS/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/CS/GCstar.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/DE/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/DE/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/DE/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/DE/GCstar.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/EL/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/EL/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/EL/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/EL/GCstar.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/EN/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/EN/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/EN/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/EN/GCstar.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/ES/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/ES/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/ES/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/ES/GCstar.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/FR/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/FR/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/FR/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/FR/GCstar.pm create mode 100644 lib/gcstar/GCLang/GCLangUtils.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/GL/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/GL/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/GL/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/GL/GCstar.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/HU/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/HU/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/HU/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/HU/GCstar.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/ID/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/ID/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/ID/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/ID/GCstar.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/IT/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/IT/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/IT/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/IT/GCstar.pm create mode 100644 lib/gcstar/GCLang/IT/README.txt create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/NL/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/NL/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/NL/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/NL/GCstar.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/PL/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/PL/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/PL/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/PL/GCstar.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/PT/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/PT/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/PT/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/PT/GCstar.pm create mode 100644 lib/gcstar/GCLang/README create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/RO/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/RO/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/RO/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/RO/GCstar.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/RU/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/RU/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/RU/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/RU/GCstar.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/SR/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/SR/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/SR/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/SR/GCstar.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/SV/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/SV/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/SV/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/SV/GCstar.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/TR/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/TR/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/TR/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/TR/GCstar.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/UK/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/UK/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/UK/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/UK/GCstar.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/ZH/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/ZH/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/ZH/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/ZH/GCstar.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportCSV.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportExternal.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportHTML.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportPDB.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportSQL.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportTarGz.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCExport/GCExportXML.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportAlexandria.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportCSV.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportFolder.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportGCstar.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportList.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportScanner.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCImport/GCImportTellico.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCTVepisodes.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCTVseries.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCboardgames.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCbooks.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCcoins.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCcomics.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCfilms.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCgames.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCgeneric.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCminicars.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCmusics.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCperiodicals.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCsmartcards.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCsoftware.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCstamps.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCModels/GCwines.pm create mode 100644 lib/gcstar/GCLang/ZH_CN/GCstar.pm create mode 100644 lib/gcstar/GCMail.pm create mode 100644 lib/gcstar/GCMainWindow.pm create mode 100644 lib/gcstar/GCMenu.pm create mode 100644 lib/gcstar/GCModel.pm create mode 100644 lib/gcstar/GCModels/GCTVepisodes.gcm create mode 100644 lib/gcstar/GCModels/GCTVseries.gcm create mode 100644 lib/gcstar/GCModels/GCboardgames.gcm create mode 100644 lib/gcstar/GCModels/GCbooks.gcm create mode 100644 lib/gcstar/GCModels/GCcoins.gcm create mode 100644 lib/gcstar/GCModels/GCcomics.gcm create mode 100644 lib/gcstar/GCModels/GCfilms.gcm create mode 100644 lib/gcstar/GCModels/GCgames.gcm create mode 100644 lib/gcstar/GCModels/GCminicars.gcm create mode 100644 lib/gcstar/GCModels/GCmusics.gcm create mode 100644 lib/gcstar/GCModels/GCperiodicals.gcm create mode 100644 lib/gcstar/GCModels/GCsmartcards.gcm create mode 100644 lib/gcstar/GCModels/GCsoftware.gcm create mode 100644 lib/gcstar/GCModels/GCstamps.gcm create mode 100644 lib/gcstar/GCModels/GCwines.gcm create mode 100644 lib/gcstar/GCOptions.pm create mode 100644 lib/gcstar/GCPanel.pm create mode 100644 lib/gcstar/GCPlugins.pm create mode 100644 lib/gcstar/GCPlugins/GCPluginsBase.pm create mode 100644 lib/gcstar/GCPlugins/GCTVepisodes/GCTVepisodesCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCTVepisodes/GCTvdb.pm create mode 100644 lib/gcstar/GCPlugins/GCTVepisodes/GCTvdbES.pm create mode 100644 lib/gcstar/GCPlugins/GCTVepisodes/GCTvdbFR.pm create mode 100644 lib/gcstar/GCPlugins/GCTVepisodes/GCTvdbIT.pm create mode 100644 lib/gcstar/GCPlugins/GCTVseries/GCTVseriesCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCTVseries/GCThemoviedb.pm create mode 100644 lib/gcstar/GCPlugins/GCTVseries/GCTvdb.pm create mode 100644 lib/gcstar/GCPlugins/GCTVseries/GCTvdbES.pm create mode 100644 lib/gcstar/GCPlugins/GCTVseries/GCTvdbFR.pm create mode 100644 lib/gcstar/GCPlugins/GCTVseries/GCTvdbIT.pm create mode 100644 lib/gcstar/GCPlugins/GCboardgames/GCReservoirJeux.pm create mode 100644 lib/gcstar/GCPlugins/GCboardgames/GCboardgamegeek.pm create mode 100644 lib/gcstar/GCPlugins/GCboardgames/GCboardgamesCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCboardgames/GCtrictrac.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAdlibrisFI.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAdlibrisSV.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAlapage.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAmazon.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAmazonCA.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAmazonDE.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAmazonFR.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCAmazonUK.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCBDGest.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCBibliotekaNarodowa.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCBokkilden.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCBol.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCBuscape.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCCasadelibro.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCChapitre.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCDoubanbook.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCFnac.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCFnacPT.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCISBNdb.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCInternetBokHandeln.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCInternetBookShop.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCLeLivre.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCLiberOnWeb.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCMareno.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCMediabooks.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCMerlin.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCNUKat.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCNooSFere.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCSaraiva.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCbooksAdlibrisCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCbooksAmazonCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCbooks/GCbooksCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCcomics/GCbedetheque.pm create mode 100644 lib/gcstar/GCPlugins/GCcomics/GCcomicbookdb.pm create mode 100644 lib/gcstar/GCPlugins/GCcomics/GCcomicsCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCcomics/GCmangasanctuary.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAlapage.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAllmovie.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAllocine.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAlpacineES.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAmazon.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAmazonDE.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAmazonFR.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAmazonUK.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAniDB.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAnimator.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAnimeNfoA.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCAnimeka.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCBeyazPerde.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCCartelesPeliculasES.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCCinemaClock.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCCinemotions.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCCsfd.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCCulturalia.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCDVDEmpire.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCDVDFr.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCDVDPost.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCDicshop.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCDoubanfilm.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCFilmAffinityEN.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCFilmAffinityES.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCFilmUP.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCFilmWeb.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCIbs.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCImdb.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCKinopoisk.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCMediadis.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCMetropoliES.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCMonsieurCinema.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCMovieMeter.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCMoviecovers.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCNasheKino.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCOFDb.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCOdeonHU.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCOnet.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCPortHU.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCStopklatka.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCThemoviedb.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCThemoviedbDE.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCThemoviedbES.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCThemoviedbFR.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCfilmsAmazonCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCfilms/GCfilmsCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAlapage.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAmazon.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAmazonCA.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAmazonDE.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAmazonFR.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAmazonJP.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCAmazonUK.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCDicoDuNet.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCGameSpot.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCJeuxVideoCom.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCJeuxVideoFr.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCLudus.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCMobyGames.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCNextGame.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCTheLegacy.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCgamesAmazonCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCgames/GCgamesCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCmusics/GCDiscogs.pm create mode 100644 lib/gcstar/GCPlugins/GCmusics/GCDoubanmusic.pm create mode 100644 lib/gcstar/GCPlugins/GCmusics/GCMusicBrainz.pm create mode 100644 lib/gcstar/GCPlugins/GCmusics/GCmusicsCommon.pm create mode 100644 lib/gcstar/GCPlugins/GCstar/GCAmazonCommon.pm create mode 100644 lib/gcstar/GCSplash.pm create mode 100644 lib/gcstar/GCStats.pm create mode 100644 lib/gcstar/GCStyle.pm create mode 100644 lib/gcstar/GCUpdater.pm create mode 100644 lib/gcstar/GCUtils.pm create mode 100644 lib/gcstar/GCWidgets.pm create mode 100644 man/gcstar.1 create mode 100644 packages/GCstar_Packaging_Policy.txt create mode 100644 packages/debian/changelog create mode 100644 packages/debian/compat create mode 100644 packages/debian/control create mode 100644 packages/debian/copyright create mode 100644 packages/debian/dirs create mode 100644 packages/debian/docs create mode 100644 packages/debian/gcstar.install create mode 100644 packages/debian/gcstar_logo.xpm create mode 100644 packages/debian/lintian/gcstar create mode 100644 packages/debian/menu create mode 100644 packages/debian/patches/00list create mode 100644 packages/debian/patches/01-set_usr_lib.dpatch create mode 100644 packages/debian/patches/02gzip-manpage.dpatch create mode 100644 packages/debian/patches/03_change_default_browser.dpatch create mode 100644 packages/debian/patches/04-install-set_usr_lib.dpatch create mode 100644 packages/debian/patches/07_fix_manpath.dpatch create mode 100644 packages/debian/postinst create mode 100644 packages/debian/postrm create mode 100644 packages/debian/rules create mode 100644 packages/fedora/gcstar.spec create mode 100644 packages/rpm/gcstar.spec create mode 100644 packages/win32/createExe.bat create mode 100644 packages/win32/gcs_lang.nsh create mode 100644 packages/win32/gcstar.bat create mode 100644 packages/win32/gcstar.nsi create mode 100644 packages/win32/img/banner_left.bmp create mode 100644 packages/win32/img/banner_top.bmp create mode 100644 packages/win32/img/checks.bmp create mode 100644 packages/win32/img/icon_install.ico create mode 100644 packages/win32/img/icon_uninstall.ico create mode 100644 packages/win32/img/uninstall_top.bmp create mode 100644 packages/win32/img/website.ico create mode 100644 packages/win32/langs/gcs_Bulgarian.nsh create mode 100644 packages/win32/langs/gcs_Czech.nsh create mode 100644 packages/win32/langs/gcs_English.nsh create mode 100644 packages/win32/langs/gcs_French.nsh create mode 100644 packages/win32/langs/gcs_German.nsh create mode 100644 packages/win32/langs/gcs_Italian.nsh create mode 100644 packages/win32/langs/gcs_Polish.nsh create mode 100644 packages/win32/langs/gcs_Romanian.nsh create mode 100644 packages/win32/langs/gcs_Russian.nsh create mode 100644 packages/win32/langs/gcs_SerbianLatin.nsh create mode 100644 packages/win32/langs/gcs_Spanish.nsh create mode 100644 packages/win32/langs/gcs_Turkish.nsh create mode 100644 packages/win32/update.bat create mode 100644 share/applications/gcstar-thumbnailer create mode 100644 share/applications/gcstar.desktop create mode 100644 share/applications/gcstar.xml create mode 100644 share/gcstar/LICENSE create mode 100644 share/gcstar/fonts/AUTHORS create mode 100644 share/gcstar/fonts/COPYING create mode 100644 share/gcstar/fonts/ChangeLog create mode 100644 share/gcstar/fonts/LiberationSans-Regular.ttf create mode 100644 share/gcstar/fonts/License.txt create mode 100644 share/gcstar/fonts/README create mode 100644 share/gcstar/genres/EN.genres create mode 100644 share/gcstar/genres/ES.genres create mode 100644 share/gcstar/genres/FR.genres create mode 100644 share/gcstar/helpers/xdg-open create mode 100644 share/gcstar/html_models/GCboardgames/piwi create mode 100644 share/gcstar/html_models/GCboardgames/piwi.png create mode 100644 share/gcstar/html_models/GCbooks/FloFred create mode 100644 share/gcstar/html_models/GCbooks/FloFred.png create mode 100644 share/gcstar/html_models/GCbooks/NellistosDark create mode 100644 share/gcstar/html_models/GCbooks/NellistosDark.png create mode 100644 share/gcstar/html_models/GCbooks/NellistosLight create mode 100644 share/gcstar/html_models/GCbooks/NellistosLight.png create mode 100644 share/gcstar/html_models/GCbooks/Shelf create mode 100644 share/gcstar/html_models/GCbooks/Shelf.png create mode 100644 share/gcstar/html_models/GCbooks/Simple create mode 100644 share/gcstar/html_models/GCbooks/Simple.png create mode 100644 share/gcstar/html_models/GCcoins/Simple create mode 100644 share/gcstar/html_models/GCcoins/Simple.png create mode 100644 share/gcstar/html_models/GCfilms/Flat create mode 100644 share/gcstar/html_models/GCfilms/Flat.png create mode 100644 share/gcstar/html_models/GCfilms/Shelf create mode 100644 share/gcstar/html_models/GCfilms/Shelf.png create mode 100644 share/gcstar/html_models/GCfilms/Simple create mode 100644 share/gcstar/html_models/GCfilms/Simple.png create mode 100644 share/gcstar/html_models/GCfilms/Tabs create mode 100644 share/gcstar/html_models/GCfilms/Tabs.png create mode 100644 share/gcstar/html_models/GCfilms/Tian create mode 100644 share/gcstar/html_models/GCfilms/Tian-Mario create mode 100644 share/gcstar/html_models/GCfilms/Tian-Mario-Kim create mode 100644 share/gcstar/html_models/GCfilms/Tian-Mario.png create mode 100644 share/gcstar/html_models/GCfilms/Tian.png create mode 100644 share/gcstar/html_models/GCfilms/float create mode 100644 share/gcstar/html_models/GCfilms/float.png create mode 100644 share/gcstar/html_models/GCfilms/rootII_design create mode 100644 share/gcstar/html_models/GCfilms/rootII_design.png create mode 100644 share/gcstar/html_models/GCgames/Flat create mode 100644 share/gcstar/html_models/GCgames/Flat.png create mode 100644 share/gcstar/html_models/GCgames/Simple create mode 100644 share/gcstar/html_models/GCgames/Simple.png create mode 100644 share/gcstar/html_models/GCgames/Tabs create mode 100644 share/gcstar/html_models/GCgames/Tabs.png create mode 100644 share/gcstar/html_models/GCminicars/Tian-Jim create mode 100644 share/gcstar/html_models/GCminicars/Tian-Jim.png create mode 100644 share/gcstar/html_models/GCmusics/Shelf create mode 100644 share/gcstar/html_models/GCmusics/Shelf.png create mode 100644 share/gcstar/html_models/GCmusics/Simple create mode 100644 share/gcstar/html_models/GCmusics/Simple.png create mode 100644 share/gcstar/html_models/GCstar/Shelf create mode 100644 share/gcstar/html_models/GCstar/Shelf.png create mode 100644 share/gcstar/html_models/GCstar/Simple create mode 100644 share/gcstar/html_models/GCstar/Simple.png create mode 100644 share/gcstar/icons/GCstar.ico create mode 100644 share/gcstar/icons/gcstar_128x128.png create mode 100644 share/gcstar/icons/gcstar_16x16.png create mode 100644 share/gcstar/icons/gcstar_192x192.png create mode 100644 share/gcstar/icons/gcstar_22x22.png create mode 100644 share/gcstar/icons/gcstar_24x24.png create mode 100644 share/gcstar/icons/gcstar_256x256.png create mode 100644 share/gcstar/icons/gcstar_32x32.png create mode 100644 share/gcstar/icons/gcstar_36x36.png create mode 100644 share/gcstar/icons/gcstar_48x48.png create mode 100644 share/gcstar/icons/gcstar_64x64.png create mode 100644 share/gcstar/icons/gcstar_72x72.png create mode 100644 share/gcstar/icons/gcstar_96x96.png create mode 100644 share/gcstar/icons/gcstar_scalable.svg create mode 100644 share/gcstar/icons/icon_install.ico create mode 100644 share/gcstar/icons/star.png create mode 100644 share/gcstar/icons/star_hover.png create mode 100644 share/gcstar/icons/stardark.png create mode 100644 share/gcstar/icons/stardark_hover.png create mode 100644 share/gcstar/icons/web.ico create mode 100644 share/gcstar/list_bg/Box/group.png create mode 100644 share/gcstar/list_bg/Box/list_bg.png create mode 100644 share/gcstar/list_bg/Box/style create mode 100644 share/gcstar/list_bg/Brick_and_Glass/group.png create mode 100644 share/gcstar/list_bg/Brick_and_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Brick_and_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Brick_and_Glass/style create mode 100644 share/gcstar/list_bg/Dark_Glass/group.png create mode 100644 share/gcstar/list_bg/Dark_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Dark_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Dark_Glass/style create mode 100644 share/gcstar/list_bg/Glass/group.png create mode 100644 share/gcstar/list_bg/Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Glass/style create mode 100644 share/gcstar/list_bg/Green_Glass/group.png create mode 100644 share/gcstar/list_bg/Green_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Green_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Green_Glass/style create mode 100644 share/gcstar/list_bg/Luxury_Green_Glass/group.png create mode 100644 share/gcstar/list_bg/Luxury_Green_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Green_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Green_Glass/style create mode 100644 share/gcstar/list_bg/Luxury_Green_Wood/group.png create mode 100644 share/gcstar/list_bg/Luxury_Green_Wood/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Green_Wood/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Green_Wood/style create mode 100644 share/gcstar/list_bg/Luxury_Grey_Glass/group.png create mode 100644 share/gcstar/list_bg/Luxury_Grey_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Grey_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Grey_Glass/style create mode 100644 share/gcstar/list_bg/Luxury_Grey_Wood/group.png create mode 100644 share/gcstar/list_bg/Luxury_Grey_Wood/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Grey_Wood/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Grey_Wood/style create mode 100644 share/gcstar/list_bg/Luxury_Purple_Glass/group.png create mode 100644 share/gcstar/list_bg/Luxury_Purple_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Purple_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Purple_Glass/style create mode 100644 share/gcstar/list_bg/Luxury_Purple_Wood/group.png create mode 100644 share/gcstar/list_bg/Luxury_Purple_Wood/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Purple_Wood/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Purple_Wood/style create mode 100644 share/gcstar/list_bg/Luxury_Red_Glass/group.png create mode 100644 share/gcstar/list_bg/Luxury_Red_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Red_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Red_Glass/style create mode 100644 share/gcstar/list_bg/Luxury_Red_Wood/group.png create mode 100644 share/gcstar/list_bg/Luxury_Red_Wood/list_bg.png create mode 100644 share/gcstar/list_bg/Luxury_Red_Wood/list_fg.png create mode 100644 share/gcstar/list_bg/Luxury_Red_Wood/style create mode 100644 share/gcstar/list_bg/Marble/group.png create mode 100644 share/gcstar/list_bg/Marble/list_bg.png create mode 100644 share/gcstar/list_bg/Marble/style create mode 100644 share/gcstar/list_bg/Wood/group.png create mode 100644 share/gcstar/list_bg/Wood/list_bg.png create mode 100644 share/gcstar/list_bg/Wood/list_fg.png create mode 100644 share/gcstar/list_bg/Wood/style create mode 100644 share/gcstar/list_bg/Wood2/group.png create mode 100644 share/gcstar/list_bg/Wood2/list_bg.png create mode 100644 share/gcstar/list_bg/Wood2/list_fg.png create mode 100644 share/gcstar/list_bg/Wood2/style create mode 100644 share/gcstar/list_bg/Wood_and_Glass/group.png create mode 100644 share/gcstar/list_bg/Wood_and_Glass/list_bg.png create mode 100644 share/gcstar/list_bg/Wood_and_Glass/list_fg.png create mode 100644 share/gcstar/list_bg/Wood_and_Glass/style create mode 100644 share/gcstar/logos/Peri.png create mode 100644 share/gcstar/logos/Peri_main_logo.png create mode 100644 share/gcstar/logos/Peri_main_logo.svg create mode 100644 share/gcstar/logos/about.png create mode 100644 share/gcstar/logos/bg_no.png create mode 100644 share/gcstar/logos/book_no.png create mode 100644 share/gcstar/logos/button.png create mode 100644 share/gcstar/logos/cd_no.png create mode 100644 share/gcstar/logos/film_no.png create mode 100644 share/gcstar/logos/find.png create mode 100644 share/gcstar/logos/install.png create mode 100644 share/gcstar/logos/no.png create mode 100644 share/gcstar/logos/no_minicars.png create mode 100644 share/gcstar/logos/no_smartcards.png create mode 100644 share/gcstar/logos/no_stamp.png create mode 100644 share/gcstar/logos/periscope_main_logo.svg create mode 100644 share/gcstar/logos/splash.png create mode 100644 share/gcstar/overlays/canevas-timbre.png create mode 100644 share/gcstar/overlays/cd.png create mode 100644 share/gcstar/overlays/dvd.png create mode 100644 share/gcstar/overlays/favourite_large.png create mode 100644 share/gcstar/overlays/favourite_med.png create mode 100644 share/gcstar/overlays/favourite_small.png create mode 100644 share/gcstar/overlays/favourite_verysmall.png create mode 100644 share/gcstar/overlays/favourite_xlarge.png create mode 100644 share/gcstar/overlays/film.png create mode 100644 share/gcstar/overlays/flip.png create mode 100644 share/gcstar/overlays/flip2.png create mode 100644 share/gcstar/overlays/lend_large.png create mode 100644 share/gcstar/overlays/lend_med.png create mode 100644 share/gcstar/overlays/lend_small.png create mode 100644 share/gcstar/overlays/lend_verysmall.png create mode 100644 share/gcstar/overlays/lend_xlarge.png create mode 100644 share/gcstar/overlays/minicars.png create mode 100644 share/gcstar/overlays/subtle.png create mode 100644 share/gcstar/panels/Classic create mode 100644 share/gcstar/panels/Dark create mode 100644 share/gcstar/panels/WebSite create mode 100644 share/gcstar/schemas/gcm.xsd create mode 100644 share/gcstar/style/GCstar/gtkrc create mode 100644 share/gcstar/style/GCstar/icons/about/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/about/64x64.png create mode 100644 share/gcstar/style/GCstar/icons/add/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/add/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/add/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/cancel/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/cancel/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/clear/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/clear/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/convert/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/convert/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/convert/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/delete/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/delete/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/delete/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/directory/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/dnd/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/error/64x64.png create mode 100644 share/gcstar/style/GCstar/icons/execute/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/execute/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/execute/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/find/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/find/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/find/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/go-back/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/go-back/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/go-down/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/go-down/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/go-forward/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/go-forward/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/go-up/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/go-up/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/help/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/help/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/help/64x64.png create mode 100644 share/gcstar/style/GCstar/icons/home/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/jump-to/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/jump-to/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/media-next/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/media-next/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/media-play/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/media-play/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/media-play/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/network/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/new/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/new/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/new/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/ok/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/ok/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/open/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/open/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/open/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/preferences/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/preferences/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/preferences/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/properties/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/properties/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/quit/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/quit/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/refresh/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/refresh/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/refresh/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/remove/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/remove/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/revert-to-saved/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/revert-to-saved/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/revert-to-saved/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/save-as/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/save-as/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/save-as/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/save/16x16.png create mode 100644 share/gcstar/style/GCstar/icons/save/24x24.png create mode 100644 share/gcstar/style/GCstar/icons/save/32x32.png create mode 100644 share/gcstar/style/GCstar/icons/select-color/32x32.png create mode 100644 share/gcstar/style/GCstar/lend.png create mode 100644 share/gcstar/style/Gtk/gtkrc create mode 100644 share/gcstar/style/Gtk/lend.png create mode 100644 share/gcstar/style/kde/active.png create mode 100644 share/gcstar/style/kde/active2.png create mode 100644 share/gcstar/style/kde/add.png create mode 100644 share/gcstar/style/kde/arrowdown.png create mode 100644 share/gcstar/style/kde/arrowleft.png create mode 100644 share/gcstar/style/kde/arrowright.png create mode 100644 share/gcstar/style/kde/arrowup.png create mode 100644 share/gcstar/style/kde/bghonrizontalscroll.png create mode 100644 share/gcstar/style/kde/bgverticalscroll.png create mode 100644 share/gcstar/style/kde/box.png create mode 100644 share/gcstar/style/kde/box2.png create mode 100644 share/gcstar/style/kde/box3.png create mode 100644 share/gcstar/style/kde/cancel.png create mode 100644 share/gcstar/style/kde/cdrom.png create mode 100644 share/gcstar/style/kde/checked.png create mode 100644 share/gcstar/style/kde/clear.png create mode 100644 share/gcstar/style/kde/delete.png create mode 100644 share/gcstar/style/kde/display.png create mode 100644 share/gcstar/style/kde/exec.png create mode 100644 share/gcstar/style/kde/export.png create mode 100644 share/gcstar/style/kde/find.png create mode 100644 share/gcstar/style/kde/gtkrc create mode 100644 share/gcstar/style/kde/gtkrcold create mode 100644 share/gcstar/style/kde/harddisk.png create mode 100644 share/gcstar/style/kde/help.png create mode 100644 share/gcstar/style/kde/home.png create mode 100644 share/gcstar/style/kde/horizontal.png create mode 100644 share/gcstar/style/kde/horizontal_hover.png create mode 100644 share/gcstar/style/kde/import.png create mode 100644 share/gcstar/style/kde/internet.png create mode 100644 share/gcstar/style/kde/khelpcenter.png create mode 100644 share/gcstar/style/kde/lend.png create mode 100644 share/gcstar/style/kde/new.png create mode 100644 share/gcstar/style/kde/ok.png create mode 100644 share/gcstar/style/kde/open.png create mode 100644 share/gcstar/style/kde/paths.png create mode 100644 share/gcstar/style/kde/preferences.png create mode 100644 share/gcstar/style/kde/properties.png create mode 100644 share/gcstar/style/kde/quit.png create mode 100644 share/gcstar/style/kde/radiochecked.png create mode 100644 share/gcstar/style/kde/radiounchecked.png create mode 100644 share/gcstar/style/kde/refresh.png create mode 100644 share/gcstar/style/kde/remove.png create mode 100644 share/gcstar/style/kde/save.png create mode 100644 share/gcstar/style/kde/saveas.png create mode 100644 share/gcstar/style/kde/sortdown.png create mode 100644 share/gcstar/style/kde/sortup.png create mode 100644 share/gcstar/style/kde/spindown.png create mode 100644 share/gcstar/style/kde/spinup.png create mode 100644 share/gcstar/style/kde/tab_corner.png create mode 100644 share/gcstar/style/kde/tonight.png create mode 100644 share/gcstar/style/kde/unchecked.png create mode 100644 share/gcstar/style/kde/vertical.png create mode 100644 share/gcstar/style/kde/vertical_hover.png create mode 100644 share/gcstar/xml_models/GCfilms/Ant_Movie_Catalog create mode 100644 share/gcstar/xml_models/GCfilms/DVDProfiler create mode 100644 share/gcstar/xslt/applyXSLT.pl create mode 100644 share/gcstar/xslt/createGCSValidator.xsl create mode 100644 templates/GCExportTemplate.pm create mode 100644 templates/GCImportTemplate.pm create mode 100644 templates/GCSiteTemplate.pm diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..761147d --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,1306 @@ +1.7.0 + + * New translations + - Chinese (Simplified) translation added thanks to 林雪凡. + + * User interface changes + - All the settings for the current type of list can be changed directly + below the list, on the bottom left of the application. By default, + the settings panel is hidden, but it can be shown by dragging the + separator. + - It's now possible to define default values for the fields when adding + a new item to a collection. + - New menu to show/hide menu, tool and status bars. This also appears + in the context menu shown when right-clicking on an element in the + list. It contains an option to use fullscreen mode. + - Slight animations have been added in image mode. + - A popup is displayed in image mode to show a summary of the item. + + * Bugs fixed + - There was a bug when saving an item which date is the current one. + - The list of saved searches in toolbar was not reset when displaying + all collection items. + - Ranges were not handled correctly when searching. + - With collections of video games, the units field was not hidden if + user chose to hide file size. + - In image mode, some items were not displayed correctly when grouping. + - Bug fixed in some website plugins: + All Amazon plugins for books + Allocine + Bedetheque + CSFD + Discogs + GameSpot + IMDb + jeuxvideo.com + MobyGames + Saraiva + +------------------------------------------------------------------- + +1.6.2 + + * New features + - Option added for sorting items while in image views + * Export/Import/Fetch/Extract changes + - Amazon book plugins now use the Amazon Web Service, for more reliable + plugin operation + - New plugin for batch importing with a barcode scanner. It can be used + with GCstar Scanner for Android or any barcode scanner acting like a + keyboard. + * Default collections changes + - Film collection: Country field is now a list box, allowing multiple countries + * New plugins to fetch information from websites + - Books: + Douban (ZH) provided by BW. + - Movies: + Douban (ZH) provided by BW. + - Music: + Douban (ZH) provided by BW. + * Bugs fixed + - Set focus to the title field automatically after adding an item + - Correctly handle foreign characters in external filenames under Windows + - Correctly sort numeric fields used for grouping in image lists + - It was not possible to delete a unique item in image mode + - Fixed bug with image caching + - HTTP return code 302 managed correctly + - Removed use of Switch.pm as it is deprecated + - Bug fixed in some website plugins: + Alapage + Allocine + Bedetheque + Bol + Comic Book DB + CSFD.cz + IMDb + jeuxvideo.com + jeuxvideo.fr + Moby Games + The Movie DB + +------------------------------------------------------------------- + +1.6.1 + + * New features + - CSV plugin now allows you to select which field to use to search for + internet results + - Graphs generated from statistics are smoothed, to eliminate sharp lines + * Default collections changes + - New default collections: + TV shows (series) collection model. Useful for tracking dvd + collections of tv shows. + Computer Software collection, written by Qoolman + - Video games collection: + New fields: Region, Serial Number. + * New plugins to fetch information from websites + - TV shows (series): + Thetvdb + Themoviedb + * Bugs fixed + - Unable to remove more than 9 items from a collection at a time + - Crash when trying to create a cached image from a missing picture, or when + image does not exist + - Some characters were incorrectly encoded in read only views + - Add option in collection read only views for fields to "collapse" and hide + when they have no content + - Fixed problem at startup when some items were hidden in the toolbar. + - The "View modules" menu was not working anymore. + - Add play toolbar button for tv episodes collections. + - Item window was not properly re-constructed after changing collections. + - Dependency on Sort::Naturally removed. + - Date::Calc module was not shown as an optional dependency while it is + needed for statistics. + +------------------------------------------------------------------- + +1.6.0 + * New features + - Generation of statistics: + New item in 'File' menu to create pictures based on number of items + having the same value for a given field (e.g. you can see how many + video games you have for each genre). + Options to select the type of graphic (bars, area, pie). + Histories can be created showing the evolution of the collection. + - Images thumbnails are now cached. This results in a dramatic speed up + of loading collections when hi-resolution pictures are used. + - Dragging and dropping a file to a collection will add it to the + collection if the file is: + A video file on a movie collection. + An ebook file on a book collection. + * New translations + - Chinese (Traditional) translation added thanks to 林雪凡. + * User interface changes + - Language strings are now collection dependant. This results in a + nicer user experience, replacing strings like "New item" with + "New movie", "New game", etc. + - New right click context menu on file chooser control, with the option + of choosing a folder instead of a file. Useful for dvd rips stored in + folders, where the folder path needs to be passed to an external + program. + - Added Update button to items, which refreshes the current item's + details from the web page it was originally fetched from. + - Fields with a history have an auto-completion feature when user + starts to enter text. + - Right click context menu works as expected when multiple items are + selected. + - The collection may be saved if something was changed without clicking + on an item in the list. + - When closing an collection without a filename, you now have the + option to save, cancel or discard the changes. + - File open dialog is no longer case sensitive when filtering + extensions. + - Video and Ebooks file chooser filters files to valid file types + - Lots of new skins and skin remakes thanks to Qoolman + - Items are now sorted using a 'natural sort' routine, for more + predictable sorting of mixed numeric/text titles + * Default collections changes + - New default collections: + Smart cards (by jimjim92). + - Books collections can now link to a local or online 'digital version' + of the book. + - Video games collection: + New fields: Exclusive, Press rating, Size and Display resolutions. + - Music collection + New fields: Press rating + * Export/Import/Fetch/Extract changes + - New plugin to export to an external disk. It just copies the + collection to a directory with all the pictures in a sub-directory. + Everything can also be compressed in one single file. Useful to copy + a collection for GCstar Viewer on Android. + http://wiki.gcstar.org/en/gcstar_viewer + - New plugin to export in Palm PDB format. + - Some plugins are now preferred by gcstar, these are for sites with + high quality data and with fully operational plugins. These sites are + now shown first in the plugins list, and are marked by a star. + - Default plugin choice now matches with the user's language settings. + - CSV Import plugin now has the option to pull information from + websites during import. + - DVDFr plugin (Movies - FR) now supports searching by EAN. That means + it can directly be used with a barcode scanner. + - Importing from Tellico handles ISBN and rank for book collections and + developers for game collections (patch from DanielS). + - HTML export handles pictures and link with generic models (e.g. + Shelf). + - Folder import is not shown when an incompatible collection is open. + * New plugins to fetch information from websites + - Movies: + Amazon UK (EN) and DVDEmpire (EN) provided by dingsi. + CartelesPeliculas (ES) provided by DoVerMan. + * Bugs fixed + - Bug fixed preventing opening of collections with custom fields in + some circumstances. + - When fields are hidden, they don't appear anymore in the expanders + (e.g. subtitles in movies collections). + - Tonight window works as expected after changing collections. + - Menus were not updated on Windows systems. + - Some urls where misinterpreted as collections when drag and dropped + onto main window + - Bug fixed in some website plugins: + - Massive amount of plugin fixes provided by Tenbaht, including: + DVDFr + OFDb + Imdb + Alpacine + BeyazPerde + Csfd + Moviemeter + Onet + Mediadis + * Removed plugins: + - dvdspot.com was shut down Oct. 2008, removed the GCfilm + plugin GCDVDSpot.pm + - movieclub.be shut down, removed GCfilm plugins + GCMovieClubFR.pm and GCMovieClubNL.pm + +------------------------------------------------------------------- + +1.5.0 + + * User interface changes + - When too many fields are shown in the dialog box to select which of + them should be displayed, scrollbars are used. + * Collection changes + - Added an optional attribute to collection fields, sorttype, which + overrides the sorting method for that field. Can be either 'number' + or 'date'. + * Bugs fixed + - Preview when fetching information was broken. + - Button to open the web page associated to an item was disabled when + locking a collection. + - It was not possible to modify the title to search for on websites. + - Some collections may not be opened if custom fields were added. + - The groups of fields were shown more than once in some places when + adding custom fields. + - Many bugs fixed for image mode. + - Cast was not fetched for every title when using imdb with an import + plugin + - Sorting by movie release date was not working with non 'yyyy-mm-dd' + formats + - On Windows systems, GCstar now stores its configuration files in the + users Application Data folder, rather than in the GCstar program + folder. This should stop GCstar needing admin rights to run. On first + run of the new version, GCstar should copy the configuration from the + old path. + - Exporting only visible items now works properly with "items matching + any of the criteria" type filters. + - Searching OFDB with German characters works properly + - No more storable warnings when searching from plugins + - Fix star rating widget display with small screen sizes + - Better sizing of image display window + - GCstar now correctly installs icons in the hicolor theme, and now includes + a scalable svg icon + +------------------------------------------------------------------- + +1.5.0 beta1 + + * User interface changes + - Items can be grouped in picture mode as in detailed mode. + - Pictures are resized if needed when displayed in a window. + - User filters can be added to toolbar. + - A right-click on the "Fetch information" button displays a menu + where the mode to use can be selected. + - Column used for quick searches is the one used for ordering in + detailed mode. + - File associated to an item can be a URL (http or ftp) instead of a + local file. + - Search box displays a message when no field have been selected in + personal model. + + * Export/Import/Fetch/Extract changes + - User can choose for each field from which sites information should be + fetched and in which order (if missing from 1st, choose 2nd, etc...). + - FolderImport changes (by BubbleGum) + Can add only recent items from a folder. + Can update path from moved files. + Parse filename to retrieve infos and test for subtitle file. + Remove custom regular expression from filename. + More options when no or many results. + + * Default collections changes + - New default collections: + Stamps (by Bigoud) + Periodicals (by yggdrasiil) + Mini vehicles (by jimjim92) + - Rank in series added to books collections + - Press rating added to movies collections + + * New plugins to fetch information from websites: + - Video games: + Amazon (CA - EN) and NextGame (IT) provided by TPF. + - Movies: + Internet Bookshop (IT) provided by t-storm. + Alpacine and CartelesMetropoliGlobal (ES) provided by DoVerMan. + Kinopoisk (RU) provided by Nazarov Pavel. + - Books: + Saraiva (PT) provided by nirev. + - Comics: + Manga-Sanctuary (FR) provided by Biggriffon. + + * Bugs fixed + - Collections with invalid characters are auto-repaired instead of + generating error message and being unusable. + - Prevent data corruption if a crash occurs while saving collection. + - Histories were lost when editing the collection model. + - Issues fixed with grouping when there are some spaces in lists. + - Default size for history of opened files is 5 instead of 1. + - Item window was shown after closing a picture window. + - Fixed problems when saving searches with numeric comparisons. + - Fixed issues with suffixes in plugin to import from a folder. + - Disable items in popup menu for pictures when collection is locked. + - Correctly export value in options fields when using personal models. + - Bug fixed when performing a search while the current item has been + modified. + - Bug fixed in some website plugins: + All Amazon plugins + All Tvdb plugins (TV shows episodes) + Adlibris (Books - FI, SV) + Alapage (Books - FR) + Alapage (Video Games - FR) + Allocine (Movies - FR) + Bedetheque (Comics - FR) + Bol (Books - IT) + Buscape (Books - PT) + Cinemotions (Movies - FR) + DicoDuNet (Video Games - FR) + Discogs (Music - EN) + FilmWeb (Movies - PL) + Fnac (Books - FR) + GameSpot (Video Games - EN) + IMDb (Movies - EN) + ISBNdb (Books - EN) + JeuxVideo.com (Video games - FR) + MobyGames (Video games - EN) + nooSFere (Books - FR) + OFDb (Movies - DE) + +------------------------------------------------------------------- + +1.4.3 + + * New translations + - Dutch translation added thanks to kim. + + * Export/Import/Fetch/Extract changes + - Improved performances for plugins fetching data from websites. + - Discogs plugin (Music - EN) supports searches using artist or label. + + * Bugs fixed + - Fixed display problem in image mode when removing or renaming items. + - Shortcuts can also be defined on saved searches. + - Use system ACL when ckecking file permissions. + - The default options were not used on first startup. + - Problems fixed with find-as-you-type in image mode. + - Web searches were broken with new versions of Storable. + - Bug fixed in some website plugins: + Amazon (Books - FR) + Amazon (Movies - FR) + Discogs (Music - EN) + FilmAffinity (Movies - EN and ES) + GameSpot (Video Games - EN) + +------------------------------------------------------------------- + +1.4.2 + + * Export/Import/Fetch/Extract changes + - Fetch can be done using writer for comics collections. Supported by + Bedetheque plugin. + - More plugins use big pictures if option is checked (Thanks to adiGuba). + Allocine (Movies - FR) + Cinemotions (Movies - FR) + - Import identifier and media labels from movies collections created with + Ant Movie Catalog. + + * New plugins to fetch information from websites: + - nooSFere (Books - FR) provided by TPF + - TheLegacy (Video games - DE) provided by TPF + + * Bugs fixed + - Title when opening items in windows was not updated. + - Rating was lost when displayed with stars and fetched from a website. + - Title in the list was incorrect for some collections when modifying + many items together. + - When changing a picture, the previous one was not always selected in + the file manager window. + - Duplicating an item also duplicates the pictures that are managed by + GCstar. + - An extra dot was added on the end of borrowers email when sending a + message. + - The option to use big pictures from websites were ignored when + importing from a list of name or from a directory. + - .tar.gz import was failing for default collections with added fields. + - Exporting from command line didn't copy the pictures. + - Bug fixed in some website plugins: + All Tvdb plugins (TV shows episodes) + Allocine (Movies - FR) + Bedetheque (Comics - FR). Thanks to Ogddit + Cinemotions (Movies - FR) + Fnac (Books - FR) + JeuxVideo.com (Video games - FR) + +------------------------------------------------------------------- + +1.4.1 + + * New plugin to import collections from MyMovies done by Rob Maas. + + * Missing translations added for Greek and Polish. + + * Bugs fixed + - Music collections were corrupted. + - Export plugins had problems with pictures without suffixes. + - Option to fetch big pictures from websites was hidden on Windows + systems. + - A few texts were left untranslated. + - Bug fixed in image mode when updating an item and filtering. + - Bug fixed in some website plugins: + - Amazon (Video games - FR) + - Fnac (Books - FR) + +------------------------------------------------------------------- + +1.4.0 + + * Default models can be modified through the graphical user interface + to add some user-defined fields. These fields will be in separate tabs + (one for each group) or in expanders depending on the kind of panel used. + These additional fields are stored in the collection itself to let users + share it between different machines/systems. + + * New default models to manage comic books and episodes of TV shows. + + * New translations + - Ukrainian translation added thanks to Ailandar. + - Hungarian translation added thanks to Takács László Krisztián. + - Greek translation added thanks to Dimitri Glentadakis. + + * Toolbar improvements + - User can choose what controls are shown and their order. + - Useful items from menus can also be used as buttons in toolbar. + - A quick search can be added to the toolbar. + - New controls can be added to change the list view or the information + layout. + + * Options/preferences changes + - Number of columns in image mode can be automatically calculated by + GCstar. + - A language can be specified in the properties for a collection. It will + be used for spell checking. + - Tear-off menus can be disabled. + - Different sizes may be specified for pictures in image and detailed + modes. + - Behaviour of user specified programs has changed, now GCstar will use + the system default applications for any categories with an empty command. + This allows the user to override the application for individual formats, + while leaving the other applications at the default. + - Added the option for a user-specified image editor. + + * Search improvements + - Users can save an advanced search or the current one to create a + filter. + - New kinds of comparison in advanced search: 'Does not contain' and + 'Regular expression'. + - An option has been added in advanced search to select if it should be + case-sensitive or not. + - Another option in advanced search is available to ignore accents and + other diacritics. + - When comparison is 'Range', there are 2 fields to enter values in + criteria of advanced search. + - An option has been added in find and replace to select if it should + be case-sensitive or not. + - Performance enhancements. + + * User interface changes + - New default images when none is specified. + - User can change default picture in the collection properties. + - Main picture for some default collections can be flipped to see back. + Flipping is done by clicking on the button that appears when hovering + picture or by pressing F or BackSpace when it is active. + - Ratings are now shown graphically, with a new star-rating control. + - Fields selections are done through a hierarchical view. + - Standard confirmation is used when trying to overwrite a file. + - Window title conforms to GNOME HIG with collection name first. + - Right clicking on an image field gives the option to open with an image + editor. If Gnome2::VFS is present, this list will be populated with + all installed applications associated with images on the system. + - The button linking to the web page displays the plugin used to fetch + the information. + + * Export/Import/Fetch/Extract changes + - Information could be extracted from audio CDs using FreeDB thanks to + DomiX. + - It's possible to add items or to create a new collection of movies or + music from files in a directory (Menu File > Import > Folder). + - A new option is available to fetch big pictures when available. For + the moment, it only has some effect with: + JeuxVideo.com (Video games - FR) + MobyGames (Video games - EN) + - The order for sorting can be specified when exporting. + - The HTML export resizes the pictures using specified height. + - New template for HTML export for board games collections created by + Piwi. + - Default templates for HTML export are available even if specific ones + are defined. + - A charset can be speficied for CSV export. + + * Default collections changes + - A back cover added to these collections: Video Games. + - New field 'Region' for movies collections. + - New field 'Origin' for music collections. + + * New plugins to fetch information from websites: + - Books: + Chapitre.com and Le-Livre (FR) provided by TPF + - Comics: + Bedetheque (FR) provided by Chessnico + - Movies: + Onet and Stopklatka (PL) provided by Marek Cendrowicz + - Video games: + Amazon (JP) provided by TPF + + * Other improvements + - It's possible to set an item as returned from the window displaying + the list of borrowed items. It also displays the lending history. + - When opening a collection that was created with a more recent + version, a warning is displayed to let the user know some data could + be lost if it is saved. + - Install script registers mime-types and .desktop files, to properly + associate .gcs files with the system. + - When trying to play the file associated to an item, an error is + displayed if it can't be found. + + * Bugs fixed + - Borrowers filter was empty when using read-only layout. + - Problem fixed when there was special characters in the mail template + for borrowers. + - Date format was not used in the read-only panel, the advanced search + and the history of borrowings. + - There was a problem when filtering a collection with grouped items. + - Find and Replace was broken for complex fields. + - After importing, the item count was not updated in detailed list. + - Next button when using 'Many sites' for information fetching was broken. + - Prevented a problem with user-defined collections if a field has been + removed. + - .tar.gz export didn't add the model when it was a personal one with a + name. + - Problem fixed when grouping field contains a number between + parentheses. + - Sorting by a number or a date was incorrect when exporting. + - Bug fixed with import/export plugins that have a translated name when + using them from command line. + - Bug fixed with plugin importing from a list of name when using it + from command line. + - When extracting information from a video file, nothing happened if + some values were not selected. + - With Tian and Tian-Mario templates for HTML exports, the internal + links were wrong. + - Bug fixed in some website plugins: + All Amazon plugins + CulturaliaNet (Movies - ES) + FilmWeb (Movies - PL) + IMDb (Movies - EN) + JeuxVideo.com (Video games - FR) + JeuxVideo.fr (Video games - FR) + OFDb (Movies - DE) + +------------------------------------------------------------------- + +1.3.2 + + * Galician translation added thanks to Daniel Espineira. + + * New plugin to fetch information from websites: + - Music: Discogs (EN) provided by TPF + + * New template for HTML export: Float (Movies) + + * Changes for fetching + - Rating is fetched from IMDb website (Movies - EN). + - Empty fields are not fetched. + + * Bugs fixed + - There was a problem when grouping and with articles on the end. + - Bug occured when removing the last item of a group. + - Some invalid characters were added to collections. + - The date format was not used when exporting. + - Number of items in status bar was sometimes not updated. + - Decimal numbers were truncated in detailed list. + - Fields used to sort when exporting was not restored when exporting + again. + - 'Serie' was left untranslated in books collections. + - Correct order for Favourite and Keywords in Tags panel when + choosing items to display. + - Bug fixed in some website plugins: + All Amazon plugins + BDGest (Books - FR) + DVDFr (Movies - FR) + FilmUP (Movies - IT) + JeuxVideo.com (Video games - FR) + JeuxVideo.fr (Video games - FR) + MusicBrainz (Music - EN) + +------------------------------------------------------------------- + +1.3.1 + + * New plugin to fetch information from websites: + - Video games: + Ludus (IT) provided by TPF + - Books: + ISBNdb (EN), Buscape (PT) and LiberOnWeb (IT) provided by TPF. + + * Bugs fixed + - When modifying the cover picture from a window, it was not updated + in the main panel. + - For new books, Read field is initialized to 'not read'. + - Removing many items in picture mode sometimes failed. + - XML export plugin didn't list the default models and had problems + opening a specified one. + - Spelling checked is deactivated if required dictionary is not + installed instead of generating an error. + - Bug fixed in some website plugins: + All Amazon plugins + Alapage (Video games - FR) + BDGest (Books - FR) + Gamespot (Video Games - EN) + JeuxVideo.com (Video games - FR) + JeuxVideo.fr (Video games - FR) + TricTrac (Boardgames - FR) + +-------------------------------------------------------------------- + +1.3.0 + + * Multiple selections + - Many items may be selected in the list using Control and Shift keys. + They can be modified or deleted together. + - It is possible to select many items after searching on websites. All + of them will be added to current collection. Information for the + first one will be fetched in current item. + + * Collections of board games added thanks to Zombiepig. + + * Improvements for image mode + - It is possible to add an overlay picture in image mode to make them + look better. + - New skin has been added to image mode when using a background + picture: Glass and DarkGlass. They add some reflection under the + pictures. + - Improved look for borrowed items. + - A color could be used for selection, even with a background picture. + + * New or changed fields + - For movies collections, the list of actors is now a real list. A + role can be attached to each actor. + - Some tags could be assigned to items. It is also possible to mark + some of them as favorites. + + * Options/preferences changes + - A spelling checker is available for long text fields. It could be + activated or deactivated in the Features tab of preferences. + - User may change the accelerators associated to items in menus. The + menu should be highlighted, and then the keys to use should be + pressed. + - Users may choose the format to display dates. This format follows + the same rules as the one for strftime(3). + - A new option let users select what should be done when text in + expanders is too long. + - It's possible to define a cookie jar that will be used by the + fetch plugins who need it. + - A new skin has been added to read-only panels: Dark. + - In detailed mode, another field could be used to sort items grouped + in a parent item. + - Small reorganization in preferences window + + * Advanced search improvements + - The fields for entering values are changed according to the expected + data. As an example, fields will have their histories when one + exists. + - Users could select to search in all of the fields for given text. + + * New plugin to fetch information from websites: + - Board games: + Board Game Geek (EN) provided by Zombiepig + Reservoir Jeux and Tric Trac (FR) provided by Florent + - Movies: + NasheKino and Animator (RU) provided by zserghei + + * CSV import + - Some fields could be ignored. + - A charset could be specified. + + * Command line + - A new command line option --list-plugins is available to list all + the plugins of a given collection type. + - When using a wrong name for a plugin in command line, an explicit + error is displayed. + - Better management of pictures. + + * Bugs fixed + - Default shortcut to add a new item has been changed from Ctrl-A to + Ctrl-T. Then Ctrl-A is available again for selecting all text. + - The tooltip in results for web searches is hidden when clicking on + it. + - When canceling creation of a new collection, current one was closed. + - Drag and drop of a picture works also when the path contains some + spaces. + - Picture selection is done by opening the previously used directory + if there is no picture. + - Some plugins for website did not set a suffix for fetched pictures. + - Bugs fixed with sort when exporting. + - After import, the number of items in status bar was not changed when + needed. + - Bug fixed when importing from a list of items if a plugin returned + no picture. + - Bug fixed in some website plugins: + All Amazon plugins + Biblioteka Narodowa (Books - PL) + Allmovie (Movies - EN) + Beyaz Perde (Movies - TR) + Alapage (Video Games - FR) + Gamespot (Video Games - EN) + +-------------------------------------------------------------------- + +1.2.2 + + * Bug fixed in some plugins: + IMDb (Movies - EN) + MusicBrainz (Music - EN) + + * Bugs fixed with extraction of information from playlist for + music collections. + + * Bug fixed when filtering soon after startup a collection + displayed in picture mode. + + * In picture mode, if modification on an item changed its + place in the list, it was not selected after clicking on + it. + + * When updating GCstar, the extract plugins are also updated + if required. + +-------------------------------------------------------------------- + +1.2.1 + + * New plugin to fetch information from Allmovie (EN) provided + by Zombiepig. + + * 2 extra spaces were added on the end of long text fields + when saving. + + * Size of the fields list in the settings for user-defined + collections could be changed. + + * In preferences, toolbar size could be set to system setting + instead of a specific value. + + * Labels for Tonight and Play buttons were always shown, even + if the user choose to display only icons in toolbars (this is + a system setting, not a GCstar's one). + + * A bug prevented a user from creating a new kind of collection + on startup. + +-------------------------------------------------------------------- + +1.2.0 + + * Bug fixed when fetching information for an item while in + image mode. + + * If a fetch was canceled and a different one was launched, + the previous results were used again. + + * When changing the main picture of an item, it was loaded + two times. + + * Bug fixed in some plugins because of websites changes: + Allocine (Movies - FR) + CSFD.cz (Movies - CS) + IMDb (Movies - EN) + + * Some missing translations have been added to + DE, ES and SV versions. + +-------------------------------------------------------------------- + +1.2.0.beta3 + + * Correct number of displayed items is used in status + bar when filtering in image mode. + + * An error is displayed when trying to export from + command line to HTML with a non existing model. + + * The query field in the window where the user may + select the plugin to use was sometimes not initialized. + + * It's now possible to perform a quick search also in image + mode. Quick search begins when the user enters some characters + while the list is active. + + * These keys are managed in picture mode: Page Up, Page Down, + Home and End. + + * When a critical error occurs, user may report a bug + semi-automatically. + + * The progress bars in splash screen and in status area + of main window are more accurate. + + * Search dialog was not centered on first opening. The same + applied to the window used to create a new collection type. + + * Bug fixed in some plugins because of websites changes: + Alapage (Books - FR) + Cinemotions (Movies - FR) + Dicshop (Movies - SV) + Merlin (Books - PL) + + * Some missing translations have been added to + FR, SV, PL and CA versions. + +-------------------------------------------------------------------- + +1.2.0.beta2 + + * When the picture was hidden in one of the default + collections, all the fields next to it were also + hidden. + + * When using some searches of filters, if nothing was + displayed, the panel displayed the warning message + instead of the "View All" button. + + * Escape XML entities in long text fields for saved + collections. + + * Fixed a bug when there is no active collection and + another one could not be opened. + + * Fixed a potential problem when using command line actions + if no explicit model has been specified. + + * HTML export on command line has a default template + +-------------------------------------------------------------------- + +1.2.0.beta1 + + * Swedish translation added thanks to Ramon Radnoci. + + * Indonesian translation added thanks to Nugrahadi. + + * Brazilian Protuguese translation added thanks to Daniel Valença. + + * Wine collections added thanks to Yves Martin. + + * New plugin to fetch information from websites: + Movies: + DVDSpot (EN) + provided by Marc Deslauriers + CSFD.cz (CS) + provided by Pajdus + Beyaz Perde (TR) + provided by Zuencap + MovieMeter (NL) + provided by MaTiZ + Port.hu (HU) + provided by an anonymous contributor + Books: + BDGest (FR) + provided by Rataflo + National Library, Merlin, NUKat and Mareno (PL) + provided by WG + Bokkilden (NO) + provided by Tian + Fnac and Mediabooks (PT), Bol and InternetBookShop (IT), + provided by TPF + Video Games: + DicoDuNet (FR) + provided by TPF + + * New plugin to export to Latex format created by Zserghei. + + * When creating a new collection or at first startup, user can + import an existing collection with some of the available + plugins. + + * When grouping items, a new option is available to add the + number of items in a category. + + * An option has been added for grouping of orphaned + items or not. Items whitout a master, when grouping is + activated, are together on the end of the list by + default. But they could be mixed with masters for + ordering. + + * In detailed view, the order of the columns could be + changed using drag and drop on their headers. + + * It's possible to select a field for sorting when + exporting (if applicable). + + * Only the required pictures are copied during export. + + * New field in movies collections to store a numeric + identifier. The unique identifier displayed next to + the title is now read-only. + + * Added a field to collections of video games: Executable. + This field could contain the path to a program that + could be launched directly from a Play button in + GCstar's toolbar. + + * When changing field to use for fetching, the value from + panel is used. + + * Delete key removes current item in multiple lists. + + * When opening window to perform searches, the first + field has focus. + + * Removed dot from picture format when exporting to + Tellico (Patch from Robby Stephenson). + + * Use proxy for updates (Patch from Javier Donaire). + + * Bug fixed in some plugins because of websites changes: + All Amazon plugins + IMDb (Movies - EN) + Allocine (Movies - FR) + FilmUP (Movies - IT) + Alapage, JeuxVideo.com (Games - FR) + Gamespot (Games - EN) + Adlibris (Books - FI, SV) + Casadelibro (Books - ES) + + * Bug fixed when performing an update (gcstar -u) if + a new directory should be created. + + * Bug fixed with plugin to import a collection + generated from Ant Movie Catalog that contains + some semicolons. + + * Bug fixed in read only mode when data contains some + HTML tags. + + * Bug fixed with the title of the window when using + "Tonight" feature. + + * When opening a collection of a type different than the + current one, if a filter was present, nothing was + displayed. + + * Updated bug report URL in man page and when using + -h or --help on command line. + + * Reduced time for startup and to load a collection + +-------------------------------------------------------------------- + +1.1.1 + + * Bug fixed when items are grouped and the grouping field is + changed. + + * FilmWeb plugin was not working anymore because of website + changes. + + * Better layout for Paths tab in preferences. + +-------------------------------------------------------------------- + +1.1.0 + + * Instead of specifying many external applications (for web + pages, video and audio files), you may use the settings + defined by the system. It works for any Unix (including + GNU/Linux) using XFCE, Gnome or KDE) and also for MacOS X and + for Microsoft Windows. + + * Numismatic collections added thanks to szdavid. + They could be imported and exported from/to Tellico. + + * Catala translation added thanks to Ponç J. Llaneras. + + * Arabic translation added thanks to Muhammad Bashir Al-Noimi. + + * New plugins to get information from websites. All of them + are made by TPF. + Books: Adlibris (FI, SV), Casadelibro (ES), Fnac (FR) + Video games: Alapage (FR), Amazon (US, DE, FR, UK) + + * The directory used to store pictures could be specific to a + collection. It's defined in the collection properties + (File menu > Properties) + + * Path of directory used to store pictures could contain some + special text. + - %WORKING_DIR% or . will be replaced with collection directory + (use only on beginning of path) + - %FILE_BASE% will be replaced by collection file name + without suffix (.gcs) + Default value (that could be restored) is now: + ./%FILE_BASE%_pictures/ + + * A new type of field is available for user-defined + collections: "Dependant on other fields". It will be generated + thanks to other fields. The Default value should then contain the + format were %Name of other field% will be replaced with the + value of the field. + + * New fields for default collections: Add date. Its default + value when adding a new item is the current date. + + * If the Fetch button is clicked with right button of the mouse, + the plugin to use will be asked to the user, even if one is + specified in preferences. + + * When a value is truncated in web search results, a tooltip + popup containing the whole text is displayed when the mouse + pointer is over it. + + * Some options not useful for most users are hidden until the + expert mode is activated + + * In dialog for date selection, double-clicking on a date + validate it. + + * When importing from GCfilms, it's possible to reuse unique + identifiers instead of generating them. + + * Image was not updated in images list when downloading + information. + + * Bug fixed when dropping a picture dragged from a web page. + + * Number of items was not updated when an item was removed. + + * Windows installation program doesn't install dependencies. + More information here: + http://wiki.gcstar.org/en/install_windows + +-------------------------------------------------------------------- + +Task and bugs number refer to items on Gna! project page: +https://gna.org/projects/gcstar/ + +1.0.0 + + * Management of music collection. + + * Extraction of video information from a file has been + re-added (it has been removed when creating GCstar + from GCfilms). + (bug #7157) + + * Plugin for music collection using MusicBrainz website. + + * Import and export music collections from and to Tellico. + + * Information about an album could be extracted from a + playlist. + + * New plugin for video games using MobyGames (EN) made by TPF. + + * New plugin for books using Alapage (FR) made by TPF. + + * New plugin for movies using MovieCovers.com (FR) made by + Patrick Fratczak. + + * It's possible to search using EAN or UPC code (barcode) + instead of ISBN for books using one of the plugins for Amazon. + + * In file properties dialog, there are also the full file + path, the file size and the number of items in the collection. + + * Lists used for some data (such as genre for movies or codes + for video games) could be reordered using drag and drop. + + * Widths of columns in detailed mode are saved when leaving the + application or opening another file. + (bug #7666) + + * In list of plugins for web searches, the fields that could + be used for searches are displayed. + + * New item in Help menu to display all the available plugins + for web searches. + + * Templates provided for HTML export of music collections. + + * New template for HTML export of book collections: FloFred + provided by Florent. + + * --verbose option added to installation program. + + * All Amazon plugins changed because of website changes. + + * Patch from tiwek to get original titles from FilmWeb. + + * Added in search dialog some information about the kind of + comparison that will be performed. + + * When specifying a grouping field from toolbar, the master + is always auto-generated. + (bug #7500) + + * Fixed bug with CVS import for collections that are not movies + collections. + + * Fixed bug with character encoding in XML files. + (bug #7584) + + * Fixed bug with installation program that prevented an upgrade + without manual removal of previous files. + (bug #7481) + + * Fixed bugs with external programs that contains spaces in + their paths on Win32 systems. + +-------------------------------------------------------------------- + +0.5.0 + + * Bookmarks management (Menu "My Collections"). + It could be used to organize your collections and have + a quicker access to them from GCstar. + + * New "Advanced" search where the user could define the filter + to use. + + * Internet search could be made according to many fields. It + concerns for the moment only books collections where search + could be made using ISBN. + (bug #6898) + + * Results of web search could be previewed. + + * New import plugin to merge 2 GCstar collections. + (sr #1293) + + * New plugins for video games using JeuxVideo.com (FR) website + provided by TPF. + + * New plugins for books using Amazon.de (DE) website + provided by Frenkx. + + * New plugins for books using Amazon.uk (EN) website. + + * German translation updated by Frenkx. + + * New item in "Help" menu to report a bug. + + * ISBN field could contain an 'X' character. + + * Bugs fixed with import plugins when inserting items in current + collection. + + * Export plugin for .tar.gz also stores collection information + (description, owner,...) + + * HTML template "Shelf" has a new feature. Information within a + group could be hidden or shown. + + * Bug has been fixed for Windows installation program + (bug #7122) + +-------------------------------------------------------------------- + +0.4.1 + + * Mail programs could be defined. They will be used to + send e-mails instead of Sendmail or direct SMTP server. + Default configuration provided for Sylpheed-Claws, + Thunderbird and Evolution. + + * Search could be performed according to original + title for movies collections. + (sr #1246) + + * Bug fixed with old HTTP::Headers versions. + (sr #1245) + + * Bug fixed for Windows users with launch of external + programs. + (bug #6762) + +-------------------------------------------------------------------- + +0.4 + + * Added books collection management. + + * 2 plugins for books: Amazon.com (EN) and Amazon.fr (FR). + + * Import book collections from Tellico and Alexandria. + Export book collections to Tellico. + + * If grouping is done using a value that is a list, it will + be splitted. So an item will appear many times, for each + value. + + * New command line options to execute some tasks automatically. + More information here: + http://www.gcstar.org/doc.execute + + * "gcstar --help" is more verbose. A man page has also been + created. + + * Borrowers information (Name and e-mail address) could be + imported from these formats: LDIF, Sylpheed-Claws, VCARD. + + * Pictures could be saved with a file name that is prefixed + with the item title or name (instead of gcstar_). + (sr #1175) + + * It is now possible to set some properties on a collection + (a name, owner name and e-mail address, description). It is + done through File menu, Properties item. + + * A play button is in toolbar for collections that needs one + (as for movies collections). It could also be set for + user-defined collections. + + * For user-defined collections, a read-only layout is also + generated. + + * HTML export is also possible for user-defined collections. + + * Saving is faster and gives more feedback to user. + + * When importing data from Internet, if the preview for an + image is used, it won't be downloaded only once. + + * Some templates used by HTML export have been made valid + XHTML 1.0 Strict: Shelf, Tabs, Tian, Tian-Mario + + * New option in preferences to set if the previous + collection should be loaded on startup. + + * Locked status is stored in the collection itself and not + globally. + + * Force UTF-8 encoding for models files. + (bug #6545) + + * Bug fixed with search. + (bug #6536) + + * Bug fixed when no user-defined collection exists. + (bug #6520) + +-------------------------------------------------------------------- + +0.3 + + * It is now possible to create personal collections. + Such a collection will contain some fields set by the + user according to her or his needs. More information + in the documentation: + http://www.gcstar.org/doc.models.en.php + + * If something is changed in a collection and the + auto-save feature is not activated, the user is warned + when closing the collection. + + * Installer failed when an unsupported locale was used. + (bug #6324) + + * CSV import didn't correcty set the charset to UTF-8. + + * Added an update shortcut in Start menu for Windows users. + + * HTML templates was not listed on Windows systems. + (bug #6366) + + * Website plugins works also if sites send pages in a compressed + format (useful for AniDB that was broken). + +-------------------------------------------------------------------- + +0.2 + + * Added Video games management: GCgames. + + * 2 plugins for video games to import from websites: + GameSpot.com (EN) and JeuxVideo.fr (FR). They also + import screenshots and the tips that could be found. + + * Added Turkish translation thanks to KaraGarga. + + * Collections member could be grouped directly from toolbar. + More options are available for different kind of grouping + in preferences dialog. + + * A picture could be dragged and dropped from a web browser + to any displayed picture (movie cover for GCfilms, + box picture or screenshot for GCgames). + + * Bug fixes and performances enhancements. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/README b/README new file mode 100644 index 0000000..a206948 --- /dev/null +++ b/README @@ -0,0 +1,37 @@ +GCstar, personal collections manager + +Application useful to manage your collections. +More information available on http://www.gcstar.org/ + +Install +------- + +You need to have Gtk2, perl and perl-gtk2 on your system. + +Then launch install script (being root for a system wide installation). +Here are available options: + +Graphic mode installation: +./install + +Text mode installation: +./install --text + +Automatic installation in a directory +./install --prefix=/path/to/directory + + +Contribution +------------ + +If you want to contribute to GCstar, consult this page: + +http://gcstar.org/contribute + + +Bug submission +-------------- + +To submit a bug, go there: + +http://forums.gcstar.org/viewforum.php?id=4 diff --git a/README.fr b/README.fr new file mode 100644 index 0000000..19d59a2 --- /dev/null +++ b/README.fr @@ -0,0 +1,37 @@ +GCstar, gestionnaire de collections personnelles + +Application permettant de gérer ses collections. +Plus d'informations : http://www.gcstar.org/ + +Installation +------------ + +Vous devez avoir au préalable installé Gtk2, perl et perl-gtk2. +Puis lancez le script install comme indiqué ci-dessous. +Pour faire une installation sur tout le systeme, lancez le en tant que root. + +Pour installer en mode graphique : +./install + +Pour installer en mode texte : +./install --text + +Pour automatiquement installer dans un répertoire : +./install --prefix=/chemin/vers/le/repertoire + + +Contribution +------------ + +Si vous souhaitez contribuer à GCstar, consultez cette page : + +http://gcstar.org/contribute + + +Rapport d'anomalie +------------------ + +Pour soumettre un rapport d'anomalie, rendez vous ici : + +http://forums.gcstar.org/viewforum.php?id=9 + diff --git a/bin/gcstar b/bin/gcstar new file mode 100644 index 0000000..4f83b71 --- /dev/null +++ b/bin/gcstar @@ -0,0 +1,358 @@ +#!/usr/bin/perl + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use utf8; + +my $initTime; +$ENV{GCS_PROFILING} = 0 if ! exists $ENV{GCS_PROFILING}; +if ($ENV{GCS_PROFILING} > 0) +{ + eval 'use Time::HiRes'; + eval '$initTime = [Time::HiRes::gettimeofday()]'; +} + +my $VERSION = '1.7.0'; + +my $MULTI_PROCESS = ((!exists $ENV{GCS_PROFILING}) || ($ENV{GCS_PROFILING} == 0)); + +use Getopt::Long; +use File::Path; +use File::Spec; +use FindBin qw($RealBin); +use POSIX qw(locale_h); + +$ENV{GCS_BIN_DIR} = $RealBin; +($ENV{GCS_LIB_DIR} = $ENV{GCS_BIN_DIR}) =~ s/bin\/?$/lib\/gcstar/; +($ENV{GCS_SHARE_DIR} = $ENV{GCS_BIN_DIR}) =~ s/bin\/?$/share\/gcstar/; +use lib File::Spec->canonpath("$RealBin/../lib/gcstar"); +if (exists $ENV{PAR_TEMP}) +{ + unshift @INC, "$RealBin/../lib/gcstar"; +} + +# For win32, redirect all the output to files +if ($^O =~ /win32/i) +{ + my $logdir = $ENV{'APPDATA'}; + $logdir =~ s/\\/\//g; + mkpath $logdir; + close(STDOUT); + close(STDERR); + open(STDOUT, ">$logdir/gcstar.log"); + open STDERR, ">&STDOUT"; + select STDERR; $| = 1; +} + +#XDG stuff +my $home = $ENV{'HOME'}; +$home = $ENV{'APPDATA'} if ($^O =~ /win32/i); +$home =~ s/\\/\//g if ($^O =~ /win32/i); + +$ENV{XDG_CONFIG_HOME} = $home.'/gcstar/config' if ($^O =~ /win32/i); +$ENV{XDG_CONFIG_HOME} = $home.'/.config' if ! exists $ENV{XDG_CONFIG_HOME}; + +$ENV{XDG_DATA_HOME} = $home.'/gcstar' if ($^O =~ /win32/i); +$ENV{XDG_DATA_HOME} = $home.'/.local/share' if ! exists $ENV{XDG_DATA_HOME}; + +$ENV{GCS_CONFIG_HOME} = $ENV{XDG_CONFIG_HOME}.'/gcstar'; +$ENV{GCS_CONFIG_HOME} = $ENV{XDG_CONFIG_HOME} if ($^O =~ /win32/i); + +# Migrate settings from old windows data path if required +if (($^O =~ /win32/i) && !(-d $ENV{XDG_DATA_HOME})) +{ + if (-d $RealBin.'/../config') + { + print ("Need to migrate settings from old gcstar data store\n"); + mkpath $ENV{XDG_CONFIG_HOME}; + mkpath $ENV{XDG_DATA_HOME}; + my $winRealBin = $RealBin; + $winRealBin =~ s/\//\\/g; + my $winXDGConfig = $ENV{XDG_CONFIG_HOME}; + $winXDGConfig =~ s/\//\\/g; + my $winXDGData = $ENV{XDG_CONFIG_HOME}; + $winXDGData =~ s/\//\\/g; + system "xcopy /Y \"".$winRealBin."\\..\\config\" \"".$winXDGConfig."\""; + system "xcopy /Y \"".$winRealBin."\\..\\data\" \"".$winXDGData."\""; + } +} + +mkpath $ENV{XDG_CONFIG_HOME}; +mkpath $ENV{XDG_DATA_HOME}; + +mkdir $ENV{GCS_CONFIG_HOME}; +mkdir $ENV{GCS_CONFIG_HOME}.'/GCModels/'; +$ENV{GCS_CONFIG_FILE} = $ENV{GCS_CONFIG_HOME}.'/GCstar.conf'; +$ENV{GCS_DATA_HOME} = $ENV{XDG_DATA_HOME}.'/gcstar'; +$ENV{GCS_DATA_HOME} = $ENV{XDG_DATA_HOME} if ($^O =~ /win32/i); +mkdir $ENV{GCS_DATA_HOME}; + +use GCOptions; +my $options = new GCOptionLoader($ENV{GCS_CONFIG_FILE}, 1); + +my $lang = $options->getFullLang; +$ENV{LANG} = $lang; +$ENV{LANGUAGE} = $lang; +$ENV{LC_ALL} = $lang; +$ENV{LC_CTYPE} = $lang; +setlocale(LC_ALL, $lang); + +sub usage +{ + print "Usage: $0 [-u UPDATE-OPTIONS] [-x EXECUTE-OPTIONS] [FILENAME] + +Launch GCstar, a personal collection manager. Without any option, it will open +FILENAME if specified or the previously opened file. + +Update options: + + -u, --update Tell GCstar to look for available updates + -a, --all Update all components + -c, --collection Update collection models + -w, --website Update plugins to download information + -i, --import Update plugins to import data + -e, --export Update plugins to export data + -l, --lang Update translations + -n, --noproxy Don't ask for a proxy + +Execute options: + + -x, --execute Enter non-interactive mode + -c, --collection MODEL Specify the collection type + -w, --website PLUGIN Specify the plugin to use to download information + -i, --import PLUGIN Specify the plugin to use to import a collection + -e, --export PLUGIN Specify the plugin to use to export the collection + -f, --fields FILENAME File containing fields list to use for import/export + -o, --output FILENAME Write output in FILENAME instead of standard output + --download TITLE Search for the item with TITLE as name + --importprefs PREFERENCES Preferences for the import plugin + --exportprefs PREFERENCES Preferences for the export plugin + --list-plugins List all the plugins available to download information + + Preferences for import/export plugins are specified using this schema: + \"Key1=>Value1,Key2=>Value2\" + +Environment variables: + + \$HOME Used to define following variables if needed + \$XDG_CONFIG_HOME Where configuration files should be stored + If not defined: \$HOME/.config + \$XDG_DATA_HOME Where some data will be stored + If not defined: \$HOME/.local/share + +Bugs reporting: + + To report bugs, please use this forum: + http://forums.gcstar.org/viewforum.php?id=4 + +"; +} + +sub version +{ + print "GCstar $VERSION\n"; +} + +Getopt::Long::Configure ('bundling'); +my ($help, $version, $update, $toBeUpdated, $noProxy, $listPlugins) = (0, 0, 0, {}, 0); +my ($collection, $website, $import, $export) = (undef, undef, undef, undef); +my ($execute, $title, $output, $inPrefs, $outPrefs, $fields) = (0, '', '', '', '', ''); +(usage, exit 1) if !GetOptions("h|help" => \$help, + "v|version" => \$version, + "u|update" => \$update, + "x|execute" => \$execute, + "a|all" => \$toBeUpdated->{all}, + "c|collection:s" => \$collection, + "w|website:s" => \$website, + "i|import:s" => \$import, + "e|export:s" => \$export, + "l|lang" => \$toBeUpdated->{lang}, + "n|noproxy" => \$noProxy, + "download=s" => \$title, + "o|output=s" => \$output, + "importprefs=s" => \$inPrefs, + "exportprefs=s" => \$outPrefs, + "f|fields=s" => \$fields, + "list-plugins" => \$listPlugins, + ); + +my $atLeastOne = 0; +foreach (keys %$toBeUpdated) +{ + $atLeastOne = 1 if $toBeUpdated->{$_}; +} +(usage, exit 1) if $help || ($atLeastOne && !$update); +(version, exit 0) if $version; + +if ($update) +{ + eval ' + use GCUpdater; + use GCLang; + '; + my $langContainer = $GCLang::langs{$options->lang}; + $toBeUpdated->{all} = 1 if !$atLeastOne; + $toBeUpdated->{models} = defined($collection); + $toBeUpdated->{plugins} = defined($website); + $toBeUpdated->{import} = defined($import); + $toBeUpdated->{export} = defined($export); + my $updater = new GCTextUpdater($langContainer, + $ENV{GCS_LIB_DIR}, + $toBeUpdated, + $noProxy, + $VERSION); + $updater->update; + exit 0; +} +elsif ($execute) +{ + use GCCommandLine; + + my $execution = new GCCommandExecution($options, + $collection, + $website, + $import, + $export, + $output); + if ($listPlugins) + { + if ($collection) + { + $execution->listPlugins; + exit 0; + } + else + { + print "A kind of collection should be specified (with -c) when using --list-plugins\n"; + exit 1; + } + } + + if ($ARGV[0]) + { + if ($import) + { + $execution->import($ARGV[0], $inPrefs); + } + else + { + $execution->open($ARGV[0]); + } + } + $execution->setFields($fields) if $fields; + $execution->load($title) if $title; + if ($export) + { + $execution->export($outPrefs); + } + else + { + $execution->save; + } + exit 0; +} +if ($ARGV[0]) +{ + # We have to make it absolute if needed + my $file = $ARGV[0]; + $file = File::Spec->rel2abs($file) + if (!File::Spec->file_name_is_absolute($file)); + $options->file($file); +} + +my $pid; +if ($MULTI_PROCESS) +{ + pipe(RCOMMAND, WCOMMAND); + pipe(RDATA, WDATA); + if ($^O !~ /win32/i) + { + if (!($pid = fork)) + { + use GCPlugins; + close WCOMMAND; + close RDATA; + my $searchJob = new GCPluginJob(\*RCOMMAND, \*WDATA); + + $searchJob->run; + } + } +} + +use Gtk2; +use GCMainWindow; + +my %searchJob = (); + +if ($MULTI_PROCESS) +{ + close RCOMMAND; + close WDATA; + %searchJob = ( + pid => $pid, + command => \*WCOMMAND, + data => \*RDATA + ); + + if ($^O =~ /win32/i) + { + close WCOMMAND; + close RDATA; + } +} +Gtk2->init; +my $window = new GCFrame($options, $VERSION, \%searchJob); + +#Gtk2->set_locale; +Gtk2->main; + +if ($^O =~ /win32/i) +{ + # We store the language in .bat file if possible + + my $batch = 'gcstar.bat'; + if (-w $batch) + { + local $/ = undef; + open BATCH, "<$batch"; + my $lang = $options->lang; + my $bat = ; + $bat =~ s/LANG=.*$/LANG=$lang/m; + close BATCH; + open BATCH, ">$batch" or die "Cannot open .bat to save language\n"; + print BATCH $bat; + close BATCH; + } + close(STDOUT); + close(STDERR); +} + +if ($ENV{GCS_PROFILING} > 0) +{ + my $elapsed; + eval '$elapsed = Time::HiRes::tv_interval($initTime)'; + print "Elapsed : $elapsed\n"; +} + +0; diff --git a/install b/install new file mode 100755 index 0000000..6bedf76 --- /dev/null +++ b/install @@ -0,0 +1,788 @@ +#!/usr/bin/perl + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use FindBin qw($RealBin); +use File::Copy; +use File::Find; +use File::Path; +use File::Basename; + +use lib "$RealBin/lib/gcstar"; +use GCLang; + +sub usage +{ + print "Usage: install [options]\n"; + print " --text Force text installation\n"; + print " --prefix=PREFIX Installs GCstar in PREFIX [/usr/local]\n"; + print " --nomenu Don't install menu entry\n"; + print " --noclean Don't remove previous installation files\n"; + print " --verbose Display more information during installation\n"; + print " --remove Remove GCstar from system\n"; + print " --help Show this message\n"; +} + +use Getopt::Long; +my ($withHelp, $withText, $withPrefix, $withoutMenu, $withoutClean, $verbose, $remove); +(usage, exit 1) if !GetOptions("help" => \$withHelp, + "text" => \$withText, + "prefix=s" => \$withPrefix, + "nomenu" => \$withoutMenu, + "noclean" => \$withoutClean, + "verbose" => \$verbose, + "remove" => \$remove); + +(usage, exit 1) if ($ARGV[0]); + +sub verbosePrint +{ + print @_,"\n" if $verbose; +} + +our $binName = 'gcstar'; + +chdir $RealBin; +$ENV{GCS_LIB_DIR} = 'lib/gcstar'; + +our %lang; + +GCLang::loadLangs; +(my $langCode = uc $ENV{LANG}) =~ s/(^.{2}).*$/$1/; +$langCode = 'EN' if !$langCode || !$GCLang::langs{$langCode}; +%lang = %{$GCLang::langs{$langCode}}; + +our $type = 'graphic'; + +$type = 'text' if (($withPrefix) || ($withHelp) || ($withText) || ($withoutMenu) || ($withoutClean) || ($remove)); + +sub checkDependencies; + +our ($mand, $opt, $optModules) = checkDependencies('GC'); + +sub clean +{ + my $baseDir = shift; + + my $home = $ENV{HOME}; + + verbosePrint $lang{InstallCleanDirectory}, $baseDir; + foreach (glob $baseDir.'/lib/gcstar/*') + { + unlink if -f $_; + if (-d _) + { + unlink foreach (glob "$_"); + } + } + + foreach (glob $baseDir.'/share/gcstar/*') + { + unlink if -f $_; + if (-d _) + { + unlink foreach (glob "$_"); + } + } + + unlink $baseDir.'/bin/'.$binName; + unlink $baseDir.'/man/man1/'.$binName.'.1.gz'; + + # remove menu and mime items + + if (-w '/usr/share/applications') + { + unlink '/usr/share/applications/gcstar.desktop'; + } + else + { + unlink $home.'/.local/share/applications/gcstar.desktop'; + } + + unlink '/usr/share/pixmaps/gcstar.png' + if (-w '/usr/share/pixmaps'); + + # Remove icons + unlink '/usr/share/icons/hicolor/16x16/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/16x16/apps'); + unlink '/usr/share/icons/hicolor/22x22/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/16x16/apps'); + unlink '/usr/share/icons/hicolor/24x24/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/24x24/apps'); + unlink '/usr/share/icons/hicolor/32x32/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/32x32/apps'); + unlink '/usr/share/icons/hicolor/36x36/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/36x36/apps'); + unlink '/usr/share/icons/hicolor/48x48/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/48x48/apps'); + unlink '/usr/share/icons/hicolor/64x64/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/64x64/apps'); + unlink '/usr/share/icons/hicolor/72x72/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/72x72/apps'); + unlink '/usr/share/icons/hicolor/96x96/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/96x96/apps'); + unlink '/usr/share/icons/hicolor/128x128/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/128x128/apps'); + unlink '/usr/share/icons/hicolor/192x192/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/192x192/apps'); + unlink '/usr/share/icons/hicolor/256x256/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/256x256/apps'); + unlink '/usr/share/icons/hicolor/scalable/apps/gcstar.svg' + if (-w '/usr/share/icons/hicolor/scalable/apps'); + system 'gtk-update-icon-cache /usr/share/icons/hicolor'; + + # Remove mime type + unlink '/usr/share/mime/packages/gcstar.xml' + if (-w '/usr/share/mime/packages'); + + system 'update-desktop-database'; + system 'update-mime-database /usr/share/mime'; + +} + +sub recursiveCopy +{ + my ($orig, $dest) = @_; + + mkpath $dest; + + foreach (glob $orig.'/*') + { + next if /CVS/; + copy $_, $dest if -f $_; + if (-d $_) + { + my $dir = basename($_); + recursiveCopy($_, $dest.'/'.$dir); + } + } +} + +sub installMenu +{ + my $home = $ENV{HOME}; + + if (-w '/usr/share/applications') + { + verbosePrint $lang{InstallCopyDesktop}, '/usr/share/applications'; + copy 'share/applications/gcstar.desktop', '/usr/share/applications'; + } + else + { + verbosePrint $lang{InstallCopyDesktop}, $home.'/.local/share/applications'; + copy 'share/applications/gcstar.desktop', $home.'/.local/share/applications'; + } + + copy 'share/gcstar/icons/gcstar_48x48.png', '/usr/share/pixmaps/gcstar.png' + if (-w '/usr/share/pixmaps'); + + copy 'share/gcstar/icons/gcstar_16x16.png', '/usr/share/icons/hicolor/16x16/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/16x16/apps'); + copy 'share/gcstar/icons/gcstar_22x22.png', '/usr/share/icons/hicolor/22x22/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/16x16/apps'); + copy 'share/gcstar/icons/gcstar_24x24.png', '/usr/share/icons/hicolor/24x24/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/24x24/apps'); + copy 'share/gcstar/icons/gcstar_32x32.png', '/usr/share/icons/hicolor/32x32/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/32x32/apps'); + copy 'share/gcstar/icons/gcstar_36x36.png', '/usr/share/icons/hicolor/36x36/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/36x36/apps'); + copy 'share/gcstar/icons/gcstar_48x48.png', '/usr/share/icons/hicolor/48x48/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/48x48/apps'); + copy 'share/gcstar/icons/gcstar_64x64.png', '/usr/share/icons/hicolor/64x64/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/64x64/apps'); + copy 'share/gcstar/icons/gcstar_72x72.png', '/usr/share/icons/hicolor/72x72/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/72x72/apps'); + copy 'share/gcstar/icons/gcstar_96x96.png', '/usr/share/icons/hicolor/96x96/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/96x96/apps'); + copy 'share/gcstar/icons/gcstar_128x128.png', '/usr/share/icons/hicolor/128x128/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/128x128/apps'); + copy 'share/gcstar/icons/gcstar_192x192.png', '/usr/share/icons/hicolor/192x192/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/192x192/apps'); + copy 'share/gcstar/icons/gcstar_256x256.png', '/usr/share/icons/hicolor/256x256/apps/gcstar.png' + if (-w '/usr/share/icons/hicolor/256x256/apps'); + copy 'share/gcstar/icons/gcstar_scalable.svg', '/usr/share/icons/hicolor/scalable/apps/gcstar.svg' + if (-w '/usr/share/icons/hicolor/scalable/apps'); + system 'gtk-update-icon-cache /usr/share/icons/hicolor'; + + copy 'share/applications/gcstar.xml', '/usr/share/mime/packages' + if (-w '/usr/share/mime/packages'); + + system 'update-desktop-database'; + system 'update-mime-database /usr/share/mime'; + + +} + +sub doInstall +{ + my ($baseDir, $baseDir2) = @_; + $baseDir = $baseDir2->get_text if $baseDir2; + + $baseDir .= '/' if $baseDir !~ /\/$/; + print $lang{InstallDirInfo}.$baseDir."\n"; + + verbosePrint $lang{InstallCopyDirectory}, $baseDir.'/bin'; + mkpath $baseDir.'/bin'; + copy 'bin/gcstar', $baseDir.'/bin/'.$binName; + + verbosePrint $lang{InstallCopyDirectory}, $baseDir.'/man/man1'; + mkpath $baseDir.'/man/man1'; + my $manPage = "$baseDir/man/man1/$binName.1"; + copy 'man/gcstar.1', $manPage; + `gzip -f $manPage 2>&1 >/dev/null`; + + chmod 0755, $baseDir.'/bin/'.$binName; + + #Copying lib + verbosePrint $lang{InstallCopyDirectory}, $baseDir.'/lib/gcstar'; + recursiveCopy('lib/gcstar', $baseDir.'/lib/gcstar'); + + #Copying share + verbosePrint $lang{InstallCopyDirectory}, $baseDir.'/share/gcstar'; + recursiveCopy('share/gcstar', $baseDir.'/share/gcstar'); + + chmod 0755, $baseDir.'/share/gcstar/helpers/xdg-open'; +} + +if ($type eq 'text') +{ + if ($withHelp) + { + usage; + exit 0; + } + elsif ($remove) + { + my $dir; + if ($withPrefix) + { + $dir = $withPrefix; + } + else + { + print $lang{InstallPrompt}; + + $| = 1; + $_ = ; + chomp; + $dir = ($_ ? $_ : '/usr/local/'); + } + clean $dir; + print "\nRemoved\n"; + + exit 0; + } + + print "\n".$lang{InstallMandatory}."\n\n"; + my %mand = %$mand; + my @missing = (); + foreach (sort keys %mand) + { + print $_, ' 'x(35-length($_)), $mand{$_}, "\n"; + push @missing, $_ if ($mand{$_} eq $lang{InstallMissing}); + } + + print "\n".$lang{InstallOptional}."\n\n"; + my %opt = %$opt; + foreach (sort keys %opt) + { + print $_, ' 'x(35-length($_)), $opt{$_}, "\n"; + } + print "\n"; + if (scalar(@missing)) + { + print "\n".$lang{InstallErrorMissing}."\n\n"; + print "$_\n" foreach (@missing); + exit 1; + } + + my $dir; + if ($withPrefix) + { + $dir = $withPrefix; + } + else + { + print $lang{InstallPrompt}; + + $| = 1; + $_ = ; + chomp; + $dir = ($_ ? $_ : '/usr/local/'); + } + + $dir =~ s/^~/$ENV{HOME}/; + + my $dirError = 0; + if (! -e $dir) + { + eval { mkpath $dir }; + $dirError = 1 if $@; + } + if (-w $dir && !$dirError) + { + clean $dir unless ($withoutClean); + installMenu unless ($withoutMenu); + doInstall $dir; + $dir .= '/' if $dir !~ /\/$/; + print "\n",$lang{InstallEnd},"\n",$lang{InstallNoError},"\n",$lang{InstallLaunch},$dir,"bin/",$binName,"\n"; + + exit 0; + } + else + { + print $lang{InstallNoPermission}."\n"; + exit 0; + } + +} + +sub checkDependencies +{ + my $pref = shift; + + my @dependencies = (); + my @optionals = (); + my $optionalsModules = {}; + + my @files = glob 'lib/gcstar/*'; + for my $component('GCPlugins', 'GCExport', 'GCImport', 'GCExtract', 'GCBackend', 'GCItemsLists') + { + foreach (glob "lib/gcstar/$component/*") + { + if (-d $_) + { + push @files, glob "lib/gcstar/$component/$_/*.pm"; + } + else + { + push @files, $_; + } + } + } + + foreach my $file(@files) + { + open FILE, $file; + while () + { + push (@dependencies, $1) if ((/^\s*use\s*(.*?)\s*(qw.*?)?;/) && ($1 !~ /base|vars|locale|integer|^lib|utf8|strict|^$pref/)); + if ( + ((/eval.*?[\"\']use\s*(.*?)[\"\'];/) && ($1 !~ /base|vars|locale|integer|^lib|utf8|strict|\$opt|\$module|^$pref/)) + || + (/checkModule\([\"\'](.*?)[\"\']\)/) + ) + #" + { + next if $1 eq 'Time::HiRes'; + push (@optionals, $1); + push @{$optionalsModules->{$1}}, $file; + } + + } + close FILE; + } + + my %saw1; + @saw1{@dependencies} = (); + @dependencies = sort keys %saw1; + + my %saw2; + @saw2{@optionals} = (); + @optionals = sort keys %saw2; + + my %mandatoryResults = (); + my %optionalResults = (); + + foreach (@dependencies) + { + $mandatoryResults{$_} = $lang{InstallOK}; + eval "use $_"; + $mandatoryResults{$_} = $lang{InstallMissing} if ($@); + } + foreach (@optionals) + { + $optionalResults{$_} = $lang{InstallOK}; + eval "use $_"; + $optionalResults{$_} = $lang{InstallMissing} if ($@); + } + + return \%mandatoryResults, \%optionalResults, $optionalsModules; +} + +eval +' + use Gtk2 \'-init\'; + use GCDialogs; +'; + +our $installDialog = Gtk2::Window->new('toplevel'); + +sub graphicInstall +{ + my $widget = shift; + + my $dir = $installDialog->{path}->get_text; + my $dirError = 0; + if (! -e $dir) + { + eval { mkpath $dir }; + $dirError = 1 if $@; + } + if (-w $dir && !$dirError) + { + clean($installDialog->{path}->get_text) + if $installDialog->{clean}->get_active; + + installMenu if $installDialog->{menu}->get_active; + + doInstall($installDialog->{path}->get_text); + + $dir .= '/' if $dir !~ /\/$/; + + my $dialog = Gtk2::MessageDialog->new($installDialog, + [qw/modal destroy-with-parent/], + 'info', + 'ok', + $lang{InstallEnd}."\n\n".$lang{InstallNoError}."\n\n".$lang{InstallLaunch}.$dir."bin/".$binName); + $dialog->run(); + $dialog->destroy ; + + Gtk2->main_quit; + } + else + { + my $dialog = Gtk2::MessageDialog->new($installDialog, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $lang{InstallNoPermission}."\n\n"); + $dialog->run(); + $dialog->destroy ; + } +} + +sub browse +{ + my $widget = shift; + my $dialog = new GCFileChooserDialog($lang{InstallDirectory}, $installDialog, 'select-folder'); + $dialog->set_filename($installDialog->{path}->get_text); + my $response = $dialog->run; + if ($response eq 'ok') + { + $installDialog->{path}->set_text($dialog->get_filename); + } + $dialog->destroy; + +} + +sub updateLabel +{ + my ($tabs, $tabLabel) = @_; + + my $current = $tabs->get_current_page; + $current = 0 if ($current <= 0); + my $label = $tabs->get_tab_label_text($tabs->get_nth_page($current)); + $tabLabel->set_markup("$label"); +} + +$installDialog->set_title($lang{InstallTitle}); +my $iconPrefix = 'share/gcstar/icons/gcstar_'; +my $pixbuf16 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'16x16.png'); +my $pixbuf32 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'32x32.png'); +my $pixbuf48 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'48x48.png'); +my $pixbuf64 = Gtk2::Gdk::Pixbuf->new_from_file($iconPrefix.'64x64.png'); +$installDialog->set_icon_list($pixbuf16, $pixbuf32, $pixbuf48, $pixbuf64); +#$installDialog->set_icon_from_file('share/gcstar/icons/gcstar_16x16.png'); + +my $vbox = new Gtk2::VBox(0,0); + +my %mand = %$mand; +my %opt = %$opt; +my $tableDepend = new Gtk2::Table(3 + scalar(keys %mand) + scalar(keys %opt),2, 0); +$tableDepend->set_row_spacings(10); +$tableDepend->set_col_spacings(20); +$tableDepend->set_border_width(10); + +my $mandMissing = 0; +my $optMissing = 0; +my $labelMand = new Gtk2::Label; +$labelMand->set_markup(''.$lang{InstallMandatory}.''); +$labelMand->set_alignment(0.5, 0.0); +$tableDepend->attach($labelMand, 0, 2, 0, 1, 'expand', 'fill', 0, 10); +my @missings; +my @oks; +foreach (sort keys %mand) +{ + my $label1 = new Gtk2::Label($_); + my $label2 = new Gtk2::Label; + + if ($mand{$_} eq $lang{InstallMissing}) + { + $label2->set_markup("".$lang{InstallMissing}.""); + $mandMissing = 1; + push @missings, [$label1, $label2]; + } + else + { + $label2->set_markup("".$lang{InstallOK}.""); + push @oks, [$label1, $label2]; + } + +} +my $i = 1; +foreach (@missings) +{ + $tableDepend->attach($_->[0], 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $tableDepend->attach($_->[1], 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + + $i++; +} +foreach (@oks) +{ + $tableDepend->attach($_->[0], 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $tableDepend->attach($_->[1], 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + + $i++; +} + +%opt = %$opt; +my $labelOpt = new Gtk2::Label; +$labelOpt->set_markup(''.$lang{InstallOptional}.''); +$tableDepend->attach(new Gtk2::HSeparator, 0, 2, $i, $i+1, 'fill', 'fill', 0, 10); +$i++; +$tableDepend->attach($labelOpt, 0, 2, $i, $i+1, 'expand', 'fill', 0, 10); + +$i++; +@missings = (); +@oks = (); +foreach (sort keys %opt) +{ + my $label1 = new Gtk2::Label($_); + my $label2 = new Gtk2::Label; + + if ($opt{$_} eq $lang{InstallMissing}) + { + my $value; + foreach my $module (@{$optModules->{$_}}) + { + $module =~ s/.*?GC([^\/]*?)\.pm$/$1/; + $value .= $module.",\n"; + } + $value =~ s/,\n$//; + $label2->set_markup("".$lang{InstallMissingFor}." $value"); + $label2->set_line_wrap(1); + $label2->set_justify('left'); + $optMissing = 1; + push @missings, [$label1, $label2]; + } + else + { + $label2->set_markup("".$lang{InstallOK}.""); + push @oks, [$label1, $label2]; + } +} +foreach (@missings) +{ + $tableDepend->attach($_->[0], 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $tableDepend->attach($_->[1], 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + + $i++; +} +foreach (@oks) +{ + $tableDepend->attach($_->[0], 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $tableDepend->attach($_->[1], 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + + $i++; +} + +my $scrollDepend = new Gtk2::ScrolledWindow; +$scrollDepend->set_policy ('automatic', 'automatic'); +$scrollDepend->set_shadow_type('none'); +$scrollDepend->add_with_viewport($tableDepend); +$scrollDepend->set_size_request(300, 200); + +my $hasErrors = 0; +my $errorLabel = new Gtk2::Label; +$errorLabel->set_line_wrap(1); +$errorLabel->set_justify('center'); +my $vboxDepend = new Gtk2::VBox(0,0); +my $vboxPath = new Gtk2::VBox(0,0); +my $hboxActions = new Gtk2::HBox(0,0); +my $hboxControls = new Gtk2::HBox(0,1); +my $vboxOptions = new Gtk2::VBox(0,0); +my $ok = new Gtk2::Button->new_from_stock('gtk-ok'); +my $cancel = new Gtk2::Button->new_from_stock('gtk-cancel'); +my $next = new Gtk2::Button->new_from_stock('gtk-go-forward'); +my $previous = new Gtk2::Button->new_from_stock('gtk-go-back'); + +$ok->can_default(1); +$cancel->can_default(1); +$next->can_default(1); + +$hboxControls->pack_end($ok, 0, 1, 5); +$hboxControls->pack_end($next, 0, 1, 5); +$hboxControls->pack_end($previous, 0, 1, 5); +$hboxControls->pack_end($cancel, 0, 1, 5); + +$vboxOptions->set_border_width(10); + +if ($mandMissing) +{ + $errorLabel->set_markup(''.$lang{InstallMissingMandatory}.''); + $hasErrors = 2; +} +else +{ + if ($optMissing) + { + $errorLabel->set_markup(''.$lang{InstallMissingOptional}.''); + $hasErrors = 1; + } + else + { + $errorLabel->set_markup(''.$lang{InstallMissingNone}.''); + } + + $installDialog->{menu} = new Gtk2::CheckButton($lang{InstallWithMenu}); + $installDialog->{menu}->set_active(1); + $vboxOptions->pack_start($installDialog->{menu},0,0,10); + + $installDialog->{clean} = new Gtk2::CheckButton($lang{InstallWithClean}); + $installDialog->{clean}->set_active(1); + $vboxOptions->pack_start($installDialog->{clean},0,0,10); + + $hboxActions->set_border_width(20); + my $pathLabel = new Gtk2::Label($lang{InstallSelectDirectory}); + $installDialog->{path} = new Gtk2::Entry; + $installDialog->{path}->set_text('/usr/local/'); + $installDialog->{path}->set_activates_default(1); + $hboxActions->pack_start($installDialog->{path},1,1,5); + my $openButton = Gtk2::Button->new_from_stock('gtk-open'); + $openButton->signal_connect('clicked', \&browse, $installDialog); + $hboxActions->pack_start($openButton,0,0,5); + $vboxPath->pack_start($pathLabel,0,0,5); + $vboxPath->pack_start($hboxActions,0,0,5); +} + +$vboxDepend->pack_start($errorLabel,0,0,10); +$vboxDepend->pack_start($scrollDepend,1,1,0); + + +my $sep1 = new Gtk2::HSeparator; +my $sep2 = new Gtk2::HSeparator; + +#my $image = Gtk2::Image->new_from_file('share/gcstar/logos/about.png'); +#$vbox->pack_start($image,0,0,10); + +my $tabs = Gtk2::Notebook->new(); +$tabs->set_border_width(0); +$tabs->set_tab_pos('left'); +$tabs->set_show_border(1); +$tabs->set_show_tabs(0); + +$tabs->append_page($vboxDepend, $lang{InstallDependencies}); +$tabs->append_page($vboxPath, $lang{InstallPath}); +$tabs->append_page($vboxOptions, $lang{InstallOptions}); + +my $headerBox = new Gtk2::EventBox; +my $colorHeaderBg = Gtk2::Gdk::Color->parse('#ffffff'); +$headerBox->modify_bg('normal', $colorHeaderBg); +my $hboxLabel = new Gtk2::HBox; +my $leftBox = new Gtk2::EventBox; +$leftBox->modify_bg('normal', $colorHeaderBg); +$leftBox->set_size_request(83,57); +my $tabLabel = new Gtk2::Label; +my $image = Gtk2::Image->new_from_file('share/gcstar/logos/install.png'); +$hboxLabel->pack_start($leftBox,0,0,0); +$hboxLabel->pack_start($tabLabel,1,1,0); +$hboxLabel->pack_start($image,0,0,0); +$headerBox->add($hboxLabel); + +$previous->set_sensitive(0); +updateLabel($tabs, $tabLabel); +$next->signal_connect('clicked' => sub { + $tabs->next_page; + my $currentPage = $tabs->get_current_page; + updateLabel($tabs, $tabLabel); + if ($currentPage == 2) + { + my $allocation = $next->allocation; + $ok->set_size_request($allocation->width, $allocation->height); + $ok->show; + $next->hide; + $ok->grab_default; + } + $previous->set_sensitive(1) if $currentPage >= 1; +}); + +$previous->signal_connect('clicked' => sub { + $tabs->prev_page; + my $currentPage = $tabs->get_current_page; + updateLabel($tabs, $tabLabel); + $previous->set_sensitive(0) if $currentPage == 0; + if ($currentPage <= 1) + { + $ok->hide; + $next->show; + $next->grab_default; + } +}); + +my $vboxIn = new Gtk2::VBox(0,0); +$vboxIn->pack_start($headerBox,0,0,0); +$vboxIn->pack_start(Gtk2::HSeparator->new,0,0,5); +$vboxIn->pack_start($tabs,1,1,0); +my $hboxSpace = new Gtk2::HBox(0,0); +$hboxSpace->pack_start($vboxIn,1,1,12); +$vbox->pack_start($hboxSpace,1,1,10); + +$vbox->pack_start($sep2,0,0,0); +$vbox->pack_start($hboxControls,0,0,5); + +$installDialog->add($vbox); + +$ok->signal_connect('clicked' => \&graphicInstall); +$cancel->signal_connect('clicked' => sub { Gtk2->main_quit; }); +$installDialog->signal_connect(destroy => sub { Gtk2->main_quit; }); + +$installDialog->show_all; +$ok->hide; +$installDialog->resize(500,400); + +if ($hasErrors > 1) +{ + $ok->set_sensitive(0); + $next->set_sensitive(0); + $cancel->grab_default; +} +else +{ + $next->grab_default; +} + +Gtk2->main; + +0; diff --git a/lib/gcstar/GCBackend/GCBackendXmlCommon.pm b/lib/gcstar/GCBackend/GCBackendXmlCommon.pm new file mode 100644 index 0000000..b8a3054 --- /dev/null +++ b/lib/gcstar/GCBackend/GCBackendXmlCommon.pm @@ -0,0 +1,305 @@ +package GCBackend::GCBackendXmlCommon; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use utf8; +use filetest 'access'; + +{ + package GCBackend::GCBeXmlBase; + + use File::Temp qw/ tempfile /; + use File::Copy; + + my %xmlConv = ( + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + '' => '', + ); + my $toBeReplaced = join '', keys %xmlConv; + + sub new + { + my ($proto, $modelLoader) = @_; + my $class = ref($proto) || $proto; + my $self = {modelLoader => $modelLoader}; + bless $self, $class; + return $self; + } + + sub getVersion + { + my $self = shift; + my $version = undef; + return $version if (! -r $self->{file}); + open DATA, $self->{file}; + binmode(DATA, ':utf8'); + while () + { + next if ! /^\s*) + { + if (/type="(.*?)"/) + { + $model = $1; + last; + } + } + close COLLECTION; + $self->{modelLoader}->preloadModel($model); + } + + sub setParameters + { + my ($self, %options) = @_; + $self->{$_} = $options{$_} foreach keys %options; + } + + sub hashToXMLString + { + my %hash = @_; + my $result = ''; + foreach (keys %hash) + { + $result .= " $_=\"".$hash{$_}.'"'; + } + return $result; + } + + sub listToXml + { + my $value = shift; + my $xml = ''; + my $col; + foreach (@{$value}) + { + $xml .= ' +'; + foreach $col(@{$_}) + { + (my $newCol = $col) =~ s/([$toBeReplaced])/$xmlConv{$1}/go; + #" + $xml .= " $newCol\n"; + } + $xml .= ' +'; + } + return $xml; + } + + sub setHistories + { + my ($self, $histories) = @_; + + $self->{histories} = $histories; + } + + sub save + { + my ($self, $data, $info, $splash, $keepCurrentValueForDate) = @_; + + # Save into a new file to prevent crashes during saving + (my ($tmpFd, $tmpFile)) = tempfile(); + if (!$tmpFd) + { + my @error = ('SaveError', ''); + return {error => \@error}; + } + + binmode($tmpFd, ':utf8'); + + my $xmlModel = ''; + my $xmlPreferences = ''; + my $collectionType; + my $versionString = ''; + + if (exists $self->{version}) + { + $versionString = ' version="'.$self->{version}.'"'; + } + if (($self->{modelLoader}->{model}->isInline) + || ($self->{modelLoader}->{model}->isPersonal && $self->{standAlone})) + { + $xmlModel = $self->{modelLoader}->{model}->toString('collectionInlineDescription', 1); + $xmlPreferences = $self->{modelLoader}->{model}->{preferences}->toXmlString; + $collectionType = 'inline'; + } + else + { + $collectionType = $self->{modelLoader}->{model}->getName; + $xmlModel = $self->{modelLoader}->{model}->toStringAddedFields('userCollection'); + } + my $information = ' +'; + $information .= " <$_>".GCUtils::encodeEntities($info->{$_})."\n" + foreach (sort keys %{$info}); + $information .= ' '; + + # Change this to 1 to save history. Not fully functional yet + # Because we don't remove item that are no more present in data. + my $withHistory = 0; + my $histories; + if ($withHistory) + { + $histories = ' +'; + foreach (keys %{$self->{histories}}) + { + $histories .= " \n"; + foreach my $value(@{$self->{histories}->{$_}}) + { + if (ref($value) eq 'ARRAY') + { + $histories .= ' +'; + foreach my $entry(@$value) + { + next if $entry eq ''; + $entry =~ GCUtils::encodeEntities($entry); + $histories .= " $entry\n"; + } + $histories .= ' +'; + } + else + { + next if $value eq ''; + $histories .= ' '.GCUtils::encodeEntities($value)."\n"; + } + } + $histories .= ' +'; + } + $histories .= ' '; + } + + my $number = 0; + $number = scalar @$data; + + print $tmpFd ' + +',$information,' +',$xmlModel,' +',$xmlPreferences,' +',$histories,' +'; + my $i = 1; + foreach (@$data) + { + #Perform the transformation for each image value + foreach my $pic(@{$self->{modelLoader}->{model}->{managedImages}}) + { + $_->{$pic} + = $self->{modelLoader}->transformPicturePath($_->{$pic}, undef, $_, $pic); + } + + print $tmpFd ' {modelLoader}->{model}->{fieldsNames}}) + { + if (ref($_->{$field}) eq 'ARRAY') + { + push @complexFields, $field; + } + elsif ($self->{modelLoader}->{model}->{fieldsInfo}->{$field}->{type} + eq 'long text') + { + push @longFields, $field; + } + else + { + (my $data = $_->{$field}) =~ s/([$toBeReplaced])/$xmlConv{$1}/go; + if (($self->{modelLoader}->{model}->{fieldsInfo}->{$field}->{type} eq 'date') + && ($data eq 'current') + && (!$keepCurrentValueForDate)) + { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + $data = sprintf('%02d/%02d/%4d', $mday, $mon+1, 1900+$year); + } + print $tmpFd ' ', $field, '="', $data, '" +'; + } + } + print $tmpFd ' > +'; + foreach my $field(@longFields) + { + #(my $data = $_->{$field}) =~ s/&/&/g; + #$data =~ s//>/g; + #$data =~ s/"/"/g; + (my $data = $_->{$field}) =~ s/([$toBeReplaced])/$xmlConv{$1}/go; + #" + print $tmpFd ' <', $field, '>', $data, ' +'; + } + foreach my $field(@complexFields) + { + print $tmpFd ' <', $field, '> +', listToXml($_->{$field}), ' +'; + } + + print $tmpFd ' +'; + $splash->setProgressForItemsDisplay($i) if $splash; + + $self->{modelLoader}->restoreInfo($_) + if $self->{wantRestore}; + + $i++; + } + print $tmpFd ' +'; + close $tmpFd; + + # Now everything is OK, we move the temporary file over the correct one + if (!move($tmpFile, $self->{file})) + { + my @error = ('SaveError', $!); + return {error => \@error}; + } + + return {error => undef}; + } +} + +1; diff --git a/lib/gcstar/GCBackend/GCBackendXmlParser.pm b/lib/gcstar/GCBackend/GCBackendXmlParser.pm new file mode 100644 index 0000000..091823b --- /dev/null +++ b/lib/gcstar/GCBackend/GCBackendXmlParser.pm @@ -0,0 +1,491 @@ +package GCBackend::GCBackendXmlParser; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use utf8; +use filetest 'access'; +use GCBackend::GCBackendXmlCommon; + +{ + package GCBackend::GCBeXmlParser; + + use File::Temp qw/ tempfile /; + use File::Copy; + + use base 'GCBackend::GCBeXmlBase'; + + my $globalInstance; + my $globalSplash; + my $globalModelLoader; + #my @data; + #my %information; + #my %histories; + my $maxId; + my $savedMaxId; + my $historyInline; + + sub load + { + my ($self, $splash) = @_; + + if (! -r $self->{file}) + { + my @error = ('OpenError', ''); + return {error => \@error}; + } + + $self->{data} = []; + $self->{information} = {}; + $self->{histories} = (); + $maxId = 0; + $savedMaxId = 0; + + $globalInstance = $self; + $globalSplash = $splash; + $globalModelLoader = $self->{modelLoader}; + + my $parser = XML::Parser->new(Handlers => { + Init => \&StartDocument, + Final => \&EndDocument, + Start => \&StartTag, + End => \&EndTag, + Char => \&Text, + }); + # We have to preload the model into cache because XML::Parser is not + # re-entrant. Then when we begin parsing, we cannot parse the model + $self->prepareModel($self->{file}); + my $error = undef; + while (1) + { + eval { + $parser->parsefile($self->{file}); + }; + if ($@) + { + my $errorDesc = $@; + + # Here we will fix the collection if an invalid character was found by trying to remove it. + # There should be room for optimisation here + + if ($errorDesc =~ /not\s*well-formed\s*\(invalid\s*token\)\s*.*?byte\s*(\d+)/) + { + my $charPosition = $1; + # We would have failed before if it cannot be opened, so we don't check that. + open COL, $self->{file}; + seek COL, $charPosition, 0; + my $badChar; + read COL, $badChar, 1; + seek COL, 0, 0; + (my ($newCol, $tmpFile)) = tempfile(); + while () + { + s/$badChar//g; + print $newCol $_; + } + close $newCol; + close COL; + move($tmpFile, $self->{file}); + } + else + { + $errorDesc =~ s/^\n*//; + my @errorArray = ('OpenFormatError', $errorDesc); + $error = \@errorArray; + last; + } + } + else + { + last; + } + } + + # TODO : Compare performances with and without the compact below because the duplicates are checked + # also when adding to the graphical components + # Compact histories. We didn't filtered previously for performances issues + #GCUtils::compactHistories(\%histories); + + $self->{information}->{maxId} = $maxId + if ! exists $self->{information}->{maxId}; + + # gotHistory: + # 0: Nothing done + # 1: Returning history + # 2: Already initialized + + return { + error => $error, + data => $self->{data}, + information => $self->{information}, + histories => \$self->{histories}, + gotHistory => (1 + ($historyInline ? 0 : 1)), # We always have an initalized history with this BE. + }; + } + + # Parser routines + + # Some globals to speed up things + my $inCol; + my $inLine; + my $currentTag; + my $currentCol; + my $currentCount; + my $currentIsList; + my $isItem; + my $isInfo; + my $newItem; + my $modCap; + my $prefCap; + my $anyCap; + my $isInline; + my $inlineModel; + my $inlinePreferences; + + my $inHistories; + my $historyField; + # history type : + # 1 : Single list + # 2 : Multiple list + my $historyType; + my $historyCap; + + sub StartDocument + { + $isItem = 0; + $inLine = 0; + $inCol = 0; + $currentCol = ''; + $currentCount = 0; + $modCap = 0; + $prefCap = 0; + $anyCap = 0; + $inlineModel = ''; + $inlinePreferences = ''; + +# SAVED HISTORIES DEACTIVATED +# $inHistories = 0; +# $historyField = ''; +# $historyCap = 0; + $historyInline = 0; + } + + sub EndDocument + { + if (($inlineModel) && ($isInline)) + { + $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, + inlinePreferences => $inlinePreferences}); + } + } + + sub StartTag + { + #my ($expat, $tag, %attrs) = @_; + if ($isItem) + { + if ($inLine) + { + #Only a col could start in a line + $inCol = 1; + } + elsif ($_[1] eq 'line') + { + $inLine = 1; + $currentIsList = 1; + $newItem->{$currentTag} = [] if (ref($newItem->{$currentTag}) ne 'ARRAY'); + push @{$newItem->{$currentTag}}, []; + } + else + { + $currentIsList = 0; + $currentTag = $_[1]; + } + } + elsif ($isInfo) + { + $currentTag = $_[1]; + $savedMaxId = 1 if $currentTag eq 'maxId'; + } + else + { + my ($expat, $tag, %attrs) = @_; + if ($modCap) + { + $tag =~ s/^user(.)/\L$1\E/;; + $inlineModel .= "<$tag".GCBackend::GCBeXmlBase::hashToXMLString(%attrs).'>'; + } + elsif ($prefCap) + { + $inlinePreferences .= "<$tag".GCBackend::GCBeXmlBase::hashToXMLString(%attrs).'>'; + } + elsif ($tag eq 'item') + { + $newItem = \%attrs; + $isItem = 1; + } + elsif ($tag eq 'information') + { + $isInfo = 1; + } + elsif (($tag eq 'collectionInlineDescription') || ($tag eq 'userCollection')) + { + $modCap = 1; + $anyCap = 1; + $inlineModel = '\n"; + } + elsif ($tag eq 'collectionInlinePreferences') + { + $prefCap = 1; + $anyCap = 1; + $inlinePreferences = '\n"; + } + elsif ($tag eq 'collection') + { + $globalSplash->setItemsTotal($attrs{items}) + if $globalSplash; + if ($attrs{type} eq 'inline') + { + $isInline = 0; + } + else + { + if (! $globalModelLoader->setCurrentModel($attrs{type})) + { + die $globalModelLoader->{lang}->{ErrorModelNotFound}.$attrs{type} + ."\n\n" + .$globalModelLoader->getUserModelsDirError."\n"; + } + } + } +# SAVED HISTORIES DEACTIVATED +# elsif ($tag eq 'histories') +# { +# $inHistories = 1; +# $historyInline = 1; +# } +# elsif ($inHistories) +# { +# if ($tag eq 'history') +# { +# $historyField = $attrs{name}; +# # Default is single +# $historyType = 1; +# } +# elsif ($tag eq 'values') +# { +# push @{$globalInstance->{histories}->{$historyField}}, []; +# $historyType = 2; +# } +# elsif ($tag eq 'value') +# { +# if ($historyType == 1) +# { +# push @{$globalInstance->{histories}->{$historyField}}, ''; +# } +# else +# { +# push @{$globalInstance->{histories}->{$historyField}->[-1]}, ''; +# } +# $historyCap = 1; +# } +# } + } + } + + sub EndTag + { + if ($anyCap) + { + if ($modCap) + { + if (($_[1] eq 'collectionInlineDescription') || ($_[1] eq 'userCollection')) + { + $anyCap = $prefCap; + $modCap = 0; + $inlineModel .= ''; + if ($inlinePreferences) + { + $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, + inlinePreferences => $inlinePreferences}); + $inlineModel = undef; + } + elsif($_[1] eq 'userCollection') + { + $globalModelLoader->addFieldsToDefaultModel($inlineModel); + $inlineModel = undef; + } + + } + else + { + (my $tag = $_[1]) =~ s/^user(.)/\L$1\E/; + $inlineModel .= "\n"; + } + return; + } + else + { + if ($_[1] eq 'collectionInlinePreferences') + { + $anyCap = $modCap; + $prefCap = 0; + $inlinePreferences .= ''; + if ($inlineModel) + { + $globalModelLoader->setCurrentModelFromInline({inlineModel => $inlineModel, + inlinePreferences => $inlinePreferences}); + $inlineModel = ''; + } + } + else + { + $inlinePreferences .= '\n"; + } + return; + } + } + + if ($_[1] eq 'item') + { + push @{$globalInstance->{data}}, $newItem; + $currentCount++; +# SAVED HISTORIES DEACTIVATED +# if (!$historyInline) +# { + #foreach (@{$globalModelLoader->{model}->{fieldsHistory}}) + #{ + # push @{$globalInstance->{histories}->{$_}}, $newItem->{$_}; + #} + if ($globalModelLoader->{panel}) + { + foreach (@{$globalModelLoader->{model}->{fieldsHistory}}) + { + $globalModelLoader->{panel}->{$_}->addHistory($newItem->{$_}, 1); + } + } +# } + foreach (@{$globalModelLoader->{model}->{fieldsNotNull}}) + { + $newItem->{$_} = $globalModelLoader->{model}->{fieldsInfo}->{$_}->{init} if ! $newItem->{$_}; + } + + if (!$savedMaxId) + { + my $id = $newItem->{$globalModelLoader->{model}->{commonFields}->{id}}; + $maxId = $id + if $id > $maxId; + } + + $globalSplash->setProgressForItemsLoad($currentCount) + if $globalSplash; + + $isItem = 0; + } + elsif ($_[1] eq 'information') + { + $isInfo = 0 if !$isItem; + } + elsif ($inCol) + { + # We are closing a col as it could not have tags inside + push @{$newItem->{$currentTag}->[-1]}, $currentCol; + $currentCol = ''; + $inCol = 0; + } +# SAVED HISTORIES DEACTIVATED +# elsif ($inHistories) +# { +# $inHistories = 0 if $_[1] eq 'histories'; +# $historyField = '' if $_[1] eq 'history'; +# $historyCap = 0 if $_[1] eq 'value'; +# +# } + else + { + # The only tag that could prevent us from closing a line is col, but it has + # already been managed + if ($inLine) + { + $inLine = 0; + } + else + { + $currentTag = ''; + } + } + } + + sub Text + { + if ($isItem) + { + if ((! $currentTag) + || $inLine + || $currentIsList + || ((!$newItem->{$currentTag}) && ($_[1] =~ /^\s*$/oms))) + { + if ($inCol) + { + return if $_[1] =~ /^\s*$/oms; + $currentCol .= $_[1]; + } + } + else + { + $newItem->{$currentTag} .= $_[1]; + } + } + elsif ($isInfo) + { + return if $_[1] =~ /^\s*$/oms; + $globalInstance->{information}->{$currentTag} .= $_[1]; + } + else + { + if ($modCap) + { + $inlineModel .= $_[1]; + } + elsif ($prefCap) + { + $inlinePreferences .= $_[1]; + } +# elsif ($historyCap) +# { +# if ($historyType == 1) +# { +# $globalInstance->{histories}->{$historyField}->[-1] .= $_[1]; +# } +# else +# { +# $globalInstance->{histories}->{$historyField}->[-1]->[-1] .= $_[1]; +# } +# } + } + } + +} + + +1; diff --git a/lib/gcstar/GCBookmarks.pm b/lib/gcstar/GCBookmarks.pm new file mode 100644 index 0000000..e771113 --- /dev/null +++ b/lib/gcstar/GCBookmarks.pm @@ -0,0 +1,729 @@ +package GCBookmarks; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### +use utf8; +use Gtk2; + +our $bookmarksFile = 'GCbookmarks.conf'; + +use strict; +{ + package GCBookmarksLoader; + + sub new + { + my ($proto, $parent, $menu) = @_; + my $class = ref($proto) || $proto; + my $self = {parent => $parent, + menu => $menu}; + bless ($self, $class); + $self->load; + return $self; + } + + sub load + { + my $self = shift; + + open BOOKMARKS, $ENV{GCS_CONFIG_HOME}."/$bookmarksFile"; + my $xmlString = do {local $/; }; + close BOOKMARKS; + $self->{menu}->clearBookmarks; + my $bookmarks; + if ($xmlString) + { + my $xs = XML::Simple->new; + $bookmarks = $xs->XMLin($xmlString, + ForceArray => ['file', 'dir'], + KeyAttr => { + 'dir' => 'id' + }); + $self->{menu}->setBookmarks($bookmarks); + } + $self->{bookmarks} = $bookmarks; + } + + sub save + { + my ($self, $bookmarks) = @_; + + return if !$bookmarks->{file}; + $self->{bookmarks} = $bookmarks; + my $xs = XML::Simple->new; + my $xmlString = $xs->XMLout($bookmarks, + XMLDecl => '', + RootName => 'bookmarks'); + open BOOKMARKS, '>'.$ENV{GCS_CONFIG_HOME}."/$bookmarksFile"; + binmode(BOOKMARKS, ':utf8'); + print BOOKMARKS $xmlString; + close BOOKMARKS; + $self->{menu}->clearBookmarks; + $self->{menu}->setBookmarks($bookmarks); + } +} + +{ + package GCBookmarksFolders; + use base 'Gtk2::TreeView'; + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new(); + $self->{class} = $class; + $self->{model} = new Gtk2::TreeStore('Glib::String', 'Glib::Int'); + $self->set_model($self->{model}); + $self->{parent} = $parent; + my $column = Gtk2::TreeViewColumn->new_with_attributes($parent->{lang}->{BookmarksFolder}, Gtk2::CellRendererText->new, + 'text' => 0, 'editable' => 1); + $self->append_column($column); + + $self->signal_connect (cursor_changed => sub { + my ($sl, $path, $column) = @_; + my $iter = $sl->get_selection->get_selected; + $self->{currentIdx} = ($self->{model}->get($iter))[1]; + $self->{parent}->setBookmarksList($self->{bookmarks}->[$self->{currentIdx}]); + }); + + my $targetEntryReorder = { + target => 'text/plain', + flags => ['same-widget'], + info => 0, + }; + my $targetEntryMove = { + target => 'text/plain', + flags => ['same-app'], + info => 1, + }; + + $self->enable_model_drag_source('button1-mask','move', $targetEntryReorder, $targetEntryMove); + $self->enable_model_drag_dest('move', $targetEntryReorder, $targetEntryMove); + $self->signal_connect_after('drag_data_received' => \&dropHandler, $self); + + $self->signal_connect('key-press-event' => sub { + my ($widget, $event) = @_; + my $key = Gtk2::Gdk->keyval_name($event->keyval); + if ($key eq 'Delete') + { + $self->removeFolder; + return 1; + } + return 0; + }); + + bless($self, $class); + return $self; + } + + sub dropHandler + { + my ($treeview, $context, $widget_x, $widget_y, $data, $info,$time, $self) = @_; + my $source = $context->get_source_widget; + my ($targetPath, $targetPos) = $treeview->get_dest_row_at_pos($widget_x, $widget_y); + return if !$targetPath; + my $targetIter = $self->get_model->get_iter($targetPath); + my $origIter = $source->get_selection->get_selected; + if ($source == $self) + { + if (($targetPath->to_string eq $self->get_model->get_path($origIter)->to_string) + || ($self->{model}->is_ancestor($origIter, $targetIter))) + { + $context->finish(1,0,$time); + return; + } + my $newIter; + my $parent; + my $pos; + my $ref; + if ($targetPos =~ /^into/) + { + $parent = $targetIter; + $pos = 0; + $ref = 0; + } + else + { + $parent = $self->{model}->iter_parent($targetIter); + $pos = ($targetPos eq 'before') ? 1 : 0; + $ref = $targetIter; + } + $self->copyIter($origIter, $parent, $pos, $ref); + } + else + { + my @origData = $source->get_model->get_value($origIter); + my $bookmark = {'name' => $origData[0], 'path' => $origData[1]}; + my $idx = ($self->{model}->get($targetIter))[1]; + push @{$self->{bookmarks}->[$idx]}, $bookmark; + my @bookmarks; + my $i = -1; + my $selected = ($source->get_selected_indices)[0]; + foreach (@{$source->{data}}) + { + $i++; + next if $i == $selected; + push @bookmarks, {name => $_->[0], path => $_->[1]}; + } + $self->{bookmarks}->[$self->{currentIdx}] = \@bookmarks; + } + $context->finish(1,1,$time); + } + + sub copyIter + { + my ($self, $iter, $parent, $pos, $ref) = @_; + + my @origData; + my $i = 0; + foreach ($self->get_model->get_value($iter)) + { + push @origData, $i, $_; + $i++; + } + my $newIter; + if ($ref) + { + if ($pos) + { + $newIter = $self->{model}->insert_before($parent, + $ref); + } + else + { + $newIter = $self->{model}->insert_after($parent, + $ref); + } + } + else + { + $newIter = $self->{model}->append($parent); + } + $self->{model}->set($newIter, @origData); + my $childIter = $self->{model}->iter_children($iter); + while ($childIter) + { + $self->copyIter($childIter, $newIter); + $childIter = $self->{model}->iter_next($childIter); + } + } + + sub setBookmarks + { + my ($self, $bookmarks) = @_; + $self->{bookmarks} = []; + $self->{bookmarkIdx} = 0; + $self->{currentIdx} = 0; + $self->{model}->clear; + $self->addBookmarksDir($bookmarks, undef); + $self->expand_row(Gtk2::TreePath->new_from_string('0'), 0); + $self->{parent}->setBookmarksList($self->{bookmarks}->[$self->{currentIdx}]); + } + + sub addBookmarksDir + { + my ($self, $dir, $parent) = @_; + my $name = $dir->{name}; + ($name = $self->{parent}->{lang}->{MenuBookmarks}) =~ s/_//g if !$parent; + my @data = (0 => $name, 1 => $self->{bookmarkIdx}); + $self->{bookmarks}->[$self->{bookmarkIdx}] = $dir->{file}; + $self->{bookmarkIdx}++; + my $newDir = $self->{model}->append($parent); + $self->{model}->set($newDir, @data); + foreach my $sub(@{$dir->{dir}}) + { + $self->addBookmarksDir($sub, $newDir); + } + } + + sub addBookmark + { + my ($self, $path, $name) = @_; + push(@{$self->{bookmarks}->[$self->{currentIdx}]}, {name => $name, path => $path}); + } + + sub addFolder + { + my ($self, $name) = @_; + my @data = (0 => $name, 1 => $self->{bookmarkIdx}); + $self->{bookmarks}->[$self->{bookmarkIdx}] = []; + $self->{bookmarkIdx}++; + my $parent = $self->get_selection->get_selected; + $parent ||= $self->{model}->get_iter_first; + $self->{model}->set($self->{model}->append($parent), @data); + } + + sub removeFolder + { + my $self = shift; + my $iter = $self->get_selection->get_selected; + $self->{model}->remove($iter); + } + + sub getBookmarks + { + my $self = shift; + + my $result = {}; + $self->dumpTree($result, $self->{model}->get_iter_first, 1); + return $result; + } + + sub dumpTree + { + my ($self, $dir, $iter, $first) = @_; + my @data = $self->{model}->get($iter); + $dir->{name} = $data[0] if ! $first; + $dir->{file} = $self->{bookmarks}->[$data[1]]; + $dir->{dir} = []; + my $i = 0; + my $child; + while ($child = $self->{model}->iter_nth_child($iter, $i)) + { + $dir->{dir}->[$i] = {}; + $self->dumpTree($dir->{dir}->[$i], $child); + $i++; + } + } + + sub setBookmarksInCurrentFolder + { + my ($self, $bookmarksList) = @_; + + $self->{bookmarks}->[$self->{currentIdx}] = $bookmarksList; + } +} + +use GCDialogs; + +{ + package GCBookmarkNewFolderDialog; + use base 'GCModalDialog'; + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $parent->{lang}->{BookmarksNewFolder}, + 'gtk-add', + ); + bless($self, $class); + $self->{entry} = new GCShortText; + $self->{entry}->signal_connect('activate' => sub {$self->response('ok')} ); + my $hbox = new Gtk2::HBox(0,0); + $hbox->pack_start($self->{entry},1,1,$GCUtils::margin); + $self->vbox->pack_start($hbox,1,1,$GCUtils::margin); + return $self; + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + $self->{entry}->grab_focus; + my $code = $self->run; + $self->hide; + return ($code eq 'ok'); + } +} + +{ + package GCBookmarkPropertiesDialog; + use base 'GCModalDialog'; + + sub new + { + my ($proto, $parent, $title, $okLabel) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $title, + $okLabel, + ); + bless($self, $class); + + my $table = new Gtk2::Table(2, 2); + $table->set_row_spacings($GCUtils::halfMargin); + $table->set_col_spacings($GCUtils::margin); + $table->set_border_width($GCUtils::margin); + my $labelLabel = new GCLabel($parent->{lang}->{BookmarksLabel}); + $self->{label} = new GCShortText; + $self->{label}->signal_connect('activate' => sub {$self->response('ok')} ); + my $pathLabel = new GCLabel($parent->{lang}->{BookmarksPath}); + $self->{path} = new GCShortText; + $self->{path}->signal_connect('activate' => sub {$self->response('ok')} ); + $table->attach($labelLabel, 0, 1, 0, 1, 'fill', 'fill', 0, 0); + $table->attach($self->{label}, 1, 2, 0, 1, ['fill', 'expand'], 'fill', 0, 0); + $table->attach($pathLabel, 0, 1, 1, 2, 'fill', 'fill', 0, 0); + $table->attach($self->{path}, 1, 2, 1, 2, ['fill', 'expand'], 'fill', 0, 0); + $self->vbox->pack_start($table, 1, 1, 0); + $table->show_all; + return $self; + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + $self->{label}->grab_focus; + my $code = $self->run; + $self->hide; + return ($code eq 'ok'); + } +} + + +{ + package GCBookmarkAdderDialog; + use base 'GCModalDialog'; + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + (my $title = $parent->{lang}->{MenuBookmarksAdd}) =~ s/_//g; + my $self = $class->SUPER::new($parent, + $title, + 'gtk-add', + 0, + $parent->{lang}->{BookmarksNewFolder} => 'yes', + ); + + $self->{lang} = $parent->{lang}; + + my $table = new Gtk2::Table(2, 2); + $table->set_row_spacings($GCUtils::halfMargin); + $table->set_col_spacings($GCUtils::margin); + $table->set_border_width($GCUtils::margin); + my $labelLabel = new GCLabel($parent->{lang}->{BookmarksLabel}); + $self->{label} = new GCShortText; + my $pathLabel = new GCLabel($parent->{lang}->{BookmarksPath}); + $self->{path} = new GCShortText; + + $table->attach($labelLabel, 0, 1, 0, 1, 'fill', 'fill', 0, 0); + $table->attach($self->{label}, 1, 2, 0, 1, ['fill', 'expand'], 'fill', 0, 0); + $table->attach($pathLabel, 0, 1, 1, 2, 'fill', 'fill', 0, 0); + $table->attach($self->{path}, 1, 2, 1, 2, ['fill', 'expand'], 'fill', 0, 0); + + $self->{folders} = new GCBookmarksFolders($self); + my $scroller = new Gtk2::ScrolledWindow; + $scroller->set_policy ('automatic', 'automatic'); + $scroller->add($self->{folders}); + $table->attach($scroller, 0, 2, 2, 3, ['fill', 'expand'], ['fill', 'expand'], 0, 0); + + $self->vbox->pack_start($table, 1, 1, 0); + $self->set_default_size(400, 300); + + bless ($self, $class); + } + + sub setBookmark + { + my ($self, $path, $label) = @_; + $self->{path}->setValue($path); + $self->{label}->setValue($label); + } + + sub setBookmarksFolders + { + my ($self, $bookmarks) = @_; + $self->{folders}->setBookmarks($bookmarks); + + } + + sub setBookmarksList + { + } + + sub getBookmarks + { + my $self = shift; + return $self->{folders}->getBookmarks; + } + + sub addFolder + { + my $self = shift; + my $dialog = new GCBookmarkNewFolderDialog($self); + $self->{folders}->addFolder($dialog->{entry}->getValue) if $dialog->show; + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + my $response; + my $done = 0; + while (!$done) + { + $response = $self->run; + if ($response eq 'ok') + { + $self->{folders}->addBookmark($self->{path}->getValue, $self->{label}->getValue); + } + $self->addFolder if ($response eq 'yes'); + $done = 1 if ($response eq 'ok') || ($response eq 'cancel') || ($response eq 'delete-event'); + } + $self->hide; + return ($response eq 'ok'); + } +} + +{ + package GCBookmarksEditDialog; + use base 'GCModalDialog'; + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + (my $title = $parent->{lang}->{MenuBookmarksEdit}) =~ s/_//g; + my $self = $class->SUPER::new($parent, + $title, + 'gtk-save', + ); + bless ($self, $class); + + $self->{lang} = $parent->{lang}; + + $self->{folders} = new GCBookmarksFolders($self); + my $scroller1 = new Gtk2::ScrolledWindow; + $scroller1->set_policy ('automatic', 'automatic'); + $scroller1->set_shadow_type('etched-in'); + $scroller1->add($self->{folders}); + $self->{bookmarksList} = new Gtk2::SimpleList($parent->{lang}->{BookmarksBookmarks} => 'text', + 'Path' => 'text'); + $self->{blockAddSignal} = 0; + $self->{bookmarksList}->get_model->signal_connect('row-inserted' => sub { + return if $self->{blockAddSignal}; + $self->saveBookmarks; + }); + $self->{bookmarksList}->set_rules_hint(1); + $self->{bookmarksList}->get_column(1)->set_visible(0); + my $targetEntryMove = { + target => 'text/plain', + flags => ['same-app'], + info => 14, + }; + $self->{bookmarksList}->enable_model_drag_source('button1-mask','move', $targetEntryMove); + $self->{bookmarksList}->signal_connect('key-press-event' => sub { + my ($widget, $event) = @_; + my $key = Gtk2::Gdk->keyval_name($event->keyval); + if ($key eq 'Delete') + { + $self->deleteBookmark; + return 1; + } + return 0; + }); + + my $hboxFolders = new Gtk2::HBox(0,0); + my $vboxFolders = new Gtk2::VBox(0,0); + my $newFolder = new Gtk2::Button->new_from_stock('gtk-new'); + $newFolder->signal_connect('clicked' => sub { + $self->addFolder; + }); + my $removeFolder = new Gtk2::Button->new_from_stock('gtk-delete'); + $removeFolder->signal_connect('clicked' => sub { + $self->{folders}->removeFolder; + }); + $vboxFolders->pack_start($newFolder,0,0,$GCUtils::halfMargin); + $vboxFolders->pack_start($removeFolder,0,0,$GCUtils::halfMargin); + $hboxFolders->pack_start($vboxFolders,0,0,$GCUtils::margin); + $hboxFolders->pack_start($scroller1,1,1,0); + + + my $scroller2 = new Gtk2::ScrolledWindow; + $scroller2->set_policy ('automatic', 'automatic'); + $scroller2->set_shadow_type('etched-in'); + $scroller2->add($self->{bookmarksList}); + + my $hboxList = new Gtk2::HBox(0,0); + $hboxList->pack_start($scroller2,1,1,0); + my $vboxList = new Gtk2::VBox(0,0); + $hboxList->pack_start($vboxList,0,0,$GCUtils::margin); + + my $up = new Gtk2::Button->new_from_stock('gtk-go-up'); + $up->signal_connect('clicked' => sub { + $self->moveDownUp(-1); + }); + my $down = new Gtk2::Button->new_from_stock('gtk-go-down'); + $down->signal_connect('clicked' => sub { + $self->moveDownUp(1); + }); + my $edit = new Gtk2::Button->new_from_stock('gtk-edit'); + $edit->signal_connect('clicked' => sub { + $self->edit; + }); + my $new = new Gtk2::Button->new_from_stock('gtk-new'); + $new->signal_connect('clicked' => sub { + $self->newBookmark; + }); + my $delete = new Gtk2::Button->new_from_stock('gtk-delete'); + $delete->signal_connect('clicked' => sub { + $self->deleteBookmark; + }); + $vboxList->pack_start($up,0,0,$GCUtils::halfMargin); + $vboxList->pack_start($down,0,0,$GCUtils::halfMargin); + $vboxList->pack_start($edit,0,0,$GCUtils::halfMargin); + $vboxList->pack_start($new,0,0,$GCUtils::halfMargin); + $vboxList->pack_start($delete,0,0,$GCUtils::halfMargin); + + my $paned = new Gtk2::HPaned; + $paned->pack1($hboxFolders, 1, 0); + $paned->pack2($hboxList, 1, 0); + + $self->vbox->pack_start($paned, 1, 1, $GCUtils::margin); + $self->set_default_size(600, 400); + return $self; + } + + sub edit + { + my $self = shift; + my $currentId = ($self->{bookmarksList}->get_selected_indices)[0]; + return if (!defined($currentId)) || ($currentId < 0); + my ($label, $path) = @{$self->{bookmarksList}->{data}->[$currentId]}; + (my $title = Gtk2::Stock->lookup('gtk-edit')->{label}) =~ s/_//; + my $dialog = new GCBookmarkPropertiesDialog($self, $title); + $dialog->{label}->setValue($label); + $dialog->{path}->setValue($path); + if ($dialog->show) + { + $self->{bookmarksList}->{data}->[$currentId] = [$dialog->{label}->getValue, + $dialog->{path}->getValue]; + $self->saveBookmarks; + } + $dialog->destroy; + } + + sub newBookmark + { + my $self = shift; + $self->{blockAddSignal} = 1; + (my $title = Gtk2::Stock->lookup('gtk-new')->{label}) =~ s/_//; + my $dialog = new GCBookmarkPropertiesDialog($self, $title, 'gtk-new'); + $dialog->{label}->clear(); + $dialog->{path}->clear(); + if ($dialog->show) + { + push @{$self->{bookmarksList}->{data}}, [$dialog->{label}->getValue, $dialog->{path}->getValue]; + $self->saveBookmarks; + $self->{bookmarksList}->select($#{$self->{bookmarksList}->{data}}); + } + $dialog->destroy; + $self->{blockAddSignal} = 0; + } + + sub deleteBookmark + { + my $self = shift; + my $currentId = ($self->{bookmarksList}->get_selected_indices)[0]; + return if (!defined($currentId)) || ($currentId < 0); + splice (@{$self->{bookmarksList}->{data}}, $currentId, 1); + $currentId--; + $currentId = 0 if $currentId < 0; + $self->saveBookmarks; + $self->{bookmarksList}->select($currentId); + } + + sub moveDownUp + { + my ($self, $dir) = @_; + $self->{blockAddSignal} = 1; + my $currentId = ($self->{bookmarksList}->get_selected_indices)[0]; + my $newId = $currentId + $dir; + return if ($newId < 0) || ($newId >= scalar @{$self->{bookmarksList}->{data}}); + my @data; + foreach (@{$self->{bookmarksList}->{data}}) + { + push @data, [$_->[0], $_->[1]]; + } + ($data[$currentId], $data[$newId]) = ($data[$newId], $data[$currentId]); + @{$self->{bookmarksList}->{data}} = @data; + $self->{bookmarksList}->select($newId); + $self->saveBookmarks; + $self->{blockAddSignal} = 0; + } + + sub addFolder + { + my $self = shift; + my $dialog = new GCBookmarkNewFolderDialog($self); + $self->{folders}->addFolder($dialog->{entry}->getValue) if $dialog->show; + } + + sub setBookmarksFolders + { + my ($self, $bookmarks) = @_; + + $self->{folders}->setBookmarks($bookmarks); + + } + + sub setBookmarksList + { + my ($self, $list) = @_; + $self->{blockAddSignal} = 1; + @{$self->{bookmarksList}->{data}} = (); + foreach (@$list) + { + push @{$self->{bookmarksList}->{data}}, [$_->{name}, $_->{path}]; + } + $self->{blockAddSignal} = 0; + } + + sub getBookmarks + { + my $self = shift; + return $self->{folders}->getBookmarks; + } + + sub saveBookmarks + { + my $self = shift; + my @bookmarks; + foreach (@{$self->{bookmarksList}->{data}}) + { + push @bookmarks, {name => $_->[0], path => $_->[1]}; + } + $self->{folders}->setBookmarksInCurrentFolder(\@bookmarks); + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + my $done = 0; + my $response = $self->run; + $self->hide; + return ($response eq 'ok'); + } +} + +1; diff --git a/lib/gcstar/GCBorrowings.pm b/lib/gcstar/GCBorrowings.pm new file mode 100644 index 0000000..d93c444 --- /dev/null +++ b/lib/gcstar/GCBorrowings.pm @@ -0,0 +1,662 @@ +package GCBorrowings; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCDialogs; +use GCUtils; + +{ + package GCImportBorrowersDialog; + use base 'GCModalDialog'; + + use XML::Simple; + + sub importClaws + { + my ($self, $file) = @_; + my @result; + open XML, $file; + my $xmlString = do {local $/; }; + close XML; + my $xs = XML::Simple->new; + my $addressBook = $xs->XMLin($xmlString, + ForceArray => ['address', 'person'] + ); + foreach (@{$addressBook->{person}}) + { + push @result, [$_->{cn}, $_->{'address-list'}->{address}->[0]->{email}]; + } + return \@result; + } + + sub importLdif + { + my ($self, $file) = @_; + my @result; + open DATA, $file; + my %current; + while () + { + if (/^dn/) + { + push @result, [$current{name}, $current{email}] if %current; + %current = {}; + } + $current{name} = $1 if (/^cn:\s*(.*)$/); + $current{email} = $1 if (/^mail:\s*(.*)$/); + } + close DATA; + push @result, [$current{name}, $current{email}] if %current; + return \@result; + } + + sub importVcard + { + my ($self, $file) = @_; + my @result; + open DATA, $file; + my %current; + while () + { + push @result, [$current{name}, $current{email}] if /^END:VCARD/i; + $current{name} = $1 if (/^FN:(.*)$/i); + $current{email} = $1 if (/^EMAIL;INTERNET:(.*)$/); + } + close DATA; + return \@result; + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + $self->set_position('center'); + my $done = 0; + my $code; + while (!$done) + { + $code = $self->run; + if ($code ne 'ok') + { + $done = 1; + } + else + { + my $type = $self->{type}->getValue; + my $file = $self->{file}->getValue; + if (!$file) + { + my $dialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $self->{parent}->{lang}->{ImportExportFileEmpty}); + + $dialog->set_position('center-on-parent'); + $dialog->run(); + $dialog->destroy; + next; + } + if ($type eq 'claws') + { + $self->{borrowers} = $self->importClaws($file); + } + elsif ($type eq 'ldif') + { + $self->{borrowers} = $self->importLdif($file); + } + elsif ($type eq 'vcard') + { + $self->{borrowers} = $self->importVcard($file); + } + $done = 1; + } + } + $self->hide; + return ($code eq 'ok'); + } + + sub getBorrowers + { + my $self = shift; + return $self->{borrowers}; + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $parent->{lang}->{BorrowersImportTitle}, + 'gtk-convert' + ); + bless ($self, $class); + $self->{parent} = $parent; + $self->{lang} = $parent->{lang}; + + my $table = new Gtk2::Table(2,2,0); + $table->set_row_spacings($GCUtils::halfMargin); + $table->set_col_spacings($GCUtils::halfMargin); + $table->set_border_width($GCUtils::margin); + + my $typeLabel = new GCLabel($parent->{lang}->{BorrowersImportType}); + $self->{type} = new GCMenuList; + $self->{type}->setValues([ + {value => 'ldif', displayed => 'LDIF'}, + {value => 'claws', displayed => 'Claws Mail'}, + {value => 'vcard', displayed => 'VCARD'}, + ]); + my $fileLabel = new GCLabel($parent->{lang}->{BorrowersImportFile}); + $self->{file} = new GCFile($self); + + $table->attach($typeLabel, 0, 1, 0, 1, 'fill', 'fill', 0, 0); + $table->attach($self->{type}, 1, 2, 0, 1, ['expand', 'fill'], 'fill', 0, 0); + $table->attach($fileLabel, 0, 1, 1, 2, 'fill', 'fill', 0, 0); + $table->attach($self->{file}, 1, 2, 1, 2, ['expand', 'fill'], 'fill', 0, 0); + + $self->vbox->pack_start($table, 1, 1, 0); + + return $self; + } +} + +{ + package GCBorrowersDialog; + use base 'GCModalDialog'; + + sub initValues + { + use locale; + + my $self = shift; + my $keepPrevious = shift; + + my @borrowers; + my @emails; + + if ($keepPrevious) + { + foreach my $line(@{$self->{people}->{data}}) + { + push @borrowers, $line->[0]; + push @emails, $line->[1]; + } + } + else + { + @borrowers = split m/\|/, $self->{options}->borrowers; + @emails = split m/\|/, $self->{options}->emails; + } + + @{$self->{people}->{data}} = (); + my %directory; + + for (my $i = 0; $i < scalar(@borrowers); $i++) + { + $directory{$borrowers[$i]} = $emails[$i]; + } + + my @keys = sort keys %directory; + @keys = reverse @keys if $self->{reverse}; + foreach (@keys) + { + my @infos = [$_, $directory{$_}]; + push @{$self->{people}->{data}}, @infos; + } + $self->{people}->select(0); + + (my $template = $self->{options}->template) =~ s|
|\n|g; + $self->{mailTemplate}->setValue($template); + + $self->{subject}->set_text($self->{options}->subject); + } + + sub saveValues + { + my $self = shift; + + my $borrowers = ''; + my $emails = ''; + foreach (@{$self->{people}->{data}}) + { + $borrowers .= $_->[0].'|'; + $emails .= $_->[1].'|'; + } + $borrowers =~ s/.$//; + $emails =~ s/.$//; + $self->{options}->borrowers($borrowers); + $self->{options}->emails($emails); + + (my $template = $self->{mailTemplate}->getValue) =~ s/\n//g; + $self->{options}->template($template); + + $self->{options}->subject($self->{subject}->get_text); + + $self->{options}->save; + } + + sub show + { + my $self = shift; + + $self->initValues; + + $self->SUPER::show(); + $self->show_all; + + if ($self->run eq 'ok') + { + $self->saveValues; + } + $self->hide; + } + + sub importBorrowers + { + my $self = shift; + + $self->{importDialog} = new GCImportBorrowersDialog($self) + if ! $self->{importDialog}; + + if ($self->{importDialog}->show) + { + unshift @{$self->{people}->{data}}, @{$self->{importDialog}->getBorrowers}; + } + } + + sub removeCurrent + { + my $self = shift; + my @idx = $self->{people}->get_selected_indices; + + if ($^O =~ /win32/i) + { + my @newData; + my $i = 0; + foreach (@{$self->{people}->{data}}) + { + push @newData, [$_->[0], $_->[1]] if $i != $idx[0]; + $i++; + } + @{$self->{people}->{data}} = @newData; + } + else + { + splice @{$self->{people}->{data}}, $idx[0], 1; + } + } + + sub add + { + my $self = shift; + + my $dialog = new Gtk2::Dialog($self->{parent}->{lang}->{BorrowersAdd}, + $self, + [qw/modal destroy-with-parent/], + @GCDialogs::okCancelButtons + ); + + my $table = new Gtk2::Table(2,2,0); + + my $labelName = new Gtk2::Label($self->{parent}->{lang}->{BorrowersName}); + $table->attach($labelName, 0, 1, 0, 1, 'expand', 'fill', 5, 5); + my $name = new Gtk2::Entry; + $name->signal_connect('activate' => sub {$dialog->response('ok')}); + $table->attach($name, 1, 2, 0, 1, 'expand', 'fill', 5, 5); + + my $labelEmail = new Gtk2::Label($self->{parent}->{lang}->{BorrowersEmail}); + $table->attach($labelEmail, 0, 1, 1, 2, 'expand', 'fill', 5, 5); + my $email = new Gtk2::Entry; + $email->signal_connect('activate' => sub {$dialog->response('ok')}); + $table->attach($email, 1, 2, 1, 2, 'expand', 'fill', 5, 5); + + $dialog->vbox->pack_start($table,1,1,0); + $dialog->vbox->show_all; + + if ($dialog->run eq 'ok') + { + unshift @{$self->{people}->{data}}, [$name->get_text, $email->get_text]; + } + + $dialog->destroy; + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $parent->{lang}->{BorrowersTitle}, + ); + + bless ($self, $class); + + #$self->set_modal(1); + $self->set_position('center'); + $self->set_default_size(400,400); + + $self->{reverse} = 0; + + $self->{parent} = $parent; + $self->{lang} = $parent->{lang}; + $self->{options} = $parent->{options}; + + my $borrowersFrame = new GCGroup($self->{parent}->{lang}->{BorrowersList}); + my $hbox = new Gtk2::HBox(0,0); + + $self->{people} = new Gtk2::SimpleList($parent->{lang}->{BorrowersName} => "text", + $parent->{lang}->{BorrowersEmail} => "text"); + $self->{people}->set_column_editable(1, 1); + $self->{people}->set_rules_hint(1); + + $self->{people}->get_column(0)->set_sort_column_id(0); + $self->{people}->get_model->set_sort_column_id(0, 'ascending'); + + for my $i (0..1) + { + $self->{people}->get_column($i)->set_resizable(1); + } + $self->{order} = 1; + $self->{sort} = -1; + + my $scrollPanelList = new Gtk2::ScrolledWindow; + $scrollPanelList->set_policy ('never', 'automatic'); + $scrollPanelList->set_shadow_type('etched-in'); + $scrollPanelList->set_border_width(0); + $scrollPanelList->add($self->{people}); + + my $vboxButtons = new Gtk2::VBox(0,0); + my $addButton = Gtk2::Button->new_from_stock('gtk-add'); + $addButton->signal_connect('clicked' => sub { + $self->add; + }); + my $removeButton = Gtk2::Button->new_from_stock('gtk-remove'); + $removeButton->signal_connect('clicked' => sub { + $self->removeCurrent; + }); + + my $importButton = Gtk2::Button->new_from_stock('gtk-convert'); + $importButton->signal_connect('clicked' => sub { + $self->importBorrowers; + }); + + #my $editButton = new Gtk2::Button($parent->{lang}->{BorrowersEdit}); + $vboxButtons->pack_start($addButton,0,0,$GCUtils::halfMargin); + $vboxButtons->pack_start($removeButton,0,0,$GCUtils::halfMargin); + $vboxButtons->pack_start($importButton,0,0,$GCUtils::halfMargin); + #$vboxButtons->pack_start($editButton,0,0,0); + + $hbox->pack_start($scrollPanelList,1,1,0); + $hbox->pack_start($vboxButtons,0,0,$GCUtils::margin); + $hbox->set_border_width(0); + $borrowersFrame->addWidget($hbox); + $self->vbox->pack_start($borrowersFrame,1,1,0); + + my $templateFrame = new GCGroup($self->{parent}->{lang}->{BorrowersTemplate}); + my $templateBox = new Gtk2::VBox(0,0); + $templateFrame->addWidget($templateBox); + + $self->{mailTemplate} = new GCLongText; + $self->{mailTemplate}->set_size_request(-1,80); + + my $hboxSubject = new Gtk2::HBox(0,0); + my $labelSubject = new Gtk2::Label($self->{parent}->{lang}->{BorrowersSubject}); + $self->{subject} = new Gtk2::Entry; + $hboxSubject->pack_start($labelSubject,0,0,0); + $hboxSubject->pack_start($self->{subject},0,0,$GCUtils::halfMargin); + + +# $templateBox->pack_start($labelTemplate,0,0,$GCUtils::halfMargin); + $templateBox->pack_start($hboxSubject,0,0,0); + $templateBox->pack_start($self->{mailTemplate},1,1,$GCUtils::halfMargin); + + my $label1 = new Gtk2::Label($self->{parent}->{lang}->{BorrowersNotice1}); + $label1->set_alignment(0,0); + my $label2 = new Gtk2::Label($self->{parent}->{lang}->{BorrowersNotice2}); + $label2->set_alignment(0,0); + my $label3 = new Gtk2::Label($self->{parent}->{lang}->{BorrowersNotice3}); + $label3->set_alignment(0,0); + $templateBox->pack_start($label1,0,0,0); + $templateBox->pack_start($label2,0,0,0); + $templateBox->pack_start($label3,0,0,0); + + $self->vbox->pack_start($templateFrame, 1, 1, 0); + + return $self; + } + +} + +{ + package GCBorrowedDialog; + use base "Gtk2::Dialog"; + + sub setList + { + my ($self, $data, $model) = @_; + + $self->setModel($model); + my $items = $data->getItemsListFiltered; + $self->{data} = $data; + + $self->{itemsList} = []; + $self->{listModel}->clear; + my ($listId, $dataId) = (-1, -1); + foreach (@{$items}) + { + $dataId++; + next if (!$_->{$self->{borrowerField}}) || ($_->{$self->{borrowerField}} eq 'none'); + $listId++; + my $borrower = $_->{$self->{borrowerField}}; + $borrower = $self->{parent}->{model}->getDisplayedText('PanelUnknown') + if $borrower eq 'unknown'; + my $lendDate = GCUtils::timeToStr($_->{$self->{lendDateField}}, + $self->{parent}->{options}->dateFormat); + push @{$self->{itemsList}}, { + $self->{titleField} => $_->{$self->{titleField}}, + $self->{borrowerField} => $borrower, + $self->{lendDateField} => $_->{$self->{lendDateField}} + }; + $self->{listModel}->set($self->{listModel}->append, + 0 => $_->{$self->{titleField}}, + 1 => $borrower, + 2 => $lendDate, + 3 => $listId, + 4 => $dataId); + } + + $self->{listView}->columns_autosize; + return if $listId == -1; + $self->{listView}->get_selection->select_iter($self->{listModel}->get_iter_first); + } + + sub show + { + my $self = shift; + + $self->SUPER::show(); + $self->show_all; + $self->run; + $self->hide; + } + + sub setModel + { + my ($self, $model) = @_; + $self->{titleField} = $model->{commonFields}->{title}; + $self->{borrowerField} = $model->{commonFields}->{borrower}->{name}; + $self->{lendDateField} = $model->{commonFields}->{borrower}->{date}; + $self->{historyField} = $model->{commonFields}->{borrower}->{history}; + + $self->{titleColumn}->set_title($model->getDisplayedItems); + } + + sub displayItem + { + my ($self, $idx) = @_; + $self->{data}->display($idx); + $self->{data}->select($idx); + } + + sub returnItem + { + my $self = shift; + my $current = $self->{data}->getCurrent; + my $iter = $self->{listView}->get_selection->get_selected; + my $idx = $self->{listModel}->get($iter, 4); + $self->displayItem($idx); + if ($self->{data}->{panel}->itemBack) + { + $self->{listModel}->remove($iter); + } + $self->displayItem($current); + return; + } + + sub showHistory + { + my $self = shift; + my $iter = $self->{listView}->get_selection->get_selected; + return if !$iter; + my $idx = $self->{listModel}->get($iter, 4); + $self->{history}->setValue( + $self->{data}->getValue($idx, $self->{historyField}) + ); + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent->{lang}->{BorrowedTitle}, + $parent, + [qw/modal destroy-with-parent/], + 'gtk-ok' => 'ok' + ); + + bless ($self, $class); + + $self->{parent} = $parent; + + $self->set_modal(1); + $self->set_position('center'); + $self->set_default_size(400,400); + + $self->{parent} = $parent; + $self->{options} = $parent->{options}; + + my $hbox = new Gtk2::HBox(0,0); + + $self->{listModel} = new Gtk2::ListStore('Glib::String', 'Glib::String', 'Glib::String', + 'Glib::Int', 'Glib::Int'); + $self->{listView} = Gtk2::TreeView->new_with_model($self->{listModel}); + $self->{listView}->set_rules_hint(1); + $self->{listView}->set_headers_clickable(1); + + my @columns; + push @columns, Gtk2::TreeViewColumn->new_with_attributes('', + Gtk2::CellRendererText->new, + 'text' => 0); + push @columns, Gtk2::TreeViewColumn->new_with_attributes($parent->{lang}->{PanelBorrower}, + Gtk2::CellRendererText->new, + 'text' => 1); + push @columns, Gtk2::TreeViewColumn->new_with_attributes($parent->{lang}->{BorrowedDate}, + Gtk2::CellRendererText->new, + 'text' => 2); + $self->{titleColumn} = $columns[0]; + for my $i (0..2) + { + $columns[$i]->set_resizable(1); + $columns[$i]->set_sort_column_id($i); + $columns[$i]->set_reorderable(1); + $self->{listView}->append_column($columns[$i]); + } + $self->{listModel}->set_sort_func(2, sub { + my ($model, $a, $b) = @_; + my ($day, $month, $year) = split m/\//, + $self->{itemsList}->[$model->get($a, 3)]->{$self->{lendDateField}}; + my $dateA = join "_", $year, $month, $day; + ($day, $month, $year) = split m/\//, + $self->{itemsList}->[$model->get($b, 3)]->{$self->{lendDateField}}; + my $dateB = join "_", $year, $month, $day; + return $dateA cmp $dateB; + + }); + + $self->{listView}->get_selection->signal_connect ('changed' => sub { + $self->showHistory; + }); + + my $scrollPanelList = new Gtk2::ScrolledWindow; + $scrollPanelList->set_policy ('never', 'automatic'); + $scrollPanelList->set_shadow_type('etched-in'); + $scrollPanelList->set_border_width($GCUtils::margin); + $scrollPanelList->add($self->{listView}); + + $self->{context} = new Gtk2::Menu; + $self->{returned} = Gtk2::MenuItem->new($parent->{lang}->{PanelReturned}); + $self->{returned}->signal_connect('activate', sub { + $self->returnItem; + }); + $self->{context}->append($self->{returned}); + $self->{display} = Gtk2::MenuItem->new($parent->{lang}->{BorrowedDisplayInPanel}); + $self->{display}->signal_connect('activate', sub { + my $iter = $self->{listView}->get_selection->get_selected; + my $idx = $self->{listModel}->get($iter, 4); + $self->displayItem($idx); + }); + $self->{context}->append($self->{display}); + $self->{context}->show_all; + + $self->{listView}->signal_connect('button_press_event' => sub { + my ($widget, $event) = @_; + return 0 if $event->button ne 3; + $self->{context}->popup(undef, undef, undef, undef, $event->button, $event->time); + return 0; + }); + + my $historyExpander = new GCExpander($parent->{lang}->{PanelHistory}); + $historyExpander->setValue($parent->{lang}->{PanelHistory}); + + my @labels = ($parent->{lang}->{PanelBorrower}, + $parent->{lang}->{PanelLendDate}, + $parent->{lang}->{PanelReturnDate}); + $self->{history} = new GCMultipleList($self, 3, \@labels, 0, 2); + + my $historyBox = new Gtk2::VBox(0,0); + $historyBox->set_border_width($GCUtils::margin); + $historyBox->pack_start($self->{history}, 1, 1, 0); + + $historyExpander->add($historyBox); + $historyExpander->show_all; + + $self->vbox->pack_start($scrollPanelList,1,1,0); + $self->vbox->pack_start($historyExpander,0,0, $GCUtils::halfMargin); + + return $self; + } +} + +1; diff --git a/lib/gcstar/GCCommandLine.pm b/lib/gcstar/GCCommandLine.pm new file mode 100644 index 0000000..17d2cf3 --- /dev/null +++ b/lib/gcstar/GCCommandLine.pm @@ -0,0 +1,395 @@ +package GCCommandLine; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +{ + package GCPseudoFrame; + + use File::Basename; + use File::Temp qw(tempdir); + use GCUtils; + + sub new + { + my ($proto, $parent, $options, $lang, $keepPictures) = @_; + my $class = ref($proto) || $proto; + my $self = { + options => $options, + imagePrefix => 'gcstar_', + lang => $lang, + parent => $parent, + agent => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.5) Gecko/20041111 Firefox/1.0', + }; + bless ($self, $class); + $self->{tmpImageDir} = tempdir(CLEANUP => ($keepPictures ? 0 : 1)); + return $self; + } + + sub setCurrentModel + { + my ($self, $model) = @_; + return $self->{parent}->setModel($model); + } + + sub transformTitle + { + my ($self, $title) = @_; + return $title; + } + + sub getImagesDir + { + my ($self, $suffix, $itemTitle, $imagesDir) = @_; + return GCFrame::getImagesDir(@_); + } + + sub getUniqueImageFileName + { + my ($self, $suffix, $itemTitle, $imagesDir) = @_; + return GCFrame::getUniqueImageFileName(@_); + } + + sub transformPicturePath + { + my ($self, $path, $file) = @_; + return GCFrame::transformPicturePath(@_); + } + + sub preloadModel + { + my ($self, $model) = @_; + # Preload the model into the factory cache + $self->{model} = $self->{modelsFactory}->getModel($model); + } + + sub AUTOLOAD + { + our $AUTOLOAD; + (my $name = $AUTOLOAD) =~ s/.*?::(.*)/$1/; + } +} + +{ + + package GCCommandExecution; + + use File::Temp qw/ :POSIX /; + use File::Basename; + use GCData; + use GCDisplay; + use GCExport; + use GCImport; + use GCPlugins; + use GCModel; + + sub new + { + my ($proto, $options, $model, $plugin, $import, $export, $output) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + bless ($self, $class); + + $self->{file} = $output; + $self->{useStdOut} = 0; + if (!$output) + { + (undef, $self->{file}) = tmpnam; + $self->{useStdOut} = 1; + } + + GCPlugins::loadAllPlugins; + $self->{lang} = $GCLang::langs{$options->lang}; + $self->{parent} = new GCPseudoFrame($self, $options, $self->{lang}, $self->{useStdOut}); + + $self->{modelsFactory} = new GCModelsCache($self->{parent}); + $self->{model} = $self->{modelsFactory}->getModel($model); + $self->{parent}->{model} = $self->{model}; + $self->{parent}->{modelsFactory} = $self->{modelsFactory}; + + $self->{plugin} = $pluginsMap{$model}->{$plugin}; + $self->leave("Fetch plugin $plugin doesn't exist") if $plugin && (!$self->{plugin}); + + if ($import) + { + GCImport::loadImporters; + foreach (@importersArray) + { + $_->setLangName($options->lang); + if (($_->getName =~ /\Q$import/) || ($_ =~ /GCExport::GCExporter$export/)) + { + $self->{importer} = $_; + last; + } + } + $self->leave("Import plugin $import doesn't exist") if $import && (!$self->{importer}); + $self->{importer}->setModel($self->{model}); + $self->{importOptions} = {}; + foreach (@{$self->{importer}->getOptions}) + { + $self->{importOptions}->{$_->{name}} = $_->{default}; + } + } + if ($export) + { + GCExport::loadExporters; + foreach (@exportersArray) + { + $_->setLangName($options->lang); + if (($_->getName eq $export) || ($_ =~ /GCExport::GCExporter$export/)) + { + $self->{exporter} = $_; + last; + } + } + $self->leave("Export plugin $export doesn't exist") if $export && (!$self->{exporter}); + $self->{exporter}->setModel($self->{model}); + $self->{exportOptions} = { + lang => $self->{lang} + }; + foreach (@{$self->{exporter}->getOptions}) + { + $self->{exportOptions}->{$_->{name}} = $_->{default}; + } + } + + $self->{toBeRemoved} = []; + + $self->{data} = new GCItems($self->{parent}); + $self->{data}->{options} = $options; + + return $self; + } + + sub DESTROY + { + my $self = shift; + + unlink $_ foreach (@{$self->{toBeRemoved}}); + } + + sub leave + { + my ($self, $message) = @_; + print "$message\n"; + $self->DESTROY; + exit 1; + } + + sub listPlugins + { + my $self = shift; + + foreach (sort keys %{$pluginsMap{$self->{model}->getName}}) + { + print "$_\n"; + print "\t", $pluginsMap{$self->{model}->getName}->{$_}->getAuthor,"\n"; + print "\n"; + } + } + + sub setModel + { + my ($self, $model) = @_; + $self->{model} = $self->{modelsFactory}->getModel($model); + if ($self->{exporter}) + { + $self->{exporter}->setModel($self->{model}); + foreach (@{$self->{exporter}->getOptions}) + { + $self->{exportOptions}->{$_->{name}} = $_->{default}; + } + } + if ($self->{importer}) + { + $self->{importer}->setModel($self->{model}); + foreach (@{$self->{importer}->getOptions}) + { + $self->{importOptions}->{$_->{name}} = $_->{default}; + } + } + return 1; + } + + sub setFields + { + my ($self, $fieldsFile) = @_; + + $self->{fields} = []; + open FIELDS, '<'.$fieldsFile; + my $model = ; + chop $model; + while () + { + chop; + push @{$self->{fields}}, $_; + } + } + + sub load + { + my ($self, $title) = @_; + my @data; + $self->leave("No fetch plugin specified") if !$self->{plugin}; + $self->{plugin}->{title} = $title; + $self->{plugin}->{type} = 'load'; + $self->{plugin}->{urlField} = $self->{model}->{commonFields}->{url}; + $self->{plugin}->load; + my $itemNumber = $self->{plugin}->getItemsNumber(); + $self->{plugin}->{type} = 'info'; + for (my $i = 0; + $i < $itemNumber; + $i++) + { + $self->{plugin}->{wantedIdx} = $i; + my $info = $self->{plugin}->getItemInfo; + foreach (@{$self->{model}->{managedImages}}) + { + $info->{$_} = $self->downloadPicture($info->{$_}); + } + push @data, $info; + } + $self->{data}->setItemsList(\@data); + } + + sub save + { + my $self = shift; + my $previousFile = $self->{data}->{options}->file; + my $previousRelativePaths = $self->{data}->{options}->useRelativePaths; + my $prevImages = $self->{parent}->getImagesDir; + + + my $newFile = GCUtils::pathToUnix(File::Spec->rel2abs($self->{file})); + $self->{data}->{options}->file($newFile); + $self->{parent}->{file} = $newFile; + + # We re-generate it because it could have changed with new file name + my $newImages = $self->{parent}->getImagesDir; + if ($prevImages ne $newImages) + { + # The last parameter is for copy. When saving a new file, we move. + $self->{data}->setNewImagesDirectory($newImages, $prevImages, 1); + } + + $self->{data}->{model} = $self->{model}; + $self->{data}->{backend} = new GCBackend::GCBeXmlParser($self->{parent}) + if ! $self->{data}->{backend}; + $self->{data}->{options}->useRelativePaths(0) if $self->{useStdOut}; + $self->{data}->save; + $self->{data}->{options}->useRelativePaths($previousRelativePaths); + $self->{data}->{options}->file($previousFile); + open IN, $self->{file}; + if ($self->{useStdOut}) + { + print $_ while (); + } + close IN; + unlink $self->{file} if $self->{useStdOut}; + } + + sub open + { + my ($self, $file) = @_; + $self->{data}->load($file, undef, undef, 1); + $self->{original} = $file; + } + + sub import + { + my ($self, $file, $prefs) = @_; + $self->{importOptions}->{parent} = $self->{parent}; + $self->{importOptions}->{file} = $file; + $self->{importOptions}->{fields} = $self->{fields}; + $self->parsePrefs($prefs, $self->{importOptions}); + $self->{importer}->{options} = $self->{importOptions}; + $self->{data}->setItemsList($self->{importer}->getItemsArray($file)); + $self->setModel($self->{importer}->getModelName); + $self->{data}->{model} = $self->{model} + if $self->{data}; + } + + sub export + { + my ($self, $prefs) = @_; + + if (!scalar $self->{fields}) + { + $self->{fields} = $self->{model}->{fieldsNames}; + } + $self->{exportOptions}->{parent} = $self->{parent}; + $self->{exportOptions}->{fields} = $self->{fields}; + $self->{exportOptions}->{originalList} = $self->{data}; + $self->{exportOptions}->{withPictures} = 1; + $self->{exportOptions}->{file} = $self->{file}; + $self->{exportOptions}->{collection} = $self->{original}; + $self->{exportOptions}->{fieldsInfo} = $self->{model}->{fieldsInfo}; + $self->{exportOptions}->{items} = $self->{data}->getItemsListFiltered; + $self->{exportOptions}->{defaultImage} = $ENV{GCS_SHARE_DIR}.'/logos/no.png'; + $self->parsePrefs($prefs, $self->{exportOptions}); + $self->{data}->{model} = $self->{model} + if $self->{data}; + + $self->{exporter}->process($self->{exportOptions}); + + CORE::open IN, $self->{exportOptions}->{file}; + if ($self->{useStdOut}) + { + print $_ while (); + } + close IN; + unlink $self->{exportOptions}->{file} if $self->{useStdOut}; + } + + sub downloadPicture + { + my ($self, $pictureUrl) = @_; + + return '' if ! $pictureUrl; + my ($name,$path,$suffix) = fileparse($pictureUrl, "\.gif", "\.jpg", "\.jpeg", "\.png"); + (undef, my $picture) = tmpnam; + $picture .= $suffix; + + GCUtils::downloadFile($pictureUrl, $picture, $self->{parent}); + push @{$self->{toBeRemoved}}, $picture; + return $picture; + } + + sub parsePrefs + { + my ($self, $prefs, $cont) = @_; + + foreach (split /,/, $prefs) + { + my @option = split /=>/, $_; + $option[0] =~ s/^\s*//g; + $option[0] =~ s/\s*$//g; + $option[1] =~ s/^\s*//g; + $option[1] =~ s/\s*$//g; + $cont->{$option[0]} = $option[1]; + } + } +} + +1; diff --git a/lib/gcstar/GCData.pm b/lib/gcstar/GCData.pm new file mode 100644 index 0000000..84582f3 --- /dev/null +++ b/lib/gcstar/GCData.pm @@ -0,0 +1,970 @@ +package GCData; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + + +{ + package GCItems; + # + # This is seen as $main->{items} + # + use XML::Parser; + use Storable; + use File::Copy; + use File::Path; + use File::Basename; + + use GCModel; + use GCBackend::GCBackendXmlParser; + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + + $self->{parent} = $parent; + + $self->{imagesToBeRemoved} = []; + $self->{imagesToBeAdded} = []; + $self->{loaded} = {}; + + $self->{currentItem} = -1; + $self->{hasBeenDeleted} = 0; + $self->{block} = 0; + $self->{previousFile} = 0; + #$self->{filterSearch} = new GCFilterSearch; + + bless ($self, $class); + return $self; + } + + sub initModel + { + #if $modelChanged is set, that means we updated the currently used model + # and not that we changed the model + my ($self, $model, $modelUpdated) = @_; + $self->{model} = $model; + $self->{parent}->notifyModelChange($modelUpdated); + } + + sub setPanel + { + my ($self, $panel) = @_; + $self->{panel} = $panel; + } + + sub unselect + { + my ($self) = @_; + + $self->{currentItem} = -1; + } + + sub updateSelectedItemInfoFromGivenPanel + { + my ($self, $panel) = @_; + my $previousPanel = $self->{panel}; + $self->{panel} = $panel; + $self->updateSelectedItemInfoFromPanel(1); + $self->{panel} = $previousPanel; + } + + sub getInfoFromPanel + { + # $info contains default values that will be merged with new ones + my ($self, $panel, $info) = @_; + + my $idField = $self->{model}->{commonFields}->{id}; + my $panelId = $panel->$idField; + my $previousId = $info->{$idField}; + + my $changed = 0; + if ($panelId && + ($panelId != $previousId)) + { + $info->{$idField} = $panel->$idField; + $self->{loaded}->{information}->{maxId} = $panelId + if ($panelId > $self->{loaded}->{information}->{maxId}); + $self->findMaxId if $previousId == $self->{loaded}->{information}->{maxId}; + $changed = 1; + } + $panel->$idField($info->{$idField}) if $panel->$idField; + + my $previous = {$idField => $previousId}; + + for my $field (@{$self->{model}->{fieldsNames}}) + { + next if $field eq $idField; + $previous->{$field} = $info->{$field}; + next if !$panel->{$field}->hasChanged; + $panel->{$field}->addHistory if ($self->{model}->{fieldsInfo}->{$field}->{hasHistory}); + $changed = 1; + $info->{$field} = $panel->$field; + $self->{parent}->{menubar}->checkFilter($field); + } + + return ($changed, $info, $previous); + } + + sub updateSelectedItemInfoFromPanel + { + my ($self, $withSelect, $forced) = @_; + my $selectedChanged = 0; + my $filtered = 0; + return $selectedChanged if $self->{currentItem} == -1; + my $info; + if ($self->{multipleMode}) + { + $info = {}; + } + else + { + $info = ($self->getItemsListFiltered)->[$self->{currentItem}]; + } + + my $changed; + my $previous; + ($changed, $info, $previous) = $self->getInfoFromPanel($self->{panel}, $info); + if ($forced) + { + $previous->{$_} = 'GCS_FORCED' foreach @$forced; + } + if ($changed) + { + if ($self->{multipleMode}) + { + my $newIdx; + # Propagate the changes to all the items + foreach (@{$self->{multipleCurrentItems}}) + { + my $previous = Storable::dclone(($self->getItemsListFiltered)->[$_]); + my $item = ($self->getItemsListFiltered)->[$_]; + for my $field (keys %$info) + { + $item->{$field} = $info->{$field}; + } + $self->{panel}->dataChanged($item, 1); + $newIdx = $self->{parent}->{itemsView}->changeItem($_, $previous, $item); + if ($newIdx != $_) + { + $selectedChanged = 1; + } + } + if ($selectedChanged) + { + $self->{currentItem} = $self->{parent}->{itemsView}->getCurrentIdx; + $self->{multipleMode} = 0; + $self->displayCurrent; + } + } + else + { + $self->{panel}->dataChanged($info); + my $current = $self->{parent}->{itemsView}->changeCurrent($previous, + $info, + $self->{currentItem}, + $withSelect); + # If we didn't selected the same, the selection didn't change + if ($current != $self->{currentItem}) + { + $self->{currentItem} = $current; + $self->displayCurrent if $withSelect; + $selectedChanged = 1; + } + if ($selectedChanged) + { + $filtered = 1; + $selectedChanged = 0 if !$withSelect; + } + } + $self->{parent}->checkPanelVisibility; + $self->{parent}->markAsUpdated; + } + return ($selectedChanged, $filtered); + } + + sub getTitle + { + my ($self, $idx) = @_; + if ($self->{multipleMode}) + { + # Multiple items selected, so just use collection title or filename + my $name; + if ($self->{parent}->{options}->file) + { + $name = $self->{parent}->{items}->getInformation->{name} + if $self->{parent}->{items}; + $name ||= basename($self->{parent}->{options}->file); + } + else + { + $name = $self->{parent}->{lang}->{UnsavedCollection}; + } + return $name; + } + else + { + my $realIdx = $idx; + $realIdx = $self->{currentItem} if ! defined $idx; + return ($self->getItemsListFiltered)->[$realIdx]->{$self->{model}->{commonFields}->{title}}; + } + } + + sub getCurrent + { + my ($self) = @_; + return $self->{currentItem}; + } + + sub displayCurrent + { + my ($self) = @_; + $self->displayInPanel($self->{panel}, undef); + } + + sub displayInPanel + { + my ($self, $panel, $idx) = @_; + my $info; + if ($self->{multipleMode}) + { + # We merge all the items here + my %fields = map {$_ => 1} @{$self->{model}->{fieldsNotFormatted}}; + foreach (@{$self->{multipleCurrentItems}}) + { + my $item = ($self->getItemsListFiltered)->[$_]; + for my $field (keys %fields) + { + if (exists $info->{$field}) + { + if ($self->transformValue($info->{$field}, $field) + ne + $self->transformValue($item->{$field}, $field)) + { + $info->{$field} = ''; + delete $fields{$field}; + # TODO store the information also elsewhere to mark + # the fields in panel. Or mark it immediately with something + # such as + # panel->markAsDirty($field); + } + } + else + { + $info->{$field} = $item->{$field}; + } + } + } + } + else + { + $idx = $self->{currentItem} if ! defined $idx; + return if $self->{currentItem} < 0; + $info = ($self->getItemsListFiltered)->[$idx]; + } + $self->displayDataInPanel($panel, $info); + } + + sub displayDataInPanel + { + my ($self, $panel, $info) = @_; + for my $field (@{$self->{model}->{fieldsNotFormatted}}) + { + $panel->$field($info->{$field}); + $panel->{$field}->resetChanged + if $panel->{$field}; + } + + $panel->dataChanged; + $GCGraphicComponent::somethingChanged = 0; + } + + sub display + { + my $self = shift; + return if (! $self->{itemArray}) || (! scalar @{$self->{itemArray}}); + my @numbers = @_; + my $number; + my $multipleMode; + my $withSelect = 0; + my $noUpdate = 0; + if ($#numbers > 0) + { + $multipleMode = 1; + $self->{multipleCurrentItems} = \@numbers; + } + else + { + $multipleMode = 0; + $number = $numbers[0]; + if ($number == -1) + { + $number = 0; + $noUpdate = 1; + } + # We want a selection if user clicked on the same one + $withSelect = ($number == $self->{currentItem}); + } + my ($selectedHasChanged, $filtered) = (0, 0); + + if ((!$noUpdate) && ($self->{currentItem} > -1) && !($self->{hasBeenDeleted})) + { + ($selectedHasChanged, $filtered) = $self->updateSelectedItemInfoFromPanel($withSelect); + } + else + { + $self->{currentItem} = $number + if !$multipleMode; + } + $self->{multipleMode} = $multipleMode; + $self->{hasBeenDeleted} = 0; + + $self->{currentItem} = $number if !$selectedHasChanged && !$multipleMode; + $self->displayCurrent if !$selectedHasChanged; + return ($selectedHasChanged || $filtered); + } + + sub valueToDisplayed + { + my ($self, $value, $field) = @_; + my $displayed = $self->{model}->getDisplayedValue($self->{model}->{fieldsInfo}->{$field}->{values}, $value); + return $displayed if $displayed; + # For personal models, it won't return a value. Then we keep the original one. + return $value; + } + + sub transformValue + { + my ($self, $value, $field, $type) = @_; + + $type ||= $self->{model}->{fieldsInfo}->{$field}->{type}; + $value = $self->{parent}->transformTitle($value) if $field eq $self->{model}->{commonFields}->{title}; + #$value = GCPreProcess::reverseDate($value) if $type eq 'date'; + $value = GCUtils::timeToStr($value, $self->{parent}->{options}->dateFormat) + if $type eq 'date'; + $value = $self->valueToDisplayed($value, $field) if $type eq 'options'; + $value = GCPreProcess::multipleList($value, $type) if $type =~ /list$/o; + return $value; + } + + sub getValue + { + my ($self, $idx, $field) = @_; + + return ($self->getItemsListFiltered)->[$idx]->{$field}; + } + + sub setValue + { + my ($self, $idx, $field, $value) = @_; + + ($self->getItemsListFiltered)->[$idx]->{$field} = $value; + if ($idx == $self->{currentItem}) + { + $self->{panel}->$field($value); + $self->{panel}->dataChanged; + } + } + + sub getItemsListFiltered + { + my ($self, $filter) = @_; + + return $self->{itemArray} if ! $filter; + my @results = (); + foreach (@{$self->{itemArray}}) + { + if ($self->{parent}->{filterSearch}->test($_)) + { + push @results, $_; + } + } + return \@results; + } + + # Should only be used by GCCommandExecution + sub setItemsList + { + my ($self, $itemsList) = @_; + + $self->{itemArray} = $itemsList; + } + + sub getInformation + { + my $self = shift; + $self->{loaded}->{information} ||= {}; + return $self->{loaded}->{information}; + } + + sub setInformation + { + my ($self, $info) = @_; + $self->{loaded}->{information} = $info; + } + + sub reloadList + { + my ($self, $splash, $fullProgress, $filtering) = @_; + return if $self->{block}; + if ($splash) + { + $splash->initProgress if $fullProgress; + $splash->setItemsTotal(scalar @{$self->{itemArray}}) + if $self->{itemArray}; + } + $self->{parent}->{itemsView}->reset if $self->{parent}->{itemsView}; + + my $lastDisplayed = -1; + my $hasId = 0; + my $j = 0; + my $idField = $self->{model}->{commonFields}->{id}; + my $currentId; + + # If we don't get an history from BE, we will have to initialize it now + my $historyNeeded = ! $self->{loaded}->{gotHistory}; + my %histories; + + foreach (@{$self->{itemArray}}) + { + if ($historyNeeded) + { + foreach my $field (@{$self->{model}->{fieldsHistory}}) + { + #push @{$histories{$field}}, $_->{$field}; + $self->{panel}->addHistory($_->{$field}, 1); + } + } + + $currentId = $_->{$idField}; + + $self->{parent}->{itemsView}->addItem($_, 0); + $lastDisplayed = $j; + $splash->setProgressForItemsDisplay($j) if $splash; + $j++; + } + + if ($historyNeeded) + { + for my $hfield(@{$self->{model}->{fieldsHistory}}) + { + $self->{panel}->{$hfield}->setDropDown; + } + # Now we are sure we got one + $self->{loaded}->{gotHistory} = 2; + } + + $self->{panel}->show if $j; + + #if ($splash && $fullProgress) + #{ + # $splash->endProgress; + #} + + if (! $self->{parent}->{initializing}) + { + $self->{parent}->reloadDone(0, $splash); + $self->select($self->{currentItem}, 0); + } + } + + sub select + { + my ($self, $value, $init) = @_; + return if !$self->{parent}->{itemsView}; + return $self->{parent}->{itemsView}->select($value, $init) unless $value < -1; + } + + sub removeCurrentItems + { + my $self = shift; + + my $numbers = $self->{parent}->{itemsView}->getCurrentItems; + + my $nbRemoved = 0; + # Numerically sort list + foreach my $number(sort {$a <=> $b} @$numbers) + { + # We need to adjust it because we already removed other ones. + my $actualNumber = $number - $nbRemoved; + foreach (@{$self->{model}->{managedImages}}) + { + my $image = $self->{itemArray}->[$actualNumber]->{$_}; + $self->{parent}->checkPictureToBeRemoved($image); + } + + splice @{$self->{itemArray}}, $actualNumber, 1; + $nbRemoved++; + } + my $newIdx = $self->{parent}->{itemsView}->removeCurrentItems; #($number); + + $self->{currentItem} = $newIdx; + $self->{multipleMode} = 0; + $self->{hasBeenDeleted} = 1; + $self->displayCurrent; + } + + sub addItem + { + my ($self, $info, $keepId, $noSelect) = @_; + my $nbItems = scalar @{$self->{itemArray}}; +# $self->{panel}->show if ! $nbItems; + + my $currentId; + if ($keepId) + { + $currentId = $self->{itemArray}->[$nbItems]->{$self->{model}->{commonFields}->{id}}; + } + else + { + $self->{loaded}->{information}->{maxId}++; + $currentId = $self->{loaded}->{information}->{maxId}; + $self->{itemArray}->[$nbItems]->{$self->{model}->{commonFields}->{id}} = $currentId; + } + + for my $field (@{$self->{model}->{fieldsNames}}) + { + next if $field eq $self->{model}->{commonFields}->{id}; + if ($self->{model}->{fieldsInfo}->{$field}->{hasHistory}) + { + $self->{panel}->{$field}->addHistory($info->{$field}); + } + $self->{itemArray}->[$nbItems]->{$field} = $info->{$field}; + } + + $self->{parent}->{itemsView}->addItem($self->{itemArray}->[$nbItems], 1); + + $self->{multipleMode} = 0; + $self->{currentItem} = $nbItems; + $self->displayCurrent; + $self->select($nbItems, 0) + if !$noSelect; + + $self->{parent}->{itemsView}->showCurrent; + + return $currentId; + } + + sub setOptions + { + my ($self, $options) = @_; + + $self->{options} = $options; + + #return $self->load($options->file, $splash, 0); + } + + sub markToBeRemoved + { + my ($self, $image) = @_; + push @{$self->{imagesToBeRemoved}}, $image; + } + + sub markToBeAdded + { + my ($self, $image) = @_; + push @{$self->{imagesToBeAdded}}, $image; + } + + sub removeMarkedPictures + { + my $self = shift; + my $image; + foreach $image(@{$self->{imagesToBeRemoved}}) + { + unlink $image; + } + + $self->{imagesToBeRemoved} = []; + } + + sub addMarkedPictures + { + my $self = shift; + + $self->{imagesToBeAdded} = []; + } + + sub clean + { + my $self = shift; + my $image; + foreach (@{$self->{imagesToBeAdded}}) + { + unlink $_; + } + $self->{oldImagesDirectory} = {}; + $self->{newImagesDirectory} = undef; + $self->{copyImagesWhenChangingDir} = 0; + } + + sub setNewImagesDirectory + { + # $prev is also a parameter because we didn't store it here + my ($self, $new, $prev, $withCopy) = @_; + $new =~ s|/$||; + $self->{newImagesDirectory} = $new; + $self->{copyImagesWhenChangingDir} = $withCopy; + # We stored the previous one as a hash so it will be easier for tests + $prev =~ s|/$||; + $self->{oldImagesDirectory}->{$prev} = 1; + } + + sub setPreviousFile + { + my ($self, $prev) = @_; + + $self->{previousFile} = $prev; + } + + sub queryReplace + { + my ($self, $field, $old, $new, $caseSensitive) = @_; + foreach (@{$self->{itemArray}}) + { + if (ref($_->{$field}) eq 'ARRAY') + { + foreach my $subval(@{$_->{$field}}) + { + foreach my $val(@$subval) + { + if ($caseSensitive) + { + $val =~ s/$old/$new/g; + } + else + { + $val =~ s/$old/$new/gi; + } + } + } + } + else + { + if ($caseSensitive) + { + $_->{$field} =~ s/$old/$new/g; + } + else + { + $_->{$field} =~ s/$old/$new/gi; + } + } + } + $self->displayCurrent; + $self->reloadList; + } + + sub findMaxId + { + my $self = shift; + + $self->{loaded}->{information}->{maxId} = -1; + foreach (@{$self->{itemArray}}) + { + $self->{loaded}->{information}->{maxId} = $_->{$self->{model}->{commonFields}->{id}} + if $_->{$self->{model}->{commonFields}->{id}} > $self->{loaded}->{information}->{maxId}; + } + } + + sub clearList + { + my $self = shift; + $self->{currentItem} = -1; + $self->{loaded} = {}; + $self->{itemArray} = []; + $self->{panel}->hide if $self->{panel}; + $self->{parent}->{itemsView}->clearCache if $self->{parent}->{itemsView}; + $self->{parent}->{itemsView}->reset if $self->{parent}->{itemsView}; + #$self->{parent}->reloadDone(1) if ! $self->{parent}->{initializing}; + #$self->reloadList if ! $self->{parent}->{initializing}; + } + + sub getNbItems + { + my $self = shift; + return 0 if ! $self->{itemArray}; + return scalar @{$self->{itemArray}}; + } + + sub setLock + { + my ($self, $value) = @_; + $self->{loaded}->{information}->{locked} = $value; + } + + sub getLock + { + my $self = shift; + return $self->{loaded}->{information}->{locked}; + } + + sub getBackend + { + my ($self, $file) = @_; + $self->{backend} = new GCBackend::GCBeXmlParser($self->{parent}) + if !$self->{backend}; + $self->{backend}->setParameters(file => $file, + version => $self->{parent}->{version}); + return $self->{backend}; + } + + sub getVersion + { + my ($self, $file) = @_; + + return $self->getBackend($file)->getVersion; + } + + sub load + { + my ($self, $file, $splash, $fullProgress, $noReload) = @_; + + my $initTime; + if ($ENV{GCS_PROFILING} > 0) + { + eval 'use Time::HiRes'; + eval '$initTime = [Time::HiRes::gettimeofday()]'; + } + + $self->clean; + if (!$file) + { + $self->{parent}->setCurrentModel; + return 0; + } + + my $collection; + + $self->{block} = 1; + $self->clearList; + $self->{block} = 0; + $self->{splash} = $splash; + my $backend; + eval + { + $backend = $self->getBackend($file); + + $self->{loaded} = $backend->load($splash); + # We keep a direct access to this one + $self->{itemArray} = $self->{loaded}->{data}; + }; + if ($@) + { + my @error = ('Fatal error while reading file', $@); + return (0, \@error); + } + elsif ($self->{loaded}->{error}) + { + return (0, $self->{loaded}->{error}); + } + + # Perform Models Change if needed + if(!$self->{model}->{isInline} && ($backend->getVersion() ne $backend->{version})) + { + my $modelFormatUpdater=GCModelsChanges->new($self,$self->{model}->{collection}->{name}); + $modelFormatUpdater->applyChanges($self->{itemArray}, $backend->getVersion(), $backend->{version}); + } + + # Hide the panel if no item + if (! scalar @{$self->{itemArray}}) + { + $self->{panel}->hide; + } + + # gotHistory = 1 means we got one but it has not been set in components + if ($self->{loaded}->{gotHistory} == 1) + { + for my $hfield(@{$self->{model}->{fieldsHistory}}) + { + if (exists $self->{loaded}->{histories}->{$hfield}) + { + $self->{panel}->{$hfield}->setValues($self->{loaded}->{histories}->{$hfield}, 1); + } + $self->{panel}->{$hfield}->setDropDown; + } +# $self->{loaded}->{gotHistory} = 2; + } + elsif ($self->{loaded}->{gotHistory} == 2) + { + for my $hfield(@{$self->{model}->{fieldsHistory}}) + { + $self->{panel}->{$hfield}->setDropDown; + } + } + + $self->reloadList($splash, $fullProgress) unless $noReload; + + if ($ENV{GCS_PROFILING} > 0) + { + my $elapsed; + eval '$elapsed = Time::HiRes::tv_interval($initTime)'; + print "Load time : $elapsed\n"; + } + + return 1; + } + + sub movePictures + { + my ($self) = @_; + eval { + mkpath $self->{newImagesDirectory}; + my $file; + my $dataFile = $self->{previousFile} ? $self->{previousFile} : $self->{options}->file; + foreach (@{$self->getItemsListFiltered}) + { + foreach my $pic(@{$self->{model}->{fieldsImage}}) + { + $file = GCUtils::getDisplayedImage($_->{$pic}, + '', + $dataFile); + # Not moving picture if it is not in the previous directory + next if !$file; + next if ! exists $self->{oldImagesDirectory}->{Cwd::realpath(dirname($file))}; + (my $suffix = $file) =~ s/.*?(\.[^.]*)$/$1/; + my $new = + $self->{parent}->getUniqueImageFileName( + $suffix, + $_->{$self->{model}->{commonFields}->{title}}); + + if ($self->{copyImagesWhenChangingDir}) + { + copy $file, $new; + } + else + { + move $file, $new; + } + $_->{$pic} = $new; + } + } + }; + $self->{previousFile} = 0; + return $@ if $@; + $self->displayCurrent; + $self->{newImagesDirectory} = undef; + $self->{copyImagesWhenChangingDir} = 0; + $self->{oldImagesDirectory} = {}; + return 0; + } + + sub save + { + my ($self, $splash) = @_; + + my $initTime; + if ($ENV{GCS_PROFILING} > 0) + { + eval 'use Time::HiRes'; + eval '$initTime = [Time::HiRes::gettimeofday()]'; + } + + $self->updateSelectedItemInfoFromPanel if ($self->{currentItem} > -1); + + # TODO : Use progress bar for this operation also + my $moveError = $self->movePictures + if $self->{newImagesDirectory}; + return (0, ['SaveError', $moveError]) + if $moveError; + + + if ($splash) + { + $splash->initProgress($self->{parent}->{lang}->{StatusSave}); + $splash->setItemsTotal(scalar @{$self->{itemArray}}); + } + $self->addMarkedPictures; + $self->removeMarkedPictures; + + my $backend = $self->getBackend($self->{options}->file); + + # We re-generate histories to give it to backend + # Deactivated for the moment +# if ($self->{panel}) +# { +# my %histories; +# for my $hfield(@{$self->{model}->{fieldsHistory}}) +# { +# $histories{$hfield} = $self->{panel}->{$hfield}->getValues; +# } +# $backend->setHistories(\%histories); +# } + + my $result = $backend->save($self->{itemArray}, + $self->{loaded}->{information}, + $splash); + + $self->{parent}->endProgress; + if ($result->{error}) + { + return (0, $result->{error}); + } + $self->{parent}->removeUpdatedMark; + + if ($ENV{GCS_PROFILING} > 0) + { + my $elapsed; + eval '$elapsed = Time::HiRes::tv_interval($initTime)'; + print "Save time : $elapsed\n"; + } + return 1; + } + + sub getSummary + { + my ($self, $idx) = @_; + + my $info = ($self->getItemsListFiltered)->[$idx]; + + my $summary = "".GCUtils::encodeEntities($info->{$self->{model}->{commonFields}->{title}})."\n"; + + for my $field (@{$self->{model}->getSummaryFields}) + { + my $value = $info->{$field}; + + if ($field eq $self->{model}->{commonFields}->{borrower}->{name}) + { + $value = $self->{parent}->{lang}->{PanelNobody} + if $value eq 'none'; + $value = $self->{parent}->{lang}->{PanelUnknown} + if $value eq 'unknown'; + } + else + { + $value = GCUtils::encodeEntities($self->transformValue($value, $field)); + } + $summary .= "\n" + .GCUtils::encodeEntities($self->{model}->getDisplayedLabel($field)) + .$self->{parent}->{lang}->{Separator} + ."" + .$value; + } + return $summary; + } +} + +1; diff --git a/lib/gcstar/GCDialogs.pm b/lib/gcstar/GCDialogs.pm new file mode 100644 index 0000000..19908cd --- /dev/null +++ b/lib/gcstar/GCDialogs.pm @@ -0,0 +1,1519 @@ +package GCDialogs; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use Gtk2; +use utf8; + +our @okCancelButtons = ('gtk-cancel'=>'cancel', 'gtk-ok'=>'ok'); + +my $hasAboutDialog = 1; +eval 'Gtk2::AboutDialog->set_email_hook(undef, undef)'; +$hasAboutDialog = 0 if $@; + +{ + package GCModalDialog; + use base "Gtk2::Dialog"; + + sub showMe + { + my $self = shift; + + $self->present; + } + + sub activateOkButton + { + my ($self, $value) = @_; + ($self->action_area->get_children)[$self->{okPosition}]->set_sensitive($value); + } + + sub activateExtraButton + { + my ($self, $value) = @_; + ($self->action_area->get_children)[$self->{extraPosition}]->set_sensitive($value); + } + + sub setOkLabel + { + my ($self, $label) = @_; + my @buttons = $self->action_area->get_children; + my $tmpWidget = $buttons[0]; + $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox'); + ($tmpWidget->get_children)[1]->set_label($label); + } + + sub setCancelLabel + { + my ($self, $label) = @_; + my @buttons = $self->action_area->get_children; + my $tmpWidget = $buttons[1]; + $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox'); + ($tmpWidget->get_children)[1]->set_label($label); + } + + sub new + { + my ($proto, $parent, $title, $okLabel, $extraAfter, @extraButtons) = @_; + $title =~ s/_//g; + my $class = ref($proto) || $proto; + my @buttons; + if ((defined $okLabel) && ($okLabel =~ /^gtk-/)) + { + @buttons = ('gtk-cancel'=>'cancel', $okLabel=>'ok'); + $okLabel = ''; + } + else + { + @buttons = @GCDialogs::okCancelButtons; + } + my ($okPosition, $extraPosition) = (0, -1); + if (@extraButtons) + { + if ($extraAfter) + { + $okPosition = 1; + $extraPosition = 0; + push @buttons, @extraButtons; + } + else + { + $okPosition = 0; + $extraPosition = 2; + unshift @buttons, @extraButtons; + } + } + my $self = $class->SUPER::new($title, + $parent, + [qw/modal destroy-with-parent/], + @buttons + ); + bless ($self, $class); + + ($self->{okPosition}, $self->{extraPosition}) = ($okPosition, $extraPosition); + + $self->setOkLabel($okLabel) if $okLabel; + $self->set_default_response('ok'); + + $self->{parent} = $parent; + + $self->vbox->set_border_width($GCUtils::margin); + + return $self; + } +} + +{ + package GCAboutDialog; + if (!$hasAboutDialog) + { + use base "Gtk2::Dialog"; + } + + sub show + { + my $self = shift; + + if ($hasAboutDialog) + { + $self->{about}->set_position('center-on-parent'); + $self->{about}->run; + $self->{about}->hide; + } + else + { + $self->SUPER::show(); + $self->show_all; + my $code = $self->run; + $self->hide; + } + } + + sub changeStyle + { + my $self = shift; + $self->{vBox}->set_border_width(0); + ($self->{vBox}->get_children)[1]->set_border_width($self->{border}); + } + + sub new + { + my ($proto, $parent, $version) = @_; + my $class = ref($proto) || $proto; + + my $self; + + my $logoFile = $parent->{logosDir}.'about.png'; + + if ($hasAboutDialog) + { + $self = { + about => new Gtk2::AboutDialog, + parent => $parent + }; + bless ($self, $class); + + open LICENSE, "<".$ENV{GCS_SHARE_DIR}.'/LICENSE'; + my $license = do {local $/; }; + close LICENSE; + my @authors = split m/\n/, $parent->{lang}->{AboutWho}; + + $self->{about}->set_transient_for($parent); + $self->{about}->set_url_hook( sub { + my ($widget, $url) = @_; + $self->{parent}->launch($url, 'url'); + }); + + if (-f $logoFile) + { + my $logo = Gtk2::Gdk::Pixbuf->new_from_file($logoFile); + $self->{about}->set_logo($logo); + } + $self->{about}->set_program_name('GCstar'); + $self->{about}->set_comments($parent->{lang}->{AboutDesc}); + $self->{about}->set_version($version); + $self->{about}->set_authors('', @authors); + $self->{about}->set_documenters(("",'Christian Jodar (Tian)','http://wiki.gcstar.org/')); + $self->{about}->set_artists("",$parent->{lang}->{AboutDesign}); + $self->{about}->set_copyright($parent->{lang}->{AboutLicense}); + $self->{about}->set_license($license); + $self->{about}->set_translator_credits("\n".$parent->{lang}->{AboutTranslation}); + $self->{about}->set_website("http://www.gcstar.org/"); + $self->{vBox} = $self->{about}->get_children; + $self->{border} = $self->{vBox}->get_border_width; + if ($self->{about}->signal_query('style_set')) + { + $self->{about}->signal_connect('style_set' => sub {$self->changeStyle }); + } + $self->changeStyle; + } + else + { + $self = $class->SUPER::new($parent->{lang}->{AboutTitle}, + $parent, + [qw/modal destroy-with-parent/], + 'gtk-ok' => 'ok' + ); + bless ($self, $class); + my $labelDesc = Gtk2::Label->new($parent->{lang}->{AboutDesc}); + my $labelVersion = Gtk2::Label->new($parent->{lang}->{AboutVersion}.' '.$version); + #my $labelTeam = Gtk2::Label->new($parent->{lang}->{AboutTeam}); + + my $who = new Gtk2::Label($parent->{lang}->{AboutWho}); + + my $labelTranslation = Gtk2::Label->new($parent->{lang}->{AboutTranslation}); + my $labelLicense = Gtk2::Label->new($parent->{lang}->{AboutLicense}); + $labelLicense->set_justify('center'); + my $button = Gtk2::Button->new_with_mnemonic('_http://www.gcstar.org/'); + $button->child->set_padding(10,0); + $button->signal_connect('clicked', sub { + my ($widget, $parent) = @_; + (my $url = $widget->get_label) =~ s/^_//; + $parent->launch($url, 'url'); + }, $parent); + my $labelDesign = Gtk2::Label->new($parent->{lang}->{AboutDesign}); + + $self->vbox->set_homogeneous(0); + if (-f $logoFile) + { + my $image = Gtk2::Image->new_from_file($logoFile); + $self->vbox->pack_start($image, 0, 0, 0); + } + $self->vbox->pack_start($labelDesc, 1, 1, 4); + $self->vbox->pack_start($labelVersion, 1, 1, 4); + $self->vbox->pack_start($labelLicense, 1, 1, 4); + $self->vbox->pack_start(Gtk2::HSeparator->new, 1, 1, 4); + my $hbox = new Gtk2::HBox(0,0); + $hbox->pack_start($button, 1, 0, 10); + $self->vbox->pack_start($hbox, 0, 0, 4); + my $hboxDesign = new Gtk2::HBox(0,0); + $self->vbox->pack_start($labelDesign, 1, 1, 4); + $self->vbox->pack_start($hboxDesign, 0, 0, 4); + + my $teamButton = Gtk2::Button->new($parent->{lang}->{AboutTeam}); + $teamButton->signal_connect('clicked' => sub { + my $dialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + 'info', + 'ok', + $parent->{lang}->{AboutWho}); + $dialog->run; + $dialog->destroy; + }); + $self->action_area->pack_start($teamButton,0,0,0); + $self->action_area->reorder_child($teamButton,0); + + + #$self->vbox->set_size_request(400,-1); + $self->set_resizable(0); + $self->set_position('center-always'); + } + + return $self; + } +} + +{ + package GCImageDialog; + use base "Gtk2::Dialog"; + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + $self->set_position('center-always'); + $self->{scrollArea}->signal_connect('size-allocate' => sub { + return if !$self->{scrollArea}; + my ($width, $height) = $self->get_size; + return if ($width == $self->{width}) && ($height == $self->{height}); + my $allocation = $self->{scrollArea}->allocation; + return if $allocation->height < 10; + $self->{image}->parent->set_size_request(-1, -1); + $self->set_position('center'); + my $pixbuf = GCUtils::scaleMaxPixbuf($self->{originalPixbuf}, $allocation->width, $allocation->height); + $self->{image}->set_from_pixbuf($pixbuf); + ($self->{width}, $self->{height}) = ($width, $height); + }) if $self->{scrollArea}; + my $code = $self->run; + $self->hide; + $self->{windowParent}->showMe; + } + + sub new + { + my ($proto, $parent, $file, $windowParent) = @_; + my $class = ref($proto) || $proto; + $windowParent ||= $parent; + my $self = $class->SUPER::new($parent->{lang}->{ImportViewPicture}, + $windowParent, + [qw/modal destroy-with-parent/], + 'gtk-ok' => 'ok' + ); + bless($self, $class); + + $self->{parent} = $parent; + $self->{windowParent} = $windowParent; + + if (-f $file) + { + $self->{image} = Gtk2::Image->new; + $self->{originalPixbuf} = Gtk2::Gdk::Pixbuf->new_from_file($file); + $self->{image}->set_from_pixbuf($self->{originalPixbuf}); + $self->{image}->set_size_request(0,0); + $self->{scrollArea} = new Gtk2::ScrolledWindow; + $self->{scrollArea}->set_policy ('automatic', 'automatic'); + $self->{scrollArea}->set_shadow_type('none'); + $self->{scrollArea}->add_with_viewport($self->{image}); + $self->vbox->pack_start($self->{scrollArea},1,1,0); + my ($screenWidth, $screenHeight) = ($self->get_screen->get_width, $self->get_screen->get_height); + my ($pixWidth, $pixHeight) = ($self->{originalPixbuf}->get_width, $self->{originalPixbuf}->get_height); + + # Minimum amount of spacing we want to leave for panels, window decorations, borders, etc + my $heightMargin = 150; + my $widthMargin = 30; + + my $ratio = $pixWidth / $pixHeight; + + # Check if picture will fit into screen, or if we'll need to resize + if (($pixHeight > ($screenHeight - $heightMargin)) && ($pixWidth <= ($screenWidth - $widthMargin))) + { + # Image is higher than vertical space we have available, but not wider + $pixHeight = $screenHeight - $heightMargin; + $pixWidth = $pixHeight * $ratio; + } + elsif (($pixHeight <= ($screenHeight - $heightMargin)) && ($pixWidth > ($screenWidth - $widthMargin))) + { + # Image is wider than horizontal space we have available, but not taller + $pixWidth = $screenWidth - $widthMargin; + $pixHeight = $pixWidth / $ratio; + } + elsif (($pixHeight > ($screenHeight - $heightMargin)) && ($pixWidth > ($screenWidth - $widthMargin))) + { + # Image is both too high and too wide for space we have, so see which direction will be + # affected the most + if ($screenHeight - $heightMargin - $pixHeight < $screenWidth - $widthMargin - $pixWidth) + { + # Constrained by vertical height + $pixHeight = $screenHeight - $heightMargin; + $pixWidth = $pixHeight * $ratio; + } + else + { + # Constrained by horizontal width + $pixWidth = $screenWidth - $widthMargin; + $pixHeight = $pixWidth / $ratio; + } + } + $self->{image}->parent->set_size_request($pixWidth, $pixHeight); + } + else + { + my $label = new Gtk2::Label; + $label->set_markup(''.$parent->{lang}->{PanelImageNoImage}.''); + $self->vbox->pack_start($label,1,1,4 * $GCUtils::margin); + } + + return $self; + } +} + +{ + package GCNumberEntryDialog; + use base "Gtk2::Dialog"; + + sub getUserValue + { + my $self = shift; + my $value = -1; + my $code = $self->run; + $value = $self->{value}->get_value if ($code eq 'ok'); + $self->hide; + return $value; + } + + sub setValue + { + my ($self, $value) = @_; + + $self->{value}->set_value($value); + } + + sub new + { + my ($proto, $parent, $title, $min, $max, $step) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($title, + $parent, + [qw/modal destroy-with-parent/], + @GCDialogs::okCancelButtons + ); + + my $label = Gtk2::Label->new($parent->{lang}->{DialogEnterNumber}); + $label->set_line_wrap(1); + $label->set_padding(5,0); + $self->{value} = new GCNumeric(($min + $max) / 2, $min, $max, $step); + + my $hboxRating = new Gtk2::HBox(1,10); + + $self->vbox->set_homogeneous(0); + $self->vbox->set_spacing(20); + $self->vbox->pack_start($label, 0, 0, 5); + $hboxRating->pack_start($self->{value}, 0, 0, 5); + $self->vbox->pack_start($hboxRating, 0, 0, 5); + $self->vbox->show_all; + + bless ($self, $class); + return $self; + } +} + +{ + package GCDependenciesDialog; + use base "Gtk2::Dialog"; + + use GCUtils 'glob'; + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + my $code = $self->run; + $self->hide; + } + + sub checkDependencies + { + my $self = shift; + + my $pref = 'GC'; + + my @optionals = (); + my $optionalsModules = {}; + + my @files = glob $ENV{GCS_LIB_DIR}.'/*.pm'; + + for my $component('GCPlugins', 'GCExport', 'GCImport', 'GCExtract') + { + foreach (glob $ENV{GCS_LIB_DIR}."/$component/*") + { + if (-d $_) + { + push @files, glob $ENV{GCS_LIB_DIR}."/$component/$_/*.pm"; + } + else + { + push @files, $_; + } + } + } + foreach my $file(@files) + { + open FILE, $file; + while () + { + if ( + ((/eval.*?[\"\']use\s*(.*?)[\"\'];/) && ($1 !~ /base|vars|locale|integer|^lib|utf8|\$opt|\$module|strict|^$pref/)) + || + (/checkModule\([\"\'](.*?)[\"\']\)/) + ) + #" + { + next if $1 eq 'Time::HiRes'; + push (@optionals, $1); + push @{$optionalsModules->{$1}}, $file; + } + + } + close FILE; + } + + my %saw; + @saw{@optionals} = (); + @optionals = sort keys %saw; + + $self->{tableDepend}->resize(1 + scalar(@optionals),2); + + my @missings = (); + my @oks = (); + foreach my $opt(sort @optionals) + { + my $label1 = new Gtk2::Label($opt); + my $label2 = new Gtk2::Label; + + $@ = ''; + eval "use $opt"; + if ($@) + { + my $value; + foreach my $module (@{$optionalsModules->{$opt}}) + { + $module =~ s/.*?GC([^\/]*?)\.pm$/$1/; + $value .= $module.",\n"; + } + $value =~ s/,\n$//; + $label2->set_markup("".$self->{parent}->{lang}->{InstallMissingFor}." $value"); + $label2->set_line_wrap(1); + $label2->set_justify('left'); + push @missings, [$label1, $label2]; + } + else + { + $label2->set_markup("".$self->{parent}->{lang}->{InstallOK}.""); + push @oks, [$label1, $label2]; + } + + } + + my $i = 0; + my $labelOpt = new Gtk2::Label; + $labelOpt->set_markup(''.$self->{parent}->{lang}->{InstallOptional}.''); + $self->{tableDepend}->attach($labelOpt, 0, 2, $i, $i+1, 'expand', 'fill', 0, $GCUtils::margin); + $i++; + + foreach (@missings) + { + $self->{tableDepend}->attach($_->[0], 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $self->{tableDepend}->attach($_->[1], 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + + $i++; + } + foreach (@oks) + { + $self->{tableDepend}->attach($_->[0], 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $self->{tableDepend}->attach($_->[1], 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + + $i++; + } + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent->{lang}->{InstallDependencies}, + $parent, + [qw/modal destroy-with-parent/], + 'gtk-ok' => 'ok' + ); + bless($self, $class); + + $self->{parent} = $parent; + + $self->{tableDepend} = new Gtk2::Table(1, 2, 0); + $self->{tableDepend}->set_row_spacings(10); + $self->{tableDepend}->set_col_spacings(20); + $self->{tableDepend}->set_border_width(10); + $self->{scrollDepend} = new Gtk2::ScrolledWindow; + $self->{scrollDepend}->set_policy ('automatic', 'automatic'); + $self->{scrollDepend}->add_with_viewport($self->{tableDepend}); + $self->{scrollDepend}->set_size_request(300, 200); + $self->vbox->pack_start($self->{scrollDepend},1,1,10); + + $self->checkDependencies; + + return $self; + } +} + +{ + package GCDateSelectionDialog; + use base "GCModalDialog"; + + sub show + { + my $self = shift; + $self->SUPER::show(); + + my $response = $self->run; + $self->hide; + return ($response eq 'ok'); + } + + sub date + { + my $self = shift; + if (@_) + { + $_ = shift; + return if ! $_; + my ($day, $month, $year); + ($day, $month, $year) = split m|/|; + ($day, $month, $year) = (01, 01, $_) if ! m|/|; + $self->{calendar}->select_month($month - 1, $year); + $self->{calendar}->select_day($day); + } + else + { + my ($year, $month, $day) = $self->{calendar}->get_date; + $day = ($day < 10 ? '0' : '').$day; + $month++; + $month = ($month < 10 ? '0' : '').$month; + return join '/', $day, $month, $year; + } + } + + sub new + { + my ($proto, $parent, $title) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $title || $parent->{lang}->{PanelDateSelect}); + + $self->{calendar} = new Gtk2::Calendar; + $self->{calendar}->signal_connect('day-selected-double-click' => sub { + $self->response('ok'); + }); + + $self->vbox->pack_start($self->{calendar}, 0, 0, 5); + $self->vbox->show_all; + + $self->set_default_size(1,1); + + bless ($self, $class); + return $self; + } +} + +{ + package GCPropertiesDialog; + + use Glib::Object::Subclass + Gtk2::Dialog:: + ; + + @GCPropertiesDialog::ISA = ('GCModalDialog'); + + sub checkValues + { + my $self = shift; + + return $self->{parent}->{lang}->{OptionsPicturesWorkingDirError} + if $self->{properties}->{images}->getValue =~ /.%WORKING_DIR%/; + return undef; + } + + sub show + { + my $self = shift; + + $self->SUPER::show(); + $self->show_all; + my $response; + while(1) + { + $response = $self->run; + last if $response ne 'ok'; + my $errorMessage = $self->checkValues; + last if !$errorMessage; + my $dialog = Gtk2::MessageDialog->new_with_markup($self->{parent}, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $errorMessage); + $dialog->run; + $dialog->destroy; + } + $self->hide; + return ($response eq 'ok'); + } + + sub setProperties + { + my ($self, $properties, $file, $count) = @_; + + foreach (keys %{$self->{properties}}) + { + $self->{properties}->{$_}->setValue($properties->{$_}); + } + $self->{info}->{file}->setValue($file); + $self->{info}->{items}->setValue($count); + $self->{info}->{size}->setValue(GCUtils::sizeToHuman((-s $file), + $self->{parent}->{lang}->{PropertiesFileSizeSymbols})); + } + + sub getProperties + { + my $self = shift; + + my %properties; + foreach (keys %{$self->{properties}}) + { + $properties{$_} = $self->{properties}->{$_}->getValue; + } + return \%properties; + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $title = Gtk2::Stock->lookup('gtk-properties')->{label}; + $title =~ s/_//g; + my $self = $class->SUPER::new($parent, + $title); + + $self->{parent} = $parent; + + my $table = new Gtk2::Table(14,4,0); + $table->set_row_spacings($GCUtils::halfMargin); + $table->set_col_spacings($GCUtils::halfMargin); + $table->set_border_width($GCUtils::margin); + + my $line = 0; + + my $fileGroupLabel = new GCHeaderLabel($parent->{lang}->{PropertiesFile}); + $table->attach($fileGroupLabel, 0, 4, $line, $line + 1, 'fill', 'fill', 0, 0); + $line++; + + my $fileLabel = new GCLabel($parent->{lang}->{PropertiesFilePath}); + $self->{info}->{file} = new GCShortText; + $self->{info}->{file}->lock(1); + $table->attach($fileLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{info}->{file}, 3, 4, $line, $line + 1, ['expand', 'fill'], 'fill', 0, 0); + $line++; + + $self->{info}->{itemsLabel} = new GCLabel($parent->{lang}->{PropertiesItemsNumber}); + $self->{info}->{items} = new GCShortText; + $self->{info}->{items}->lock(1); + $table->attach($self->{info}->{itemsLabel}, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{info}->{items}, 3, 4, $line, $line + 1, ['expand', 'fill'], 'fill', 0, 0); + $line++; + + my $sizeLabel = new GCLabel($parent->{lang}->{PropertiesFileSize}); + $self->{info}->{size} = new GCShortText; + $self->{info}->{size}->lock(1); + $table->attach($sizeLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{info}->{size}, 3, 4, 3, 4, ['expand', 'fill'], 'fill', 0, 0); + + $line += 3; + + my $collectionGroupLabel = new GCHeaderLabel($parent->{lang}->{PropertiesCollection}); + $table->attach($collectionGroupLabel, 0, 4, $line, $line + 1, 'fill', 'fill', 0, 0); + $line++; + + my $nameLabel = new GCLabel($parent->{lang}->{PropertiesName}); + $self->{properties}->{name} = new GCShortText; + $table->attach($nameLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{name}, 3, 4, $line, $line + 1, ['expand', 'fill'], 'fill', 0, 0); + $line++; + + my $langLabel = new GCLabel($parent->{lang}->{PropertiesLang}); + $self->{properties}->{lang} = new GCHistoryText; + my @langValues; + push @langValues, "$_ (".$GCLang::langs{$_}->{LangName}.')' + foreach (keys %GCLang::langs); + @langValues = sort @langValues; + $self->{properties}->{lang}->setValues(\@langValues); + $table->attach($langLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{lang}, 3, 4, $line, $line + 1, ['expand', 'fill'], 'fill', 0, 0); + $line++; + + my $ownerLabel = new GCLabel($parent->{lang}->{PropertiesOwner}); + $self->{properties}->{owner} = new GCShortText; + $table->attach($ownerLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{owner}, 3, 4, $line, $line + 1, ['expand', 'fill'], 'fill', 0, 0); + $line++; + + my $emailLabel = new GCLabel($parent->{lang}->{PropertiesEmail}); + $self->{properties}->{email} = new GCShortText; + $table->attach($emailLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{email}, 3, 4, $line, $line + 1, ['expand', 'fill'], 'fill', 0, 0); + $line++; + + my $descriptionLabel = new GCLabel($parent->{lang}->{PropertiesDescription}); + $self->{properties}->{description} = new GCLongText; + $table->attach($descriptionLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{description}, 3, 4, $line, $line + 1, ['expand', 'fill'], ['expand', 'fill'], 0, 0); + $line++; + + my $picturesDirLabel = new GCLabel($parent->{lang}->{OptionsImages}); + $self->{properties}->{images} = new GCFile($self, $parent->{lang}->{FileChooserOpenDirectory}, 'select-folder'); + $table->attach($picturesDirLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{images}, 3, 4, $line, $line + 1, ['expand', 'fill'], ['fill'], 0, 0); + $line++; + + my $defaultImageLabel = new GCLabel($parent->{lang}->{PropertiesDefaultPicture}); + $self->{properties}->{defaultImage} = new GCFile($self, $parent->{lang}->{FileChooserOpenFile}, 'open'); + $table->attach($defaultImageLabel, 2, 3, $line, $line + 1, 'fill', 'fill', 0, 0); + $table->attach($self->{properties}->{defaultImage}, 3, 4, $line, $line + 1, ['expand', 'fill'], ['fill'], 0, 0); + + $self->vbox->pack_start($table, 1, 1, 5); + $self->vbox->show_all; + + bless ($self, $class); + return $self; + } +} + +{ + package GCQueryReplaceDialog; + use base "GCModalDialog"; + + sub show + { + my $self = shift; + + $self->SUPER::show(); + $self->show_all; + my $response = $self->run; + $self->{field} = $self->{fieldsOption}->getValue; + $self->{oldValue} = $self->{old}->getValue; + $self->{newValue} = $self->{new}->getValue; + $self->{caseSensitive} = $self->{useCase}->getValue; + $self->hide; + return ($response eq 'ok'); + } + + sub setModel + { + my ($self, $model) = @_; + + $self->{model} = $model; + $self->{fieldsOption}->setModel($model); + } + + sub updateFields + { + my $self = shift; + + $self->{layoutTable}->remove($self->{old}); + ($self->{old}, undef) = $self->{fieldsOption}->createEntryWidget($self, 'eq', $self->{old}); + $self->{old}->signal_connect('activate' => sub {$self->response('ok')} ) + if $self->{old}->isa('GCShortText'); + $self->{layoutTable}->attach($self->{old}, 1, 2, 1, 2, 'fill', 'expand', 0, 0); + $self->{old}->show_all; + + $self->{layoutTable}->remove($self->{new}); + ($self->{new}, undef) = $self->{fieldsOption}->createEntryWidget($self, 'eq', $self->{new}); + $self->{new}->signal_connect('activate' => sub {$self->response('ok')} ) + if $self->{new}->isa('GCShortText'); + $self->{layoutTable}->attach($self->{new}, 1, 2, 2, 3, 'fill', 'expand', 0, 0); + $self->{new}->show_all; + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $title = Gtk2::Stock->lookup('gtk-find-and-replace')->{label}; + $title =~ s/_//g; + my $self = $class->SUPER::new($parent, + $title, + $parent->{lang}->{QueryReplaceLaunch} + ); + + $self->{parent} = $parent; + + # These ones are required for createWidget + $self->{lang} = $parent->{lang}; + $self->{options} = $parent->{options}; + + $self->{layoutTable} = new Gtk2::Table(4,2,0); + $self->{layoutTable}->set_row_spacings($GCUtils::halfMargin); + $self->{layoutTable}->set_col_spacings($GCUtils::margin); + $self->{layoutTable}->set_border_width($GCUtils::margin); + + my $fieldLabel = new Gtk2::Label($parent->{lang}->{QueryReplaceField}); + $fieldLabel->set_alignment(0,0.5); + $self->{fieldsOption} = new GCFieldSelector(0, undef, 0); + $self->{fieldsOption}->signal_connect('changed' => sub { + $self->updateFields; + }); + $self->{layoutTable}->attach($fieldLabel, 0, 1, 0, 1, 'fill', 'fill', 0, 0); + $self->{layoutTable}->attach($self->{fieldsOption}, 1, 2, 0, 1, 'fill', 'expand', 0, 0); + + my $oldLabel = new Gtk2::Label($parent->{lang}->{QueryReplaceOld}); + $oldLabel->set_alignment(0,0.5); + + $self->{old} = new GCShortText; + $self->{layoutTable}->attach($oldLabel, 0, 1, 1, 2, 'fill', 'fill', 0, 0); + $self->{layoutTable}->attach($self->{old}, 1, 2, 1, 2, 'fill', 'expand', 0, 0); + + my $newLabel = new Gtk2::Label($parent->{lang}->{QueryReplaceNew}); + $newLabel->set_alignment(0,0.5); + $self->{new} = new GCShortText; + $self->{layoutTable}->attach($newLabel, 0, 1, 2, 3, 'fill', 'fill', 0, 0); + $self->{layoutTable}->attach($self->{new}, 1, 2, 2, 3, 'fill', 'expand', 0, 0); + + $self->{useCase} = new GCCheckBox($parent->{lang}->{AdvancedSearchUseCase}); + $self->{layoutTable}->attach($self->{useCase}, 0, 2, 3, 4, 'fill', 'fill', 0, 0); + + $self->vbox->pack_start($self->{layoutTable}, 1, 1, 5); + $self->vbox->show_all; + + bless ($self, $class); + return $self; + } +} + +{ + #Class that is used to let user select + #item from a list and order them. + package GCDoubleListDialog; + + use base 'GCModalDialog'; + use GCGraphicComponents::GCDoubleLists; + + sub hideExtra + { + my $self = shift; + } + + sub clearList + { + my $self = shift; + $self->{doubleList}->clearList; + } + + sub show + { + my $self = shift; + + $self->{doubleList}->setListData($self->getData); + + $self->SUPER::show(); + $self->show_all; + $self->hideExtra; + + my $response = $self->run; + + if ($response eq 'ok') + { + $self->saveList($self->{doubleList}->getUsedItems); + } + $self->hide; + return $response; + } + + sub getDoubleList + { + my $self = shift; + + return $self->{doubleList}; + } + + sub new + { + my ($proto, $parent, $title, $withPixbuf, $unusedLabel, $usedLabel) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $title); + + bless ($self, $class); + + $self->{options} = $parent->{options}; + + $self->{doubleList} = new GCDoubleListWidget($withPixbuf, $unusedLabel, $usedLabel); + + $self->{marginBox} = new Gtk2::VBox; + $self->vbox->pack_start($self->{marginBox}, 0, 0, $GCUtils::halfMargin); + $self->vbox->pack_start($self->{doubleList}, 1, 1, 0); + + # Without some default size, everything will be shrinked as there are some scrollers + $self->set_default_size(200,400); + + return $self; + } +} + +{ + #Class that is used to let user select + #fields needed in export. + package GCFieldsSelectionDialog; + + use base 'GCModalDialog'; + use GCGraphicComponents::GCDoubleLists; + + sub hideExtra + { + my $self = shift; + } + + sub clearList + { + my $self = shift; + $self->{fieldsDoubleList}->clearList; + } + + sub show + { + my $self = shift; + + $self->{fieldsDoubleList}->setListData($self->{fieldsDoubleList}->getData); + + $self->SUPER::show(); + $self->show_all; + $self->hideExtra; + + my $response = $self->run; + +# if ($response eq 'ok') +# { +# $self->{parent}->{fields} +# $self->saveList($self->{fieldsDoubleList}->getUsedItems); +# } + $self->hide; + return $response eq 'ok'; + } + + sub getSelectedIds + { + my $self = shift; + return $self->{fieldsDoubleList}->getSelectedIds; + } + + sub getDoubleList + { + my $self = shift; + + return $self->{fieldsDoubleList}; + } + + sub saveList + { + my ($self, $list) = @_; + + my @array; + foreach (@{$list}) + { + push @array, $self->{fieldNameToId}->{$_}; + } + $self->{parent}->{fields} = \@array; + } + + sub addIgnoreField + { + my ($self, $ignoreField) = @_; + $self->{fieldsDoubleList}->addIgnoreField($ignoreField); + } + + sub removeIgnoreField + { + my ($self) = @_; + $self->{fieldsDoubleList}->removeIgnoreField; + } + + sub new + { + my ($proto, $parent, $title, $preList, $isIdList, $ignoreField) = @_; + + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $title); + + bless ($self, $class); + + $self->{options} = $parent->{options}; + + $self->{fieldsDoubleList} = new GCFieldsSelectionWidget($parent->{parent}, $preList, $isIdList, $ignoreField); + + $self->{marginBox} = new Gtk2::VBox; + $self->vbox->pack_start($self->{marginBox}, 0, 0, $GCUtils::halfMargin); + $self->vbox->pack_start($self->{fieldsDoubleList}, 1, 1, 0); + + # Without some default size, everything will be shrinked as there are some scrollers + $self->set_default_size(200,400); + + return $self; + } + +} + + +{ + package GCFileChooserDialog; + use GCGraphicComponents::GCBaseWidgets; + use File::Basename; + use File::Spec; + use Cwd 'realpath'; + + sub new + { + my ($proto, $title, $parent, $action, $withFilter, $autoAppend) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + $self->{action} = $action; + $self->{parent} = $parent; + $self->{ignoreFilter} = 1; + $self->{autoAppend} = 0; + my $dialog; + eval { $dialog = new Gtk2::FileChooserDialog($title, $parent, $action, @GCDialogs::okCancelButtons) }; + if ($@) + { + $self->{dialog} = new Gtk2::FileSelection($title); + $self->{dialog}->main_vbox->show_all; + my @vboxChildren = $self->{dialog}->main_vbox->get_children; + my @buttonBoxChildren = $vboxChildren[0]->get_children; + if ($action eq 'select-folder') + { + $buttonBoxChildren[1]->hide; + $buttonBoxChildren[2]->hide; + $self->{dialog}->selection_entry->hide; + $self->{dialog}->file_list->parent->hide; + } + elsif ($action eq 'open') + { + $self->{dialog}->hide_fileop_buttons; + $self->{dialog}->selection_entry->set_editable(0); + } + $self->{type} = 'old'; + } + else + { + $self->{dialog} = $dialog; + if ($action eq 'save') + { + $self->{requireOverwriteConfirmation} = 0; + eval { $dialog->set_do_overwrite_confirmation(1) }; + $self->{requireOverwriteConfirmation} = 1 if $@; + } + $self->{type} = 'new'; + if ($withFilter) + { + $self->{autoAppend} = $autoAppend; + my $filterAll; + $@ = ''; + eval '$filterAll = new Gtk2::FileFilter'; + if (!$@) + { + $self->{ignoreFilter} = 0; + $filterAll->set_name($self->{parent}->{lang}->{FileAllFiles}); + $filterAll->add_pattern('*'); + $self->{dialog}->add_filter($filterAll); + $self->{filters} = []; + } + } + $self->{dialog}->set_default_response ('ok'); + } + bless ($self, $class); + return $self; + } + + sub setTitle + { + my ($self, $title) = @_; + $self->{dialog}->set_title($title); + } + + sub transformFilename + { + my ($self, $file) = @_; + + #$file = GCUtils::pathToUnix($file); + if ($self->{autoAppend}) + { + my $tmpFilter = $self->{dialog}->get_filter; + if ($tmpFilter) + { + my $pattern = $self->{filtersPatterns}->{$tmpFilter->get_name}; + if ($pattern) + { + $pattern =~ s/^.*?([^*]*)$/$1/; + $file .= $pattern if $file !~ /\./; + } + } + } + return $file; + } + + sub get_filename + { + my $self = shift; + my $filename = $self->{dialog}->get_filename; + $filename .= (($^O =~ /win32/i) ? '\\' : '/') if ($self->{action} eq 'select-folder'); + #$filename .= '/' if ($self->{action} eq 'select-folder'); + return $self->transformFilename($filename); + } + + sub set_filename + { + my ($self, $file) = @_; + + $file ||= $ENV{HOME}; + my $dir = '.'; + if (! File::Spec->file_name_is_absolute( $file )) + { + $dir = dirname($self->{parent}->{options}->file) + if ($self->{parent}->{options}) + && ($self->{parent}->{options}->file); + } + my $empty = 0; + $file = $ENV{HOME}.'/' if !$file; + $file = $dir.'/'.$file if (! File::Spec->file_name_is_absolute( $file )); + $file =~ s/\//\\/g if ($^O =~ /win32/i); + $file = Cwd::realpath($file) + if -e $file; + $empty = 1 if $file eq ''; + eval { + $self->{dialog}->set_filename($file) if (!$empty && !(-d $file)); + $self->{dialog}->set_current_folder($file.'/') + if (($self->{type} eq 'new') && + (($empty) || (-d $file))); + }; + if ($self->{preview}) + { + $self->updatePreview($self) if ($self->{type} eq 'old'); + $self->{preview}->setValue($file) if ($self->{type} eq 'new'); + } + } + + sub set_pattern_filter + { + my ($self, @filters) = @_; + return if $self->{ignoreFilter}; + if ($self->{type} eq 'new') + { + $self->{dialog}->remove_filter($_) foreach(@{$self->{filters}}); + $self->{filters} = []; + $self->{filtersPatterns} = {}; + foreach my $filterPattern (@filters) + { + my $filter; + eval '$filter = new Gtk2::FileFilter'; + return if $@; + $filter->set_name($filterPattern->[0]); + if (ref($filterPattern->[1])) + { + # Filter pattern is an array. Use a custom filter so file extensions are not case sensitive + $filter->add_custom('filename', sub { + my ($filename, undef, $extension) = fileparse(shift->{filename},qr{\.[^\.]*}); + $extension =~ s/[^\.\w]//g; + $extension = lc($extension); + return (grep {$_ eq $extension} @{$filterPattern->[1]}); + }); + + } + else + { + # Filter pattern is single valid. Use a custom filter so file extensions are not case sensitive + $filter->add_custom('filename', sub { + my ($filename, undef, $extension) = fileparse(shift->{filename},qr{\..*}); + $extension =~ s/[^\.\w]//g; + $extension = lc($extension); + my $filterExt = $filterPattern->[1]; + $filterExt =~ s/^[^\.]*//; + return ($extension eq $filterExt); + }); + } + push @{$self->{filters}}, $filter; + $self->{dialog}->add_filter($filter); + $self->{filtersPatterns}->{$filterPattern->[0]} = $filterPattern->[1]; + } + $self->{dialog}->set_filter($self->{filters}->[0]) if $self->{filters}->[0]; + } + } + + sub run + { + my $self = shift; + return $self->{dialog}->run if ($self->{action} ne 'save') + || (($self->{action} eq 'save') + && (!$self->{requireOverwriteConfirmation})); + my $response; + while (1) + { + $response = $self->{dialog}->run; + return $response if ($response ne 'ok'); + my $filename = $self->get_filename; + if (-e $filename) + { + my $dialog = Gtk2::MessageDialog->new($self->{dialog}, + [qw/modal destroy-with-parent/], + 'question', + 'yes-no', + $self->{parent}->{lang}->{FileChooserOverwrite}); + + $dialog->set_position('center-on-parent'); + my $overwrite = $dialog->run; + $dialog->destroy; + return $response if ($overwrite eq 'yes'); + } + else + { + return $response; + } + } + } + + sub hide + { + my $self = shift; + return $self->{dialog}->hide; + } + + sub setWithImagePreview + { + my ($self, $value) = @_; + + if ($value) + { + $self->{preview} = new GCItemImage($self->{parent}->{options}, $self->{parent},1); + $self->{preview}->setImmediate; + if ($self->{type} eq 'new') + { + $self->{dialog}->signal_connect('update-preview' => \&updatePreview, $self); + $self->{dialog}->set_preview_widget($self->{preview}); + $self->{dialog}->set_preview_widget_active(1); + } + else + { + $self->{dialog}->file_list->signal_connect('cursor-changed' => \&updatePreview, $self); + $self->{dialog}->file_list->parent->parent->parent->pack_start($self->{preview},0,0,5); + } + $self->{preview}->show; + } + else + { + if ($self->{preview}) + { + $self->{dialog}->file_list->parent->parent->parent->remove($self->{preview}) if ($self->{type} eq 'old'); + $self->{preview}->destroy; + $self->{preview} = undef; + } + } + } + + sub updatePreview + { + my ($widget, $self, $other) = @_; + my $file; + if ($self->{type} eq 'new') + { + eval + { + $file = $self->{dialog}->get_preview_filename; + } + } + else + { + $file = $self->get_filename; + } + $self->{preview}->setValue($file) if $file; + } + + sub destroy + { + my $self = shift; + $self->{dialog}->destroy; + } +} + +{ + package GCItemWindow; + use base 'GCModalDialog'; + use GCGraphicComponents::GCBaseWidgets; + + sub show + { + my $self = shift; + + $self->SUPER::show(); + my $code = $self->run; + return $code; + } + + sub new + { + my ($proto, $parent, $title, @extraButtons) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, '', '', 1, @extraButtons); + bless ($self, $class); + + $self->set_position('none'); + my $options = new GCOptionLoader; + #$options->lockPanel(0); + $options->file($parent->{options}->file); + $self->{panel} = new GCFormPanel($parent, $options, $parent->{model}->getDefaultPanel); + $self->{panel}->createContent($parent->{model}); + + #Init combo boxes + foreach(@{$parent->{model}->{fieldsHistory}}) + { + $self->{panel}->{$_}->setValues($parent->{panel}->getValues($_)); + } + + my $scrollPanelItem = new Gtk2::ScrolledWindow; + $scrollPanelItem->set_policy ('automatic', 'automatic'); + $scrollPanelItem->set_shadow_type('none'); + $scrollPanelItem->add_with_viewport($self->{panel}); + + $self->vbox->add($scrollPanelItem); + + $self->vbox->show_all; + $self->{panel}->setShowOption($parent->getDialog('DisplayOptions')->{show}, 1); + + #Adjust some settings + $self->{panel}->disableAutoUpdate; + + $self->set_default_size($parent->{options}->itemWindowWidth,$parent->{options}->itemWindowHeight); + $self->setTitle($title); + return $self; + } + + sub setTitle + { + my ($self, $title) = @_; + $self->set_title($title.' - GCstar'); + } + +} + +{ + package GCRandomItemWindow; + use base 'GCItemWindow'; + + sub new + { + my ($proto, $parent, $title) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $title, + 'gtk-go-forward' => 'no'); + bless ($self, $class); + + $self->{panel}->deactivate; + + $parent->{tooltips}->set_tip(($self->action_area->get_children)[1], + $parent->{lang}->{RandomOkTip}); + $parent->{tooltips}->set_tip(($self->action_area->get_children)[0], + $parent->{lang}->{RandomNextTip}); + + $self->set_default_response('no'); + return $self; + } +} + +{ + package GCDefaultValuesWindow; + use base 'GCItemWindow'; + + sub new + { + my ($proto, $parent, $title) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $title); + bless ($self, $class); + + my $label = new GCLabel(''.$parent->{lang}->{DefaultValuesTip}.''); + $self->vbox->pack_start($label, 0, 0, $GCUtils::margin); + $self->vbox->reorder_child($label, 0); + $label->show_all; + + return $self; + } +} + +{ + package GCCriticalErrorDialog; + use base 'Gtk2::MessageDialog'; + + sub new + { + my ($proto, $parent, $message) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $message); + bless ($self, $class); + $self->set_position('center-on-parent'); + $self->{parent} = $parent; + $self->{message} = $message; + + my $label = $parent->{lang}->{MenuBugReport}; + $label =~ s/_//g; + my $bugReport = new Gtk2::Button($label); + $self->action_area->pack_start($bugReport,0,0,0); + $self->action_area->reorder_child($bugReport,0); + $bugReport->show_all; + $bugReport->signal_connect('clicked' => sub { + $self->reportBug; + }); + + return $self; + } + + sub show + { + my $self = shift; + $self->run; + $self->destroy; + } + + sub reportBug + { + my $self = shift; + my $subject = $self->{parent}->{lang}->{BugReportSubject}; + my $message = ' +'.$self->{parent}->{lang}->{BugReportVersion}.$self->{parent}->{lang}->{Separator}.$self->{parent}->{version}.' +'.$self->{parent}->{lang}->{BugReportPlatform}.$self->{parent}->{lang}->{Separator}.$^O.' + +'.$self->{parent}->{lang}->{BugReportMessage}.$self->{parent}->{lang}->{Separator}.$self->{message}.' + +'.$self->{parent}->{lang}->{BugReportInformation}.$self->{parent}->{lang}->{Separator}; + $self->{parent}->reportBug(undef, $subject, $message); + } +} + +1; diff --git a/lib/gcstar/GCDisplay.pm b/lib/gcstar/GCDisplay.pm new file mode 100644 index 0000000..41c9157 --- /dev/null +++ b/lib/gcstar/GCDisplay.pm @@ -0,0 +1,1146 @@ +package GCDisplay; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCUtils; + +{ + package GCFilterSearch; + + # Used to remove diacritics in test + use Unicode::Normalize 'NFKD'; + + sub compareExact + { + my ($field, $value) = @_; + return $field eq $value; + } + + sub compareContain + { + my ($field, $value) = @_; + return $field =~ m/\Q$value\E/; + } + + sub compareNotContain + { + my ($field, $value) = @_; + return $field !~ m/\Q$value\E/; + } + + sub compareRegexp + { + my ($field, $value) = @_; + return $field =~ m/$value/; + } + + sub compareLessStrings + { + my ($field, $value) = @_; + return $field lt $value; + } + + sub compareLessNumbers + { + my ($field, $value) = @_; + return 0 if !defined($field); + return $field < $value; + } + + sub compareLessOrEqualStrings + { + my ($field, $value) = @_; + return $field le $value; + } + + sub compareLessOrEqualNumbers + { + my ($field, $value) = @_; + return 0 if !defined($field); + return $field <= $value; + } + + sub compareGreaterStrings + { + my ($field, $value) = @_; + return $field gt $value; + } + + sub compareGreaterNumbers + { + my ($field, $value) = @_; + return 0 if !defined($field); + return $field > $value; + } + + sub compareGreaterOrEqualStrings + { + my ($field, $value) = @_; + return $field ge $value; + } + + sub compareGreaterOrEqualNumbers + { + my ($field, $value) = @_; + return 0 if !defined($field); + return $field >= $value; + } + + sub compareRangeStrings + { + my ($field, $value) = @_; + return 1 if $value eq ';'; + my @values = split m/;/, $value; + return ($field ge $values[0]) && ($field le $values[1]); + } + + sub compareRangeNumbers + { + my ($field, $value) = @_; + return 1 if $value eq ';'; + return 0 if !defined($field); + my @values = split m/;/, $value; + return ($field >= $values[0]) && ($field <= $values[1]); + } + + sub new + { + my ($proto, $info) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + + bless ($self, $class); + + $self->{info} = $info if $info; + $self->clear if !$info; + $self->setMode; + $self->setCase(0); + $self->setIgnoreDiacritics(0); + return $self; + } + + sub clear + { + my $self = shift; + + $self->{cleared} = 1; + + $self->{info} = {}; + $self->{currentSearch} = []; + } + + sub getComparisonFunction + { + my ($self, $type) = @_; + my ($comparison, $numeric) = @$type; + $numeric = 1 if $numeric eq 'true'; + $numeric = 0 if $numeric eq 'false'; + + if ($comparison eq 'eq') + { + return \&compareExact; + } + elsif ($comparison eq 'contain') + { + return \&compareContain; + } + elsif ($comparison eq 'notcontain') + { + return \&compareNotContain; + } + elsif ($comparison eq 'lt') + { + return \&compareLessStrings if (!$numeric); + return \&compareLessNumbers if ($numeric); + } + elsif ($comparison eq 'le') + { + return \&compareLessOrEqualStrings if (!$numeric); + return \&compareLessOrEqualNumbers if ($numeric); + } + elsif ($comparison eq 'gt') + { + return \&compareGreaterStrings if (!$numeric); + return \&compareGreaterNumbers if ($numeric); + } + elsif ($comparison eq 'ge') + { + return \&compareGreaterOrEqualStrings if (!$numeric); + return \&compareGreaterOrEqualNumbers if ($numeric); + } + elsif ($comparison eq 'range') + { + return \&compareRangeStrings if (!$numeric); + return \&compareRangeNumbers if ($numeric); + } + elsif ($comparison eq 'regexp') + { + return \&compareRegexp; + } + } + + sub setFilter + { + my ($self, $filter, $value, $type, $model, $add) = @_; + if (!$filter) + { + $self->clear; + return; + } + if ($value eq '') + { + delete $self->{info}->{$filter}; + } + else + { + $self->{cleared} = 0; + my $preprocess = $type->[2]; + if (!$preprocess) + { + my $fieldType = $model->{fieldsInfo}->{$filter}->{type}; + $preprocess = ($fieldType eq 'date') ? 'reverseDate' + : ($fieldType eq 'number') ? 'noNullNumber' + : ($fieldType eq 'single list') ? 'singleList' + : ($fieldType eq 'double list') ? 'doubleList' + : ($fieldType eq 'triple list') ? 'otherList' + : ($fieldType =~ /list$/o) ? 'singleList' + : ''; + } + my $info = { + value => $value, + comp => $self->getComparisonFunction($type), + preprocess => $preprocess, + temporary => $add + }; + if ($add) + { + push @{$self->{info}->{$filter}}, $info; + } + else + { + $self->{info}->{$filter} = [$info]; + } + push @{$self->{currentSearch}}, { + field => $filter, + value => $value, + filter => $type + }; + } + } + + sub removeTemporaryFilters + { + my $self = shift; + foreach (keys %{$self->{info}}) + { + foreach my $i(0 .. scalar @{$self->{info}->{$_}} - 1) + { + delete $self->{info}->{$_}->[$i] if $self->{info}->{$_}->[$i]->{temporary}; + } + } + } + + sub setModel + { + my ($self, $model) = @_; + + $self->{model} = $model; + } + + sub test + { + my ($self, $info) = @_; + return 1 if $self->{cleared}; + my $testAnd = $self->{mode} eq 'and'; + + foreach my $field(keys %{$self->{info}}) + { + my $value = ''; + if ($field eq $GCFieldSelector::anyFieldValue) + { + # We concatenate all of the values here to perform the + # the test on all of the fields in one shot + foreach my $key(keys %$info) + { + if (ref($info->{$key}) eq 'ARRAY') + { + $value .= GCPreProcess::otherList($info->{$key}); + } + else + { + $value .= $info->{$key}; + } + } + } + else + { + $value = $info->{$field}; + } + foreach my $filter(@{$self->{info}->{$field}}) + { + next if !$filter; + if ($filter->{preprocess}) + { + my $preProcess = 'GCPreProcess::'.$filter->{preprocess}; + eval { + no strict qw/refs/; + $value = $preProcess->($value); + }; + } + my $reference; + + if ($self->{ignoreDiacritics}) + { + # Transform diacritics into single characters + # e.g. é -> e; ç -> c + # First it normalizes the string to have 2 characters + # instead of only one. And then it removes the modifiers + ($reference = NFKD($filter->{value})) =~ s/\pm//g; + ($value = NFKD($value)) =~ s/\pm//g; + } + else + { + $reference = $filter->{value}; + } + if (!$self->{case}) + { + $reference = uc $reference; + $value = uc $value; + } + + if ($testAnd) + { + return 0 if ! $filter->{comp}->($value, $reference); + } + else + { + return 1 if $filter->{comp}->($value, $reference); + } + } + } + return $testAnd; + } + + sub setMode + { + my ($self, $mode) = @_; + $mode ||= 'and'; + #*test = \&testAnd if $mode eq 'and'; + #*test = \&testOr if $mode eq 'or'; + $self->{mode} = $mode; + } + + sub setCase + { + my ($self, $case) = @_; + $self->{case} = $case; + } + + sub setIgnoreDiacritics + { + my ($self, $id) = @_; + $self->{ignoreDiacritics} = $id; + } + + sub getCurrentSearch + { + my $self = shift; + return {mode => $self->{mode}, + info => $self->{currentSearch}, + case => $self->{case}, + ignoreDiacritics => $self->{ignoreDiacritics}}; + } + + sub isEmpty + { + my $self = shift; + return $self->{cleared}; + } +} + +use Gtk2; + +{ + package GCSearchDialog; + + use GCGraphicComponents::GCBaseWidgets; + + use base 'GCModalDialog'; + + sub initValues + { + my $self = shift; + + my $info = $self->{parent}->{filterSearch}->{info}; + + foreach (@{$self->{fields}}) + { + if (exists $info->{$_}) + { + $self->{$_}->setValue($info->{$_}->[0]->{value}); + } + else + { + $self->{$_}->clear if $self->{$_}; + } + if ($self->{fieldsInfo}->{$_}->{type} eq 'history text') + { + $self->{$_}->setValues($self->{parent}->{panel}->{$_}->getValues); + } + if ( + ( + ($self->{fieldsInfo}->{$_}->{type} eq 'single list') + || + ($self->{fieldsInfo}->{$_}->{type} eq 'double list') + ) + && + ( + $self->{parent}->{panel}->{$_}->{withHistory} + ) + ) + { + my @values; + foreach ($self->{parent}->{panel}->{$_}->getValues) + { + push @values, $_->[0]; + } + $self->{$_}->setValues(@values); + } + } + } + + sub show + { + my $self = shift; + + $self->initValues; + $self->SUPER::show(); + $self->show_all; + $self->activateOkButton($self->{notEmpty}); + $self->activateExtraButton($self->{notEmpty}); + $self->{search} = undef; + my $ended = 0; + while (!$ended) + { + my $response = $self->run; + if ($response eq 'ok') + { + my %info; + + foreach (@{$self->{fields}}) + { + $info{$_} = $self->{$_}->getValue + if ! $self->{$_}->isEmpty; + } + + $self->{parent}->{menubar}->initFilters(\%info); + + $self->{search} = \%info; + } + if (($response eq 'ok') || ($response eq 'cancel') || ($response eq 'delete-event')) + { + $ended = 1 + } + elsif ($response eq 'reject') + { + $self->clear; + } + } + $self->hide; + } + + sub clear + { + my $self = shift; + foreach (@{$self->{fields}}) + { + $self->{$_}->clear; + } + } + + sub search + { + my $self = shift; + + return $self->{search}; + } + + sub new + { + my ($proto, $parent, $specialOK, @extraButtons) = @_; + my $class = ref($proto) || $proto; + my $self; + if ($specialOK) + { + $self = $class->SUPER::new($parent, + $parent->{lang}->{SearchTitle}, + $specialOK, + ); + } + else + { + $self = $class->SUPER::new($parent, + $parent->{lang}->{SearchTitle}, + 'gtk-find', + 0, + @extraButtons, + 'gtk-clear' => 'reject', + ); + } + bless ($self, $class); + $self->set_position('center-on-parent'); + $self->{parent} = $parent; + + # These ones are required for createWidget + $self->{lang} = $parent->{lang}; + $self->{options} = $parent->{options}; + $self->{window} = $self; + + $self->{comparisonConvertor} = new GCComparisonSelector($parent); + $self->{layoutTable} = new Gtk2::Table(1,3,0); + $self->{layoutTable}->set_row_spacings($GCUtils::halfMargin); + $self->{layoutTable}->set_col_spacings($GCUtils::margin); + $self->{layoutTable}->set_border_width($GCUtils::margin); + + $self->vbox->pack_start($self->{layoutTable}, 1, 1, 0); + return $self; + } + + sub setModel + { + my ($self, $model) = @_; + $self->{model} = $model; + foreach ($self->{layoutTable}->get_children) + { + $self->{layoutTable}->remove($_); + $_->destroy; + } + + my $fieldsInfo = $model->{fieldsInfo}; + $self->{fieldsInfo} = $fieldsInfo; + my @filtersGroup = @{$model->{filtersGroup}}; + my @filtersTotal = @{$model->{filters}}; + + my $row = 0; + my $nbLines = @filtersTotal + (2 * @filtersGroup); + if ($nbLines <= 0) + { + $self->{notEmpty} = 0; + $self->{layoutTable}->resize(1, 1); + my $label = new GCLabel($self->{parent}->{lang}->{SearchNoField}); + $self->{layoutTable}->attach($label, 0, 1, 0, 1, 'expand', 'expand', $GCUtils::margin, $GCUtils::margin); + return; + } + $self->{notEmpty} = 1; + $self->{layoutTable}->resize($nbLines, 3); + + $self->{fields} = []; + foreach my $group(@filtersGroup) + { + $row++; + my @filters = @{$group->{filter}}; + my $label = new GCHeaderLabel($model->getDisplayedText($group->{label})); + $self->{layoutTable}->attach($label, 0, 3, $row, $row + 1, 'fill', 'expand', 0, 0); + $row++; + my $withComparisonLabel; + foreach my $filter(@filters) + { + my $field = $filter->{field}; + if ($field ne 'separator') + { + push @{$self->{fields}}, $field; + my $labelText = $fieldsInfo->{$field}->{displayed}; + $labelText = $model->getDisplayedText($filter->{label}) if $filter->{label}; + my $label = new GCLabel($labelText); + $self->{layoutTable}->attach($label, 0, 1, $row, $row + 1, 'fill', 'fill', 2 * $GCUtils::margin, 0); + + ($self->{$field}, $withComparisonLabel) = + GCBaseWidgets::createWidget($self, $fieldsInfo->{$field}, + $filter->{comparison}); + $self->{$field}->signal_connect('activate' => sub {$self->response('ok')} ) + if $self->{$field}->isa('GCShortText'); + if ($withComparisonLabel + && ($filter->{comparison} ne 'eq')) + { + my $labelComparison = new GCLabel( + $self->{comparisonConvertor}->valueToDisplayed($filter->{comparison}), + 1 + ); + $self->{layoutTable}->attach($labelComparison, 1, 2, $row, $row + 1, 'fill', 'fill', 0, 0); + } + $self->{layoutTable}->attach($self->{$field}, 2, 3, $row, $row + 1, ['fill', 'expand'], 'expand', 0, 0); + $self->{$field}->grab_focus if $row == 2; + } + else + { + $self->{layoutTable}->attach(Gtk2::HSeparator->new, 0, 3, $row, $row + 1, 'fill', 'fill', 0, 0); + } + $row++; + } + } + $self->{layoutTable}->show_all; + } +} + +{ + package GCAdvancedSearchDialog; + + use GCGraphicComponents::GCBaseWidgets; + + use base 'GCSearchDialog'; + + sub addItem + { + my $self = shift; + $self->{layoutTable}->resize($self->{nbFields} + 1, 3); + my $field = new GCFieldSelector(0, $self->{model}, 0, 1); + $field->{number} = $self->{nbFields}; + push @{$self->{fields}}, $field; + $self->{layoutTable}->attach($field, 0, 1, $self->{nbFields}, $self->{nbFields}+1, + ['expand', 'fill'], 'fill', 0, 0); + $field->show_all; + + my $comp = new GCComparisonSelector($self->{parent}); + $field->signal_connect('changed' => sub { + my ($fs, $cs) = @_; + $self->updateField($fs, $cs->getValue); + }, $comp); + $comp->signal_connect('changed' => sub { + my ($cs, $fs) = @_; + $self->updateField($fs, $cs->getValue); + }, $field); + push @{$self->{comps}}, $comp; + $self->{layoutTable}->attach($comp, 1, 2, $self->{nbFields}, $self->{nbFields}+1, + ['expand', 'fill'], 'fill', 0, 0); + $comp->show_all; + + my $value = new GCShortText; + push @{$self->{values}}, $value; + $self->{layoutTable}->attach($value, 2, 3, $self->{nbFields}, $self->{nbFields}+1, + ['expand', 'fill'], 'fill', 0, 0); + $value->show_all; + $self->{remove}->set_sensitive(1); + $self->{nbFields}++; + } + + sub removeItem + { + my $self = shift; + $self->{layoutTable}->remove(pop @{$self->{fields}}); + $self->{layoutTable}->remove(pop @{$self->{comps}}); + $self->{layoutTable}->remove(pop @{$self->{values}}); + delete $self->{isNumeric}->[$self->{nbFields}]; + $self->{layoutTable}->resize(--$self->{nbFields}, 3); + $self->{remove}->set_sensitive(0) if $self->{nbFields} < 2; + } + + sub generateSearch + { + my $self = shift; + my @info; + my $i = 0; + foreach (@{$self->{fields}}) + { + my $field = $_->getValue; + next if !$field; + my $numeric = 'false'; + if ($self->{model}->{fieldsInfo}->{$field}->{type} eq 'number') + { + $numeric = 'true'; + } + # We check we still have the same field in case it was changed + elsif ($self->{isNumeric}->[$i]->[0] eq $field) + { + $numeric = $self->{isNumeric}->[$i]->[1]; + } + push @info, { + field => $field, + value => $self->{values}->[$i]->getValue, + filter => [$self->{comps}->[$i]->getValue, + $numeric, + undef] + } + if ! $self->{values}->[$i]->isEmpty; + $i++; + } + $self->{search} = \@info; + } + + sub initSearch + { + my ($self, $filter) = @_; + if ($filter->{mode} eq 'and') + { + $self->{testAnd}->set_active(1); + } + elsif ($filter->{mode} eq 'or') + { + $self->{testOr}->set_active(1); + } + $self->{useCase}->set_active($filter->{case}); + $self->{ignoreDiacritics}->set_active($filter->{ignoreDiacritics}); + $self->removeItem while $self->{nbFields} > 1; + $self->{isNumeric} = []; + my $first = 1; + foreach my $line(@{$filter->{info}}) + { + $self->addItem if !$first; + $first = 0; + $self->{fields}->[-1]->setValue($line->{field}); + $self->{comps}->[-1]->setValue($line->{filter}->[0]); + $self->{values}->[-1]->setValue($line->{value}); + # We also add the field name to be able to check it later + push @{$self->{isNumeric}}, [$line->{field}, $line->{filter}->[1]]; + } + } + + sub show + { + my $self = shift; + + $self->show_all; + # If saving the search is not possible, hides the corresponding button + if (!$self->{canSave}) + { + ($self->action_area->get_children)[3]->hide; + } + $self->{search} = undef; + my $ended = 0; + while (!$ended) + { + my $response = $self->run; + if ($response eq 'ok') + { + if ($self->{userFilter}) + { + $self->saveSearch; + } + else + { + $self->generateSearch; + } + } + else + { + $self->{search} = undef; + } + $ended = 1 if ($response eq 'ok') || ($response eq 'cancel') || ($response eq 'delete-event'); + $self->clear if ($response eq 'reject'); + $self->saveSearch if ($response eq 'accept'); + } + $self->hide; + } + + sub getMode + { + my $self = shift; + return ($self->{testAnd}->get_active ? 'and' : 'or'); + } + + sub getCase + { + my $self = shift; + return ($self->{useCase}->get_active ? 1 : 0); + } + + sub getIgnoreDiacritics + { + my $self = shift; + return ($self->{ignoreDiacritics}->get_active ? 1 : 0); + } + + sub saveSearch + { + my $self = shift; + + my $response; + my $name; + if ($self->{userFilter} eq 'edit') + { + $response = 'ok'; + $name = ''; + } + else + { + my $dialog = new Gtk2::Dialog($self->{parent}->{lang}->{AdvancedSearchSaveTitle}, + $self, + [qw/modal destroy-with-parent/], + @GCDialogs::okCancelButtons + ); + + my $hbox = Gtk2::HBox->new(0, $GCUtils::halfMargin); + my $label = Gtk2::Label->new($self->{parent}->{lang}->{AdvancedSearchSaveName}); + $hbox->pack_start($label, 0, 0, 0); + my $entry = Gtk2::Entry->new; + $hbox->pack_start($entry, 1, 1, 0); + $hbox->show_all; + $dialog->vbox->pack_start($hbox, 1, 1, $GCUtils::margin); + $dialog->set_default_response('ok'); + $entry->set_activates_default(1); + while (1) + { + $response = $dialog->run; + if ($response eq 'ok') + { + $name = $entry->get_text; + if ($self->{model}->existsUserFilter($name)) + { + my $errorDialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $self->{parent}->{lang}->{AdvancedSearchSaveOverwrite}); + $dialog->set_position('center-on-parent'); + $errorDialog->run; + $errorDialog->destroy; + next; + } + last; + } + last; + } + $dialog->destroy; + } + + if ($response eq 'ok') + { + $self->generateSearch; + my $info = { + 'name' => $name, + 'mode' => $self->getMode, + 'case' => $self->getCase, + 'ignoreDiacritics' => $self->getIgnoreDiacritics, + 'info' => $self->{search} + }; + $self->{parent}->addUserFilter($info); + } + } + + sub new + { + my ($proto, $parent, $userFilter) = @_; + my $class = ref($proto) || $proto; + my $self; + if ($userFilter) + { + $self = $class->SUPER::new($parent, 'gtk-save'); + } + else + { + $self = $class->SUPER::new($parent, undef, 'gtk-save' => 'accept',); + } + bless ($self, $class); + + $self->{parent} = $parent; + $self->{userFilter} = $userFilter; + $self->vbox->remove($self->{layoutTable}); + + my $allTable = new Gtk2::Table(11,3,0); + $allTable->set_row_spacings($GCUtils::halfMargin); + $allTable->set_col_spacings($GCUtils::margin); + $allTable->set_border_width($GCUtils::margin); + + my $labelType = new GCHeaderLabel($parent->{lang}->{AdvancedSearchType}); + $self->{testAnd} = new Gtk2::RadioButton(undef, $parent->{lang}->{AdvancedSearchTypeAnd}); + $self->{testOr} = new Gtk2::RadioButton($self->{testAnd}->get_group, $parent->{lang}->{AdvancedSearchTypeOr}); + + my $prefStock = Gtk2::Stock->lookup('gtk-preferences'); + (my $prefLabel = $prefStock->{label}) =~ s/_//; + my $labelPreferences = new GCHeaderLabel($prefLabel); + $self->{useCase} = new GCCheckBox($parent->{lang}->{AdvancedSearchUseCase}); + $self->{ignoreDiacritics} = new GCCheckBox($parent->{lang}->{AdvancedSearchIgnoreDiacritics}); + + my $offset1 = 0; + $offset1 = 4; + $allTable->attach($labelType, 0, 3, $offset1 + 0, $offset1 + 1, 'fill', 'fill', 0, 0); + $allTable->attach($self->{testAnd}, 2, 3, $offset1 + 1, $offset1 + 2, 'fill', 'fill', 0, 0); + $allTable->attach($self->{testOr}, 2, 3, $offset1 + 2, $offset1 + 3, 'fill', 'fill', 0, 0); + $allTable->attach($labelPreferences, 0, 3, $offset1 + 4, $offset1 + 5, 'fill', 'fill', 0, 0); + $allTable->attach($self->{useCase}, 2, 3, $offset1 + 5, $offset1 + 6, 'fill', 'fill', 0, 0); + $allTable->attach($self->{ignoreDiacritics}, 2, 3, $offset1 + 6, $offset1 + 7, 'fill', 'fill', 0, 0); + + my $labelCriteria = new GCHeaderLabel($parent->{lang}->{AdvancedSearchCriteria}); + my $scrolled = new Gtk2::ScrolledWindow; + $scrolled->set_policy ('never', 'automatic'); + $scrolled->set_border_width(0); + $scrolled->set_shadow_type('none'); + $scrolled->add_with_viewport($self->{layoutTable}); + + my $offset2 = 8; + $offset2 = 0; + $allTable->attach($labelCriteria, 0, 3, $offset2 + 0, $offset2 + 1, 'fill', 'fill', 0, 0); + $allTable->attach($scrolled, 2, 3, $offset2 + 1, $offset2 + 2, ['expand', 'fill'], ['expand', 'fill'], 0, 0); + + my $hboxAction = new Gtk2::HBox(0,0); + $self->{add} = Gtk2::Button->new_from_stock('gtk-add'); + $self->{add}->signal_connect('clicked' => sub { + $self->addItem; + }); + $hboxAction->pack_start($self->{add}, 0, 0, 0); + $self->{remove} = Gtk2::Button->new_from_stock('gtk-remove'); + $self->{remove}->signal_connect('clicked' => sub { + $self->removeItem; + }); + $hboxAction->pack_start($self->{remove}, 0, 0, $GCUtils::margin); +# if (!$userFilter) +# { +# $self->{save} = Gtk2::Button->new_from_stock('gtk-save'); +# $self->{save}->signal_connect('clicked' => sub { +# $self->saveSearch; +# }); +# $hboxAction->pack_end($self->{save}, 0, 0, 0); +# } + $allTable->attach($hboxAction, 2, 3, $offset2 + 2, $offset2 + 3, 'fill', 'fill', 0, 0); + + $self->vbox->pack_start($allTable,1,1,0); + + $self->set_size_request(-1, 400); + return $self; + } + + sub clear + { + my $self = shift; + $self->setModel($self->{model}); + } + + sub setModel + { + my ($self, $model) = @_; + + $self->{model} = $model; + # Searches can only be saved for default collections or user collections with a name + # (i.e. when the model is not embedded within the collection). + $self->{canSave} = ($model->getName) ? 1 : 0; + $self->{nbFields} = 0; + $self->{fields} = []; + $self->{comps} = []; + $self->{values} = []; + foreach ($self->{layoutTable}->get_children) + { + $self->{layoutTable}->remove($_); + $_->destroy; + } + $self->addItem; + $self->{remove}->set_sensitive(0); + $self->{layoutTable}->show_all; + } + + sub updateField + { + my ($self, $fs, $comparison) = @_; + my $idx = $fs->{number}; + my $widget = $self->{values}->[$idx]; + $self->{layoutTable}->remove($widget); + + my $newWidget; + ($newWidget, undef) = $fs->createEntryWidget($self, $comparison, $widget); + $newWidget->signal_connect('activate' => sub {$self->response('ok')} ) + if $newWidget->isa('GCShortText'); + + $self->{values}->[$idx] = $newWidget; + $self->{layoutTable}->attach($newWidget, 2, 3, $idx, $idx+1, + ['expand', 'fill'], 'fill', 0, 0); + $newWidget->show_all; + } +} + +{ + package GCUserFiltersDialog; + + use GCGraphicComponents::GCBaseWidgets; + use Storable; + use base 'GCModalDialog'; + + sub setModel + { + my ($self, $model) = @_; + $self->{model} = $model; + $self->{filters} = Storable::dclone($model->getUserFilters); + $self->initList(1); + $self->{deletedFilters} = []; + } + + sub initList + { + my ($self, $saved) = shift; + @{$self->{filtersList}->{data}} = (); + $self->{initializing} = 1; + $self->{filters} = $self->getUserFilters; + #my @sorted = sort {uc($a->{name}) cmp uc($b->{name})} @{$self->{filters}}; + #$self->{filters} = \@sorted; + foreach(@{$self->{filters}}) + { + push @{$self->{filtersList}->{data}}, $_->{name}; + # All of them should be already saved + $_->{saved} = 1 if $saved; + } + $self->{initializing} = 0; + } + + sub getUserFilters + { + my $self = shift; + my @sorted = sort {uc($a->{name}) cmp uc($b->{name})} @{$self->{filters}}; + return \@sorted; + return $self->{filters}; + } + + sub getDeletedFilters + { + my $self = shift; + return $self->{deletedFilters}; + } + + sub show + { + my $self = shift; + + $self->SUPER::show(); + $self->show_all; + my $response = $self->run; + $self->hide; + return ($response eq 'ok'); + } + + # Callbacked by advanced search dialog + sub addUserFilter + { + my ($self, $filter) = @_; + $filter->{saved} = 0; + if ($self->{mode} eq 'new') + { + push (@{$self->{filters}}, $filter); + push @{$self->{filtersList}->{data}}, $filter->{name}; + } + else + { + my $selected = ($self->{filtersList}->get_selected_indices)[0]; + $filter->{name} = $self->{filters}->[$selected]->{name}; + $self->{filters}->[$selected] = $filter; + } + } + + sub newFilter + { + my $self = shift; + $self->{mode} = 'new'; + $self->{panel} = $self->{parent}->{panel}; + my $dialog = new GCAdvancedSearchDialog($self, $self->{mode}); + $dialog->setModel($self->{model}); + $dialog->show; + # To avoid unwanted reference if the panel is changed + delete $self->{panel}; + } + + sub editFilter + { + my $self = shift; + $self->{mode} = 'edit'; + my $selected = ($self->{filtersList}->get_selected_indices)[0]; + $self->{panel} = $self->{parent}->{panel}; + my $dialog = new GCAdvancedSearchDialog($self, $self->{mode}); + $dialog->setModel($self->{model}); + $dialog->initSearch($self->{filters}->[$selected]); + $dialog->show; + # To avoid unwanted reference if the panel is changed + delete $self->{panel}; + } + + sub deleteFilter + { + my $self = shift; + my $selected = ($self->{filtersList}->get_selected_indices)[0]; + push @{$self->{deletedFilters}}, $self->{filters}->[$selected]->{name}; + splice @{$self->{filters}}, $selected, 1; + splice (@{$self->{filtersList}->{data}}, $selected, 1); + $self->{filtersList}->select(0); + } + + sub renameFilter + { + my $self = shift; + my $selected = ($self->{filtersList}->get_selected_indices)[0]; + my $oldName = $self->{filters}->[$selected]->{name}; + my $newName = $self->{filtersList}->{data}->[$selected]->[0]; + return if $newName eq $oldName; + $self->{filters}->[$selected]->{name} = $newName; + $self->{filters}->[$selected]->{saved} = 0; + push @{$self->{deletedFilters}}, $oldName; + $self->initList(0); + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $parent->{lang}->{MenuSavedSearchesEdit}); + bless ($self, $class); + $self->set_position('center-on-parent'); + $self->{parent} = $parent; + $self->{lang} = $parent->{lang}; + my $hBox = new Gtk2::HBox(0,0); + $hBox->set_border_width($GCUtils::margin); + my $buttonBox = new Gtk2::VBox(0,0); + + my $editButton = new Gtk2::Button->new_from_stock('gtk-edit'); + $editButton->signal_connect('clicked' => sub { + $self->editFilter; + }); + my $newButton = new Gtk2::Button->new_from_stock('gtk-new'); + $newButton->signal_connect('clicked' => sub { + $self->newFilter; + }); + my $deleteButton = new Gtk2::Button->new_from_stock('gtk-delete'); + $deleteButton->signal_connect('clicked' => sub { + $self->deleteFilter; + }); + + $buttonBox->pack_start($newButton, 0, 0, $GCUtils::halfMargin); + $buttonBox->pack_start($deleteButton, 0, 0, $GCUtils::halfMargin); + $buttonBox->pack_start($editButton, 0, 0, $GCUtils::halfMargin); + + $self->{filtersList} = new Gtk2::SimpleList($parent->{lang}->{MenuSavedSearches} => 'text'); + $self->{filtersList}->set_column_editable(0, 1); + $self->{filtersList}->get_model->signal_connect("row-changed" => sub { + return if $self->{initializing}; + $self->renameFilter; + }); + + my $scroller = new Gtk2::ScrolledWindow; + $scroller->set_policy ('automatic', 'automatic'); + $scroller->set_shadow_type('etched-in'); + $scroller->add($self->{filtersList}); + + $hBox->pack_start($scroller, 1, 1, $GCUtils::margin); + $hBox->pack_start($buttonBox, 0, 0, $GCUtils::margin); + + $self->vbox->pack_start($hBox, 1, 1, 0); + $self->set_default_size(400, 400); + return $self; + } +} + +1; diff --git a/lib/gcstar/GCExport.pm b/lib/gcstar/GCExport.pm new file mode 100644 index 0000000..b32c58e --- /dev/null +++ b/lib/gcstar/GCExport.pm @@ -0,0 +1,118 @@ +package GCExport; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use File::Basename; +use GCUtils 'glob'; + +use base 'Exporter'; +our @EXPORT = qw(@exportersArray); + +our @exportersArray; + +sub loadExporters +{ + foreach (glob $ENV{GCS_LIB_DIR}.'/GCExport/*.pm') + { + my $export = basename($_, '.pm')."\n"; + next if $export =~ /GCExportBase/; + eval "use GCExport::$export"; + (my $exporter = $export) =~ s/^GCExport/GCExporter/; + my $obj; + eval "\$obj = new GCExport::$exporter"; + die "Fatal error with exporter $export\n$@" if $@; + push @exportersArray, $obj if ! $obj->{errors}; + } +} + +use Gtk2; +use GCExportImport; + +{ + package GCExportDialog; + + use Glib::Object::Subclass + Gtk2::Dialog:: + ; + + @GCExportDialog::ISA = ('GCExportImportDialog'); + + sub addOptions + { + my ($self, $options) = @_; + my $filter = ($self->{filter}->get_active) ? 1 : 0; + $options->{items} = $self->{parent}->{items}->getItemsListFiltered($filter); + $options->{collection} = $self->{parent}->{options}->file; + $options->{defaultImage} = $self->{parent}->{defaultImage}; + $options->{sorter} = $self->{sorter}->getValue; + $options->{order} = $self->{order}->getValue; + } + + sub setModel + { + my $self = shift; + $self->{fieldsDialog} = new GCFieldsSelectionDialog($self, $self->{parent}->{lang}->{ExportFieldsTitle}); + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $parent->{lang}->{ExportTitle}, 'export'); + bless ($self, $class); + + $self->{fieldsButtonLabel} = $parent->{lang}->{ExportFieldsTitle}; + $self->{fieldsTip} = $parent->{lang}->{ExportFieldsTip}; + $self->{filter} = new Gtk2::CheckButton($parent->{lang}->{ExportFilter}); + $self->{sortLabel} = GCLabel->new($parent->{lang}->{ExportSortBy}); + $self->{sorter} = new GCFieldSelector(0, undef, 0); + $self->{orderLabel} = GCLabel->new($parent->{lang}->{ExportOrder}); + my $ascStock = Gtk2::Stock->lookup('gtk-sort-ascending'); + (my $ascStockLabel = $ascStock->{label}) =~ s/_//; + my $descStock = Gtk2::Stock->lookup('gtk-sort-descending'); + (my $descStockLabel = $descStock->{label}) =~ s/_//; + $self->{order} = new GCMenuList([ + {value => 'asc', displayed => $ascStockLabel}, + {value => 'desc', displayed => $descStockLabel}, + ]); + $self->{dataTable}->resize(4, 2); + $self->{dataTable}->attach($self->{filter}, 0, 2, 0, 1, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{sortLabel}, 0, 1, 1, 2, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{sorter}, 1, 2, 1, 2, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{orderLabel}, 0, 1, 2, 3, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{order}, 1, 2, 2, 3, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{labelFile}, 0, 1, 3, 4, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{file}, 1, 2, 3, 4, ['fill', 'expand'], 'fill', 0, 0); + +# $self->vbox->pack_start(new Gtk2::HSeparator, 0, 0, 5); +# $self->vbox->pack_start($self->{filter},0,0,0); + + return $self; + } + +} + + +1; diff --git a/lib/gcstar/GCExport/GCExportBase.pm b/lib/gcstar/GCExport/GCExportBase.pm new file mode 100644 index 0000000..fb23ec2 --- /dev/null +++ b/lib/gcstar/GCExport/GCExportBase.pm @@ -0,0 +1,362 @@ +package GCExport::GCExportBase; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCExportImport; + +{ + package GCExport::GCExportBaseClass; + + use base 'GCExportImportBase'; + + use File::Basename; + use File::Copy; + use GCUtils 'glob'; + + #Methods to be overriden in specific classes + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + + bless ($self, $class); + return $self; + } + + sub getSuffix + { + return ''; + } + + sub getModels + { + return []; + } + + sub needsUTF8 + { + return 0; + } + + sub getOptions + { + } + + sub wantsDirectorySelection + { + return 0; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsImagesSelection + { + return 0; + } + + sub wantsFileSelection + { + return 1; + } + + sub getHeader + { + } + + sub getItem + { + } + + sub getFooter + { + } + + sub postProcess + { + } + + sub preProcess + { + } + + sub getEndInfo + { + } + + sub wantsOsSeparator + { + return 1; + } + + sub wantsSort + { + return 0; + } + + sub getNewPictureHeight + { + return 0; + } + + #End of methods to be overriden + + sub getUniqueImageFileName + { + my ($self, $suffix, $dir, $title) = @_; + + return $self->{options}->{parent}->getUniqueImageFileName($suffix, $title, $dir); + } + + sub duplicatePicture + { + my ($self, $orig, $field, $dir, $title, $newHeight) = @_; + $self->{saved}->{$field} = $orig; + my $newPic = $orig; + if ($orig && $self->{options}->{withPictures}) + { + $newPic = GCUtils::getDisplayedImage($orig, + $self->{options}->{defaultImage}, + $self->{original}); + if ($newPic eq $self->{options}->{defaultImage}) + { + $newPic = $self->{defaultImage}; + } + else + { + $newPic =~ /.*?(\.[^.]*)$/; + my $suffix = $1; + my $dest = $self->getUniqueImageFileName($suffix, + $dir, + $title); + my $picHeight = $self->getNewPictureHeight; + if ($picHeight) + { + my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($newPic); + my ($width, $height) = ($pixbuf->get_width, $pixbuf->get_height); + my $picWidth = $width * ($picHeight / $height); + $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf, $picWidth, $picHeight, 1); + my $format; + if ($suffix =~ /png/i) + { + $format = 'png'; + } + else + { + $dest =~ s/\.[^.]*$/\.jpg/; + $format = 'jpeg'; + } + $pixbuf->save($dest, $format); + } + else + { + copy($newPic, $dest); + } + $newPic = basename($dir).'/'.basename($dest); + } + } + else + { + $newPic = basename($dir).'/'.basename($self->{options}->{defaultImage}); + } + $newPic =~ s/\//\\/g if ($^O =~ /win32/i) && $self->wantsOsSeparator; + return $newPic; + } + + sub restorePicture + { + my $self = shift; + return $self->{saved}->{image}; + } + + sub restoreInfo + { + my ($self, $info) = @_; + + foreach (keys %{$self->{saved}}) + { + $info->{$_} = $self->{saved}->{$_}; + } + } + + sub transformValue + { + my ($self, $value, $field) = @_; + if ($self->{options}->{fieldsInfo}->{$field}->{type} eq 'image') + { + if ($self->{copyPictures}) + { + $value = $self->duplicatePicture($value, $field, + $self->{dirName}, + $self->{currentItem}->{ + $self->{model}->{commonFields}->{title} + }); + } + return $value; + } + return $self->{options}->{originalList}->transformValue($value, $field); + } + + sub getStockLabel + { + my ($self, $stock) = @_; + my $item = Gtk2::Stock->lookup($stock); + my $label = ''; + ($label = $item->{label}) =~ s/_// + if $item; + return $label; + } + + # If you need really specific processing, you can instead override the process method + sub process + { + my ($self, $options) = @_; + + $self->{saved} = {}; + $self->{currentItem} = undef; + + $self->{options} = $options; + + $options->{file} .= $self->getSuffix + if ($self->getSuffix) + && ($options->{file} !~ /\.\w*$/); + $self->{fileName} = $options->{file}; + $self->{original} = $options->{collection}; + $self->{origDir} = dirname($self->{original}); + $options->{collectionDir} = $self->{origDir}; + + ($self->{dirName} = $self->{fileName}) =~ s/\.[^.]*?$//; + $self->{dirName} .= '_images'; + if ( -e $self->{dirName}) + { + my @images = glob $self->{dirName}.'/*'; + unlink foreach (@images); + rmdir $self->{dirName}; + unlink $self->{dirName} if ( -e $self->{dirName}); + } + if ($self->{options}->{withPictures}) + { + mkdir $self->{dirName}; + #Get a copy of default picture + copy($self->{options}->{defaultImage},$self->{dirName}); + $self->{defaultImage} = basename($self->{dirName}).'/' + .basename($self->{options}->{defaultImage}); + } + + if (! $self->preProcess) + { + return $self->getEndInfo; + } + + my @tmpArray = @{$options->{items}}; + if ($self->wantsSort) + { + my $sorter = $self->{options}->{sorter}; + use locale; + if ($self->{model}->{fieldsInfo}->{$sorter}->{type} eq 'number') + { + @tmpArray = sort { + my $val1 = $a->{$sorter}; + my $val2 = $b->{$sorter}; + return $val1 <=> $val2; + } @tmpArray; + } + elsif ($self->{model}->{fieldsInfo}->{$sorter}->{type} eq 'date') + { + @tmpArray = sort { + my $val1 = GCPreProcess::reverseDate($a->{$sorter}); + my $val2 = GCPreProcess::reverseDate($b->{$sorter}); + return $val1 <=> $val2; + } @tmpArray; + } + else + { + @tmpArray = sort { + my $val1 = uc $self->{options}->{originalList}->transformValue($a->{$sorter}, $sorter); + my $val2 = uc $self->{options}->{originalList}->transformValue($b->{$sorter}, $sorter); + return $val1 cmp $val2; + } @tmpArray; + } + @tmpArray = reverse @tmpArray if $self->{options}->{order} eq 'desc'; + } + + $self->{sortedArray} = \@tmpArray; + + my $header = $self->getHeader($#tmpArray + 1); + my $body = ''; + + my $item; + my $idx = 0; + my $copyPictures = 0; + my @copiedPicturesFields; + if ($self->{options}->{withPictures}) + { + # If we don't specify fields, the pictures will be copied with transform value + # This one is used now + $copyPictures = 1 + if $self->wantsFieldsSelection; + # This one will be used by transform value + $self->{copyPictures} = !$copyPictures; + foreach my $field(@{$self->{options}->{fields}}) + { + push @copiedPicturesFields, $field + if $self->{options}->{fieldsInfo}->{$field}->{type} eq 'image'; + } + } + foreach $item(@tmpArray) + { + $self->{currentItem} = $item; + if ($copyPictures) + { + foreach my $pic(@copiedPicturesFields) + { + $item->{$pic} = $self->duplicatePicture($item->{$pic}, $pic, $self->{dirName}, + $item->{$self->{model}->{commonFields}->{title}}); + } + } + $body .= $self->getItem($item, $idx); + $self->restoreInfo($item); + $idx++; + } + $self->{currentItem} = undef; + my $footer = $self->getFooter($#tmpArray + 1); + + $self->postProcess(\$header, \$body); + + open EXPORTFILE, ">".$options->{file}; + binmode( EXPORTFILE, ':utf8') if $self->needsUTF8; + print EXPORTFILE "$header"; + print EXPORTFILE "$body"; + print EXPORTFILE "$footer"; + close EXPORTFILE; + + return $self->getEndInfo; + } +} + +1; diff --git a/lib/gcstar/GCExport/GCExportCSV.pm b/lib/gcstar/GCExport/GCExportCSV.pm new file mode 100644 index 0000000..c70fe01 --- /dev/null +++ b/lib/gcstar/GCExport/GCExportCSV.pm @@ -0,0 +1,198 @@ +package GCExport::GCExportCSV; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterCSV; + + use base qw(GCExport::GCExportBaseClass); + use Encode; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub getName + { + my $self = shift; + + return "CSV"; + } + + sub getOptions + { + my $self = shift; + + my $charsets = ''; + my @charsetList = Encode->encodings(':all'); + foreach (@charsetList) + { + $charsets .= $_.','; + } + + return [ + { + name => 'sep', + type => 'short text', + label => 'Separator', + default => ';' + }, + + { + name => 'rep', + type => 'short text', + label => 'Replacement', + default => ',' + }, + + { + name => 'charset', + type => 'options', + label => 'Charset', + valuesList => $charsets, + default => 'utf8', + }, + + { + name => 'withHeader', + type => 'yesno', + label => 'Header', + default => '1' + }, + + ]; + + } + + sub wantsFieldsSelection + { + return 1; + } + + sub wantsImagesSelection + { + return 1; + } + + sub wantsSort + { + return 1; + } + + sub needsUTF8 + { + my $self = shift; + return $self->{options}->{charset} eq 'utf8'; + } + + sub preProcess + { + my $self = shift; + return 1; + } + + sub transformValue + { + my ($self, $value, $field) = @_; + + if ($field) + { + $value = $self->SUPER::transformValue($value, $field); + } + $value =~ s/,+$//; + $value =~ s /$self->{options}->{sep}/$self->{options}->{rep}/g; + $value =~ s/\n|\r//g; + $value =~ s// /g; + $value = encode($self->{options}->{charset}, $value) + if $self->{options}->{charset} ne 'utf8'; + return $value; + } + + sub getHeader + { + my ($self, $number) = @_; + my $result = ''; + + if ($self->{options}->{withHeader}) + { + foreach (@{$self->{options}->{fields}}) + { + #my $column = $self->{options}->{lang}->{FieldsList}->{$_}; + my $column = $self->{model}->{fieldsInfo}->{$_}->{displayed}; + $result .= $self->transformValue($column).$self->{options}->{sep}; + } + $result =~ s/$self->{options}->{sep}$//; + $result .= "\n"; + } + + return $result; + } + + sub getItem + { + my ($self, $item, $number) = @_; + my $result; + foreach (@{$self->{options}->{fields}}) + { + my $value = $item->{$_}; + $result .= $self->transformValue($value, $_).$self->{options}->{sep}; + } + $result =~ s/$self->{options}->{sep}$//; + $result .= "\n"; + + return $result; + } + + sub getFooter + { + my $self = shift; + my $result; + + return $result; + } + + sub postProcess + { + my ($self, $header, $body) = @_; + } + + sub getEndInfo + { + my $self = shift; + my $message; + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCExport/GCExportExternal.pm b/lib/gcstar/GCExport/GCExportExternal.pm new file mode 100644 index 0000000..d5c096c --- /dev/null +++ b/lib/gcstar/GCExport/GCExportExternal.pm @@ -0,0 +1,182 @@ +package GCExport::GCExportExternal; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterExternal; + + use File::Copy; + use File::Basename; + use Cwd; + use XML::Simple; + use GCUtils 'glob'; + use GCBackend::GCBackendXmlParser; + use base qw(GCExport::GCExportBaseClass); + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent); + bless ($self, $class); + + $self->{useZip} = $self->checkOptionalModule('Archive::Zip'); + + return $self; + } + + sub wantsOsSeparator + { + return 0; + } + + sub transformPicturePath + { + my ($self, $path, $file, $item, $field) = @_; + return $self->duplicatePicture($path, + $field, + $self->{imageDir}, + $item->{$self->{model}->{commonFields}->{title}}); + } + + sub process + { + my ($self, $options) = @_; + $self->{parsingError} = ''; + $self->{options} = $options; + $self->{options}->{withPictures} = 1; + #$self->{fileName} = $options->{file}; + my $ext = ($self->{options}->{zip} ? 'gcz' : 'gcs'); + my $outFile = $options->{file}; + $outFile .= ".$ext" if ($outFile !~ m/\.$ext$/); + #$self->{fileName} .= '.gcs' if ($self->{fileName} !~ m/\.gcs$/); + $self->{fileName} = $outFile; + $self->{fileName} =~ s/z$/s/; + my $listFile = $self->{fileName}; + my $baseDir = dirname($listFile); + my $baseName = basename($listFile, '.gcs'); + my $imagesSubDir = $baseName.'_pictures'; + $self->{imageDir} = $baseDir.'/'.$imagesSubDir; + $self->{original} = $options->{collection}; + #$self->{original} =~ s/\\/\//g if ($^O =~ /win32/i); + $self->{origDir} = dirname($self->{original}); + + eval { + chdir $baseDir; + die 'Directory not writable' if !-w '.'; + mkdir $self->{imageDir}; + + $self->{currentDir} = getcwd; + + my $backend = new GCBackend::GCBeXmlParser($self); + $backend->setParameters(file => $listFile, + version => $self->{options}->{parent}->{version}, + wantRestore => 1, + standAlone => 1); + + my $result = $backend->save($options->{items}, + $options->{originalList}->getInformation, + undef); + + if ($result->{error}) + { + die $result->{error}->[1]; + } + }; + + if ($@) + { + $self->{parsingError} = GCUtils::formatOpenSaveError( + $self->{options}->{parent}->{lang}, + $self->{fileName}, + ['SaveError', $@] + ); + } + + if ($self->{options}->{zip}) + { + chdir $baseDir; + my $zip = Archive::Zip->new(); + $zip->addFile(basename($self->{fileName})); + $zip->addDirectory(basename($self->{imageDir})); + my @images = glob $imagesSubDir.'/*'; + $zip->addFile($_) foreach @images; + my $result = $zip->writeToFileNamed($outFile); + if ($result) + { + $self->{parsingError} = GCUtils::formatOpenSaveError( + $self->{options}->{parent}->{lang}, + $outFile, + ['SaveError', $@] + ); + } + else + { + # Cleanup to remove everything but the .gcz file + unlink $self->{fileName}; + unlink foreach (@images); + rmdir $imagesSubDir; + } + } + chdir; + return $self->getEndInfo; + } + + sub getOptions + { + my $self = shift; + my @options; + + if ($self->{useZip}) + { + push @options, { + name => 'zip', + type => 'yesno', + label => 'ZipAll', + default => '0' + }; + } + + return \@options; + } + +# sub getName +# { +# my $self = shift; +# +# return "External"; +# } + + sub getEndInfo + { + my $self = shift; + return ($self->{parsingError}, 'error') + if $self->{parsingError}; + + return ''; + } +} diff --git a/lib/gcstar/GCExport/GCExportHTML.pm b/lib/gcstar/GCExport/GCExportHTML.pm new file mode 100644 index 0000000..b083545 --- /dev/null +++ b/lib/gcstar/GCExport/GCExportHTML.pm @@ -0,0 +1,592 @@ +package GCExport::GCExportHTML; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterHTML; + + + use File::Copy; + use File::Basename; + use XML::Simple; + use base qw(GCExport::GCExportBaseClass); + use GCUtils 'glob'; + + our $FieldsList = 'GCSfields'; + our $GroupsList = 'GCSgroups'; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + $self->{genericModels} = 0; + + bless ($self, $class); + return $self; + } + + sub getName + { + my $self = shift; + + return "HTML"; + } + + sub getSuffix + { + my $self = shift; + + return ".html"; + } + + sub needsUTF8 + { + my $self = shift; + + return 1; + } + + sub getModels + { + my $self = shift; + + return []; + } + + sub setModelsDir + { + my $self = shift; + $self->{genericModelsDir} = $ENV{GCS_SHARE_DIR}.'/html_models/GCstar'; + if ($self->{model}) + { + $self->{modelsDir} = $ENV{GCS_SHARE_DIR}.'/html_models/'.$self->{model}->getName; + if ((! $self->{model}->getName) || (! -e $self->{modelsDir})) + { + $self->{modelsDir} = $self->{genericModelsDir}; + $self->{genericModels} = 1; + } + } + } + + sub getOptions + { + my $self = shift; + $self->{modelsFiles} = ''; + + $self->setModelsDir; + + my $defaultModel = ''; + $self->{isGeneric} = {}; + foreach (glob $self->{modelsDir}.'/*') + { + next if ($_ =~ /\/CVS$/) || ($_ =~ /\.png$/); + (my $mod = basename($_)) =~ s/_/ /g; + $self->{modelsFiles} .= $mod.','; + $defaultModel = $mod if !$defaultModel; + $self->{isGeneric}->{$mod} = $self->{genericModels}; + } + $self->{genericAdded} = 0; + if (!$self->{genericModels}) + { + # Previous one was specific, we also add the generic ones. + foreach (glob $self->{genericModelsDir}.'/*') + { + next if ($_ =~ /\/CVS$/) || ($_ =~ /\.png$/); + (my $mod = basename($_)) =~ s/_/ /g; + + next if exists $self->{isGeneric}->{$mod}; + $self->{modelsFiles} .= $mod.','; + $self->{isGeneric}->{$mod} = 1; + $self->{genericAdded} = 1; + } + } + $self->{modelsFiles} .= 'UseFile,'; + return [ + { + name => 'template', + type => 'options', + label => 'FileTemplate', + valuesList => $self->{modelsFiles}, + default => $defaultModel, + changedCallback => sub {shift; $self->checkFileField(@_)}, + buttonLabel => 'Preview', + buttonCallback => sub {shift; $self->preview(@_)} + }, + + { + name => 'modelFile', + type => 'file', + label => 'TemplateExternalFile', + default => '', + insensitive => 1, + }, + + { + name => 'title', + type => 'short text', + label => 'Title', + default => 'Items list', + }, + + { + name => 'imgHeight', + type => 'number', + label => 'HeightImg', + default => 160, + min => 50, + max => 500, + }, + + { + name => 'withJs', + type => 'yesno', + label => 'WithJS', + default => '1' + }, + + { + name => 'open', + type => 'yesno', + label => 'OpenFileInBrowser', + default => '0' + }, + + ] + } + + sub getNewPictureHeight + { + my $self = shift; + return $self->{options}->{imgHeight}; + } + + sub checkFileField + { + my ($self, $data) = @_; + my ($parent, $list) = @{$data}; + return if ! $parent->{options}->{modelFile}; + my $model = $list->getValue ; + $parent->{options}->{modelFile}->set_sensitive($model eq 'UseFile'); + $parent->{fieldsSelection}->set_sensitive($self->{isGeneric}->{$model}) + if $parent->{fieldsSelection}; + } + + sub preview + { + my ($self, $data) = @_; + my ($parent, $list) = @{$data}; + (my $template = $list->getValue) =~ s/ /_/g; + my $dialog = new Gtk2::Dialog($self->getLang->{Preview}.' - '.$list->getValue, + $parent, + [qw/modal destroy-with-parent/], + 'gtk-ok' => 'ok', + ); + + my $picFile; + if ($self->{isGeneric}->{$template}) + { + $picFile = $self->{genericModelsDir}.'/'.$template.'.png'; + } + else + { + $picFile = $self->{modelsDir}.'/'.$template.'.png'; + } + if (-f $picFile) + { + my $image = Gtk2::Image->new_from_file($picFile); + $image->set_padding(10,10); + $dialog->vbox->pack_start($image,0,0,0); + } + else + { + my $label = new Gtk2::Label; + $label->set_markup(''.$self->getLang->{NoPreview}.''); + $dialog->vbox->pack_start($label,1,1,0); + $dialog->set_default_size(300,300); + } + $dialog->vbox->show_all; + $dialog->run; + $dialog->destroy; + $parent->showMe; + } + + sub wantsFieldsSelection + { + my $self = shift; + return 1; + return $self->{genericAdded} || $self->{genericModels}; + } + + sub wantsImagesSelection + { + return 1; + } + + sub wantsOsSeparator + { + return 0; + } + + sub wantsSort + { + return 1; + } + + sub transformData + { + my ($self, $item, $field, $asATable) = @_; + + my $data = $item->{$field}; + if ($asATable) + { + return '' if !$data; + my $result = ''; + my $i = 1; + foreach (@{$data}) + { + my $class = ($i % 2) ? 'even' : 'odd'; + $result .= " \n"; + foreach my $item(@{$_}) + { + $result .= " $item\n"; + } + $result .= " \n"; + $i++; + } + return $result; + } + else + { + my $value = $self->transformValue($data, $field); + $value =~ s|\n|
|g; + return $value; + } + } + + sub getValues + { + my ($self, $values, $filter) = @_; + my $needFilter = (length($filter) > 2); + my @result; + if ($values eq $GroupsList) + { + # We generate the list of group for the selected fields + my %groups; + foreach (@{$self->{options}->{fields}}) + { + my $group = $self->{options}->{fieldsInfo}->{$_}->{group}; + $groups{$group} = 1; + } + foreach (@{$self->{model}->{groups}}) + { + my $group = $_->{id}; + push @result, $group if $groups{$group}; + } + } + else + { + # We could have a group name or a list of fields types + my $type; + my $group; + foreach (@{$self->{options}->{fields}}) + { + $type = $self->{options}->{fieldsInfo}->{$_}->{type}; + $group = $self->{options}->{fieldsInfo}->{$_}->{group}; + push @result, $_ + if ($type ne 'triple list') + && (($group =~ /^$values$/i) || ($values eq $FieldsList)) + && (!$needFilter || ($needFilter && ($filter =~ /$type/))); + } + } + return \@result; + } + + sub preProcess + { + my $self = shift; + + $self->{errors} = 0; + $self->setModelsDir; + my $template = $self->{options}->{template}; + my $file; + my $model; + if ($template eq 'UseFile') + { + $file = $self->{options}->{modelFile}; + if ( ! -e $file) + { + $self->{errors} = $self->getLang->{ModelNotFound}; + return 0; + } + } + else + { + $template =~ s/ /_/; + if ($self->{isGeneric}->{$template}) + { + $file = $self->{genericModelsDir}.'/'.$self->{options}->{template}; + } + else + { + $file = $self->{modelsDir}.'/'.$self->{options}->{template}; + } + + $file =~ s/"//g; + #" + } + # The problem should only happen when using command line, so a die is enough. + open FILE, $file or die "\nModel $template doesn't exist for this kind of collection"; + binmode(FILE, ':utf8' ); + $model = do { local $/; }; + close FILE; + + if ($model =~ /^/) + { + my $xs = XML::Simple->new; + my $meta = $xs->XMLin($model, + ForceArray => ['field']); + open FILE, $self->{genericModelsDir}.'/'.$meta->{model}; + binmode(FILE, ':utf8' ); + $model = do { local $/; }; + close FILE; + $self->{options}->{fields} = $meta->{fields}->{field}; + } + + if ($self->{options}->{withJs}) + { + $model =~ s/(\[JAVASCRIPT\])|(\[\/JAVASCRIPT\])//gms; + $model =~ s/\[NOJAVASCRIPT\].*?\[\/NOJAVASCRIPT\]//gms; + } + else + { + $model =~ s/\[JAVASCRIPT\].*?\[\/JAVASCRIPT\]//gms; + $model =~ s/(\[NOJAVASCRIPT\])|(\[\/NOJAVASCRIPT\])//gms; + } + + # If collection does not manage lendings, remove the LENDING blocks + $model =~ s|\[LENDING\](.*?)\[/LENDING\]| $self->{model}->{hasLending} ? $1 : '' |ems; + + #Loops + while ($model =~ m/\[LOOP([0-9]+)?\s+values=([^\s]*?)\s+idx=([^\s]*?)(\s+filter=([^\s]*?))?\]\n?(.*?)\n\s*\[\/LOOP\1\]/gms) + { + my $loopNumber = $1; + my $values = $2; + my $index = $3; + my $filter = ','.$5.','; + my $motif = $6; + my $valuesArray = $self->getValues($values, $filter); + my $string; + foreach my $value(@$valuesArray) + { + (my $line = $motif) =~ s/$index/$value/gms; + # For generic models, we add an img tag for images + # and an a tag for links + if (exists $self->{options}->{fieldsInfo}->{$value}) + { + # If this is an image + if ($self->{options}->{fieldsInfo}->{$value}->{type} eq 'image') + { + # We do it only if it is between 2 tags. + $line =~ s|>\$\$$value\$\$<|><|; + } + # If this is the item URL + elsif ($value eq $self->{model}->{commonFields}->{url}) + { + # We do it only if it is between 2 tags. + $line =~ s|>\$\$$value\$\$<|>\$\$$self->{model}->{commonFields}->{title}\$\$<|; + } + } + $string .= $line; + } + $model =~ s/(\n?)\s*\[LOOP$loopNumber\s+values=$values\s+idx=$index(\s+filter=$filter)?\].*?\[\/LOOP$loopNumber\]/$1$string/gms; + } + $model =~ s/TITLE_FIELD/$self->{model}->{commonFields}->{title}/eg; + $model =~ s/COVER_FIELD/$self->{model}->{commonFields}->{cover}/eg; + + $model =~ m{ + \[HEADER\]\n?(.*?)\n?\[\/HEADER\].*? + \[ITEM\]\n?(.*?)\n?\[\/ITEM\].*? + \[FOOTER\]\n?(.*?)\n?\[\/FOOTER\].*? + \[POST\]\n?(.*?)\n?\[\/POST\] + }xms; + $self->{header} = $1; + $self->{item} = $2; + $self->{footer} = $3; + $self->{post} = $4; + return 1; + } + + sub getHeader + { + my ($self, $total) = @_; + + my $result = $self->{header}; + + $self->{total} = $total; + $result =~ s/\$\$PAGETITLE\$\$/$self->{options}->{title}/g; + $result =~ s/\$\$TOTALNUMBER\$\$/$total/g; + $result =~ s/\$\$ITEMS\$\$/$self->{model}->getDisplayedItems/eg; + + #Search form + $result =~ s/\$\$FORM_INPUT\$\$/$self->getLang->{InputTitle}/eg; + $result =~ s/\$\$FORM_SEARCH1\$\$/$self->getLang->{SearchType1}/eg; + $result =~ s/\$\$FORM_SEARCH2\$\$/$self->getLang->{SearchType2}/eg; + $result =~ s/\$\$FORM_SEARCHBUTTON\$\$/$self->getLang->{SearchButton}/eg; + $result =~ s/\$\$FORM_SEARCHTITLE\$\$/$self->getLang->{SearchTitle}/eg; + $result =~ s/\$\$FORM_ALLBUTTON\$\$/$self->getLang->{AllButton}/eg; + $result =~ s/\$\$FORM_ALLTITLE\$\$/$self->getLang->{AllTitle}/eg; + $result =~ s/\$\$FORM_EXPAND\$\$/$self->getLang->{Expand}/eg; + $result =~ s/\$\$FORM_EXPANDTITLE\$\$/$self->getLang->{ExpandTitle}/eg; + $result =~ s/\$\$FORM_COLLAPSE\$\$/$self->getLang->{Collapse}/eg; + $result =~ s/\$\$FORM_COLLAPSETITLE\$\$/$self->getLang->{CollapseTitle}/eg; + + #Labels + $result =~ s/\$\$([a-zA-Z0-9_]*)_LABEL\$\$/$self->{model}->getDisplayedLabel($1)/eg; + + return $result."\n"; + } + + sub getFooter + { + my ($self, $item) = @_; + + my $total = $self->{total}; + my $result = $self->{footer}; + $result =~ s/\$\$PAGETITLE\$\$/$self->{options}->{title}/g; + $result =~ s/\$\$TOTALNUMBER\$\$/$total/g; + $result =~ s/\$\$GENERATOR_NOTE\$\$/$self->getLang->{Note}/eg; + $result =~ s/\$\$BORROWED_ITEMS\$\$/$self->{options}->{lang}->{BorrowedTitle}/g; + + return $result."\n"; + } + + sub getItem + { + my ($self, $item, $idx) = @_; + my $total = $self->{total}; + my $result = $self->{item}; + + #Separator + $result =~ s/\$\$SEPARATOR\$\$/$self->{options}->{lang}->{Separator}/g; + + #Labels that need a special process + $result =~ s/\$\$URL_LABEL\$\$/$self->{options}->{lang}->{PanelWeb}/g; + + #Other labels + $result =~ s/\$\$([a-zA-Z0-9_]*)_LABEL\$\$/$self->{model}->getDisplayedLabel($1)/eg; + + #Fields that need a special process + $result =~ s/\$\$HEIGHT_PIC\$\$/$self->{options}->{imgHeight}/g; + my $url = $item->{$self->{model}->{commonFields}->{url}} || '#'; + $result =~ s/\$\$URL\$\$/$url/g; + + #Borrower + my $borrowerField = $self->{model}->{commonFields}->{borrower}->{name}; + my $tmpBorrower = $item->{$borrowerField}; + my $borrowerFlag = 1; + my $borrowerYesNo = $self->getLang->{Borrowed}; + my $borrowerOrEmpty = $tmpBorrower; + if (!$tmpBorrower || ($tmpBorrower eq 'none')) + { + $tmpBorrower = $self->{options}->{lang}->{PanelNobody}; + $borrowerFlag = 0; + $borrowerYesNo = $self->getLang->{NotBorrowed}; + $borrowerOrEmpty = ''; + } + elsif ($tmpBorrower eq 'unknown') + { + $tmpBorrower = $self->{options}->{lang}->{PanelUnknown}; + } + $result =~ s/\$\$borrower\$\$/$tmpBorrower/g; + $result =~ s/\$\$borrower_OREMPTY\$\$/$borrowerOrEmpty/g; + $result =~ s/\$\$borrower_FLAG\$\$/$borrowerFlag/g; + $result =~ s/\$\$borrower_YESNO\$\$/$borrowerYesNo/g; + + $result =~ s/\$\$IDX\$\$/$idx/g; + $result =~ s/\$\$TOP\$\$/$self->getLang->{Top}/eg; + $result =~ s/\$\$BOTTOM\$\$/$self->getLang->{Bottom}/eg; + $result =~ s/\$\$TOTALNUMBER\$\$/$total/g; + + # Stock labels + $result =~ s/\$\$(gtk-[^\$]*)\$\$/$self->getStockLabel($1)/eg; + + #Multiple list displayed as a table + $result =~ s/\$\$([a-zA-Z0-9_]*)_TABLE\$\$/$self->transformData($item, $1, 1)/eg; + + #Other fields + #$result =~ s/\$\$([A-Z_]*)\$\$/$item->{lc $1}/eg; + $result =~ s/\$\$([a-zA-Z0-9_]*)\$\$/$self->transformData($item, $1, 0)/eg; + return $result."\n"; + } + + sub postProcess + { + my ($self, $headerRef, $bodyRef) = @_; + + #Variables to be used in POST section + my $header = $$headerRef; + my $body = $$bodyRef; + my @items = @{$self->{sortedArray}}; + + eval $self->{post}; + print "Errors with HTML template in POST:\n $@\n" if $@; + + $$headerRef = $header; + $$bodyRef = $body; + } + + sub getEndInfo + { + my $self = shift; + + if ($self->{errors}) + { + return ($self->{errors}, 'error'); + } + + my $message = ''; + + if ($self->{options}->{open}) + { + $self->{options}->{parent}->launch($self->{fileName}, 'url'); + } + else + { + $message = $self->getLang->{InfoFile}.$self->{fileName}; + $message .= ' + +'.$self->getLang->{InfoDir}.$self->{dirName} + if $self->{options}->{withPictures}; + } + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCExport/GCExportLatex.pm b/lib/gcstar/GCExport/GCExportLatex.pm new file mode 100644 index 0000000..0592908 --- /dev/null +++ b/lib/gcstar/GCExport/GCExportLatex.pm @@ -0,0 +1,204 @@ +package GCExport::GCExportLatex; +use utf8; + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterLatex; + + use base qw(GCExport::GCExportBaseClass); + + sub new { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub getName { + my $self = shift; + return "Latex"; + } + + sub getOptions { + my $self = shift; + return [ + { + name => 'one', + type => 'yesno', + label => 'Export One Media', + default => '0', + }, + { + name => 'disc', + type => 'number', + label => '# of Media', + default => '1', + min => '0', + max => '10000', + }, + ]; + + } + + sub wantsFieldsSelection { + return 0; + } + + sub wantsImagesSelection { + return 0; + } + + sub needsUTF8 { + return 1; + } + + sub preProcess { + my $self = shift; + return 1; + } + + sub transformValue { + my ($self, $value, $field) = @_; + + if ($field) { + $value = $self->SUPER::transformValue($value, $field); + } + $value =~ s/,+$//; + $value =~ s/\n|\r//g; + $value =~ s// /g; + $value =~ s/\^/\\^{}/g; + $value =~ s/\&/\\\&/g; + $value =~ s/\"/\'\'/g; + return $value; + } + + sub getHeader { + my ($self, $number) = @_; + my $result = ''; + $result = "\\documentclass[a4paper]{article} +\\usepackage{ucs} +\\usepackage[utf8]{inputenc} +\\usepackage[russian]{babel} +\\usepackage{geometry} +\\geometry{a4paper,top=1cm,bottom=1cm,left=1cm,right=1cm} +\\pagestyle{empty} +\\linespread{0.6} +\\sloppy + +\\newcommand{\\dvd}[2]{ +\\framebox[12cm]{ +\\begin{tabular}{p{0pt}\@{}p{11.9cm}} +\\rule[-6cm]{0pt}{11.7cm}&\\begin{minipage}{11.7cm} +{\\bf DVD #1} +\\begin{itemize} +\\setlength{\\parskip}{-3pt} +#2 +\\end{itemize}\\vspace{-3pt} +\\end{minipage} +\\end{tabular}}} + +\\begin{document} +\\footnotesize +"; + $result .= "\\dvd{$self->{options}->{disc}}{\n" + if $self->{options}->{one}; + return $result; + } + + sub getItem { + my ($self, $item, $number) = @_; + my $result; + return '' if ($self->{options}->{one} && + $item->{number} ne $self->{options}->{disc}); + $result .= '\item {\bf ' . $self->transformValue ($item->{title}, "title") . "}"; + $result .= ' / ' . $self->transformValue ($item->{original}, 'original') if $item->{original}; + $result .= " ($item->{date})" if $item->{date}; + # one line for russian cartoons + if ($self->transformValue ($item->{genre}, 'genre') =~ + m/Мультфильм/) { + $result .= ' м/Ñ„'; + } elsif ($item->{genre} || $item->{director} || + $item->{audio} || $item->{time}) { + $result .= "\\\\\n\\begin{tabular}{ll}\n"; + $result .= $self->getLocal('genre') . ': & ' . + $self->transformValue ($item->{genre}, 'genre') . '\\\\' + if $item->{genre}; + $result .= $self->getLocal('director') . ": & $item->{director}\\\\" + if $item->{director}; + my $audio = $self->transformValue ($item->{audio}, 'audio') + if $item->{audio}; + $audio =~ s/\([\w\ ]+\)//g; + $audio =~ s/\([\w\ ]+\)//g; + $audio =~ s/\ ,/,/g; + $audio =~ s/\s+$//g; + $result .= $self->getLocal('audio') . ": & $audio" if length ($audio) > 0; + $result .= "; " . $self->transformValue ($item->{subt}, 'subt') . + ' (' . $self->getLocal('subt') . ')' + if $item->{subt}; + $result .= '\\\\'; + $result .= $self->getLocal('time') . ": & $item->{time} мин.\\\\" if $item->{time}; + $result .= $self->getLocal('country') . ": & $item->{country}" if $item->{country}; + $result .= "\n\\end{tabular}\n"; + } + # don't include information about media # 0 + if ((!$self->{options}->{one}) && $item->{number} != 0) { + $self->{expdata}->{$item->{number}} .= $result; + $self->{expdata}->{all} .= $self->{expdata}->{all} ? ',' . $item->{number} : $item->{number} if $self->{expdata}->{all} !~ m/$item->{number}/; + return ''; + } elsif ($self->{options}->{one}) { + return $result; + } + return ''; + } + + sub getFooter { + my $self = shift; + my $result = ''; + if ($self->{options}->{one}) { + $result = "\n}\n\\end{document}\n"; + } else { + my @data = split (/,/, $self->{expdata}->{all}); + foreach my $key (sort @data) { + $result .= "\n\n\\dvd{$key}{\n$self->{expdata}->{$key}}"; + } + $result .= "\n\\end{document}\n"; + } + return $result; + } + + sub getLocal { + my ($self, $name) = @_; + # some abbreviations for russian language + if ($self->{options}->{lang}->{LangName} eq "Russian") { + return "Реж." if $name eq "director"; + return "Звук" if $name eq "audio"; + return "ВремÑ" if $name eq "time"; + return "Ñуб." if $name eq "subt"; + return $self->{model}->getDisplayedLabel($name); + } else { + return $self->{model}->getDisplayedLabel($name); + } + } + + sub getModels { + return ['GCfilms']; + } + + sub postProcess { + my ($self, $header, $body) = @_; + } + + sub getEndInfo { + my $self = shift; + my $message; + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCExport/GCExportPDB.pm b/lib/gcstar/GCExport/GCExportPDB.pm new file mode 100644 index 0000000..af1e4db --- /dev/null +++ b/lib/gcstar/GCExport/GCExportPDB.pm @@ -0,0 +1,295 @@ +package GCExport::GCExportPDB; + +################################################### +# +# Copyright 2009-2010 Andrew Ross +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterPDB; + + use base qw(GCExport::GCExportBaseClass); + use Encode; + + my @record_lengths; + + my $EPOCH_1904 = 2082844800; # Difference between Palm's + # epoch (Jan. 1, 1904) and + # Unix's epoch (Jan. 1, 1970), + # in seconds. + + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub getOptions + { + my $self = shift; + + return [ + { + name => 'dbname', + type => 'short text', + label => 'DatabaseName', + default => 'gcstar' + }, + ]; + + } + + sub wantsFieldsSelection + { + return 1; + } + + sub wantsImagesSelection + { + return 0; + } + + sub wantsSort + { + return 1; + } + + sub needsUTF8 + { + my $self = shift; + return 0; + } + + sub getSuffix + { + my $self = shift; + + return ".pdb"; + } + + sub preProcess + { + my $self = shift; + return 1; + } + + sub transformValue + { + my ($self, $value, $field) = @_; + + if ($field) + { + $value = $self->SUPER::transformValue($value, $field); + } + $value =~ s/,+$//; + $value =~ s/\n|\r//g; + $value =~ s// /g; + + return $value; + } + + sub getHeader + { + my ($self, $number) = @_; + my $result = ''; + + # clear the record lengths array + @record_lengths = (); + + # Add database title + my $name = $self->{options}->{'dbname'}; + if (length($name) > 31) + { + $name = substr($name, 0, 31); + } + while (length($name) < 32) + { + $name .= "\x00"; # pack out with null's + } + $result .= $name; + + # Add attribute flags (=0) + $result .= pack('n', 0); + + # Add file version (=0) + $result .= pack('n', 0); + + # Add dates for create time, modify time, backup time + # These dates are the number of seconds since 1st Jan 1904 + my $now = time() + $EPOCH_1904; + + $result .= pack('N', $now); + $result .= pack('N', $now); + $result .= pack('N', $now); + + # Add the Modification Number (=0) + $result .= pack('N', 0); + + # Add the offset to the Application Info + # offset calculated as: + # Title: 0x20 + # flags + version + 3 x dates 0x10 + # mod_number + app_offset 0x08 + # sortID + type 0x08 + # creator + seed 0x08 + # recordListID + cnt + 2byte 0x08 + # 8 bytes per record 8 * $number + $result .= pack('N', 0x50 + (8 * $number)); + + # Add null for the sortInfoID since we don't create a sortInfo + $result .= pack('N', 0); + + # Add the type + $result .= "DB00"; + + # Add the creator + $result .= "DBOS"; + + # add the uniqueIDseed = 0 + $result .= pack('N', 0); + + # Add the nextRecordListID = 0 when on disk + $result .= pack('N', 0); + + # add the record count + $result .= pack('n',$number); + + # The record offset table goes here, but is added in postProcess() + + # "Traditional" 2-byte gap to data + $result .= pack('n', 0); + + # Start the AppInfoID section + $result .= pack('N', 2); + + + # CHUNK_FIELD_NAMES (0) + $result .= pack('n',0); + my $fieldstring = ''; + foreach (@{$self->{options}->{fields}}) + { + my $column = $self->{model}->{fieldsInfo}->{$_}->{displayed}; + $fieldstring .= $self->transformValue($column)."\x00"; + } + $result .= pack('n', length($fieldstring)); + $result .= $fieldstring; + + # CHUNK_FIELD_TYPES (1) + $result .= pack('n',1); + $fieldstring = ''; + foreach (@{$self->{options}->{fields}}) + { + $fieldstring .= pack('n',0); + } + $result .= pack('n', length($fieldstring)); + $result .= $fieldstring; + + # CHUNK_LISTVIEW_OPTIONS (65) + $result .= pack('n',65); + $result .= pack('n',4); + $result .= pack('n',0); + $result .= pack('n',0); + + # CHUNK_LFIND_OPTIONS (128) + $result .= pack('n',128); + $result .= pack('n',2); + $result .= pack('n',0); + + return $result; + } + + sub getItem + { + my ($self, $item, $number) = @_; + my $result; + + my @lengths = (); + my $fieldstr; + foreach (@{$self->{options}->{fields}}) + { + my $value = $item->{$_}; + my $str = $self->transformValue($value, $_)."\x00"; + push (@lengths, length($str)); + $fieldstr .= $str; + } + + my $al = scalar(@lengths) * 2; + for(my $i=0;$i<=$#lengths;$i++) + { + $result .= pack('n', $al); + $al += $lengths[$i]; + } + $result .= $fieldstr; + push (@record_lengths, length($fieldstr)+(2 * scalar(@lengths))); + + return $result; + } + + sub getFooter + { + my $self = shift; + my $result; + + return $result; + } + + sub postProcess + { + my ($self, $header, $body) = @_; + + # add the index: + my $index = ""; + + my $numrecs = scalar(@record_lengths); + my $offset = length($$header) + (8*$numrecs); + + for (my $i=0;$i<$numrecs;$i++) + { + $index .= pack('N', $offset); + $index .= pack('n', 0); + $index .= pack('n', $i); + $offset += $record_lengths[$i]; + } + + # Insert the index into the header + $$header = substr($$header, 0, 0x4e).$index.substr($$header,0x4e); + } + + sub getEndInfo + { + my $self = shift; + my $message; + + return $message; + } + + +} + +1; diff --git a/lib/gcstar/GCExport/GCExportSQL.pm b/lib/gcstar/GCExport/GCExportSQL.pm new file mode 100644 index 0000000..5164d3b --- /dev/null +++ b/lib/gcstar/GCExport/GCExportSQL.pm @@ -0,0 +1,172 @@ +package GCExport::GCExportSQL; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterSQL; + use base qw(GCExport::GCExportBaseClass); + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + + bless ($self, $class); + return $self; + } + + sub getSuffix + { + my $self = shift; + + return ""; + } + + sub getOptions + { + my $self = shift; + + return [ + { + name => 'table', + type => 'short text', + label => 'TableName', + default => 'items' + }, + { + name => 'withDrop', + type => 'yesno', + label => 'WithDrop', + default => '1' + }, + { + name => 'withCreate', + type => 'yesno', + label => 'WithCreate', + default => '1' + }, + ] + } + + sub wantsFieldsSelection + { + return 1; + } + + sub wantsImagesSelection + { + return 1; + } + + sub getName + { + my $self = shift; + + return "SQL"; + } + + sub preProcess + { + my $self = shift; + return 1; + } + + sub getHeader + { + my ($self, $number) = @_; + + my $result = ''; + + if ($self->{options}->{withDrop}) + { + $result .= 'DROP TABLE '.$self->{options}->{table}.";\n"; + } + if ($self->{options}->{withCreate}) + { + $result .= 'CREATE TABLE '.$self->{options}->{table}.' ('; + + foreach (@{$self->{options}->{fields}}) + { + my $type = $self->{model}->{fieldsInfo}->{$_}->{type}; + my $format = 'TEXT'; + $format = 'NUMBER' if ($type eq 'number') || ($type eq 'yesno'); + $result .= "$_ $format, "; + } + $result =~ s/, $//; + $result .= ");\n"; + } + + return $result; + } + + sub getFooter + { + my $self = shift; + + my $result = "COMMIT;\n"; + return $result; + } + + sub getItem + { + my ($self, $item, $number) = @_; + my $result; + + $result = 'INSERT INTO '.$self->{options}->{table}.' ('; + my $values = ''; + foreach (@{$self->{options}->{fields}}) + { + $result .= "$_, "; + my $value = $self->transformValue($item->{$_}, $_); + $value =~ s/'/''/g; + #' + $values .= "'".$value."', "; + } + $result =~ s/, $//; + $values =~ s/, $//; + + $result .= ") VALUES ($values);\n"; + return $result; + } + + sub postProcess + { + my ($self, $value, $body) = @_; + + } + + sub getEndInfo + { + my $self = shift; + my $message = $self->getLang->{InfoFile}.$self->{fileName}; + return $message; + } +} + +1; \ No newline at end of file diff --git a/lib/gcstar/GCExport/GCExportTarGz.pm b/lib/gcstar/GCExport/GCExportTarGz.pm new file mode 100644 index 0000000..b8994d0 --- /dev/null +++ b/lib/gcstar/GCExport/GCExportTarGz.pm @@ -0,0 +1,174 @@ +package GCExport::GCExportTarGz; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterTarGz; + + use File::Copy; + use File::Basename; + use Cwd; + use XML::Simple; + use GCUtils 'glob'; + use GCBackend::GCBackendXmlParser; + use base qw(GCExport::GCExportBaseClass); + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent); + bless ($self, $class); + + $self->checkModule('Compress::Zlib'); + $self->checkModule('Archive::Tar'); + + return $self; + } + + sub wantsOsSeparator + { + return 0; + } + + sub transformPicturePath + { + my ($self, $path, $file, $item, $field) = @_; + return $self->duplicatePicture($path, + $field, + $self->{currentDir}.'/'.$self->{imageDir}, + $item->{$self->{model}->{commonFields}->{title}}); + } + + sub process + { + my ($self, $options) = @_; + $self->{parsingError} = ''; + $self->{options} = $options; + $self->{options}->{withPictures} = 1; + $self->{fileName} = $options->{file}; + $self->{fileName} .= '.tar.gz' if ($self->{fileName} !~ m/\.tar\.gz$/); + + my $listFile = 'collection.gcs'; + my $baseDir = 'tmp_items_tar_gz'; + my $imagesSubDir = 'images'; + $self->{imageDir} = $baseDir.'/'.$imagesSubDir; + $self->{original} = $options->{collection}; + #$self->{original} =~ s/\\/\//g if ($^O =~ /win32/i); + $self->{origDir} = dirname($self->{original}); + (my $tarfile = $self->{fileName}) =~ s/\.gz$//; + + eval { + chdir dirname($self->{fileName}); + die 'Directory not writable' if !-w '.'; + mkdir $baseDir; + mkdir $self->{imageDir}; + + $self->{currentDir} = getcwd; + + my $backend = new GCBackend::GCBeXmlParser($self); + $backend->setParameters(file => $baseDir.'/'.$listFile, + version => $self->{options}->{parent}->{version}, + wantRestore => 1, + standAlone => 1); + + my $result = $backend->save($options->{items}, + $options->{originalList}->getInformation, + undef); + + if ($result->{error}) + { + die $result->{error}->[1]; + } + + chdir $self->{currentDir}; + + my $tar = Archive::Tar->new(); + chdir $baseDir; + + $tar->add_files($listFile, $imagesSubDir); + my @images = glob $imagesSubDir.'/*'; + $tar->add_files($_) foreach (@images); + $tar->write($tarfile); + + my $gz = Compress::Zlib::gzopen($self->{fileName}, "wb"); + $gz or die 'Cannot write'; + open(TAR, $tarfile) or die "Cannot open $tarfile"; + binmode(TAR); + my $buff; + while (read(TAR, $buff, 8 * 2**10)) + { + $gz->gzwrite($buff); + } + $gz->gzclose; + close TAR; + unlink foreach (@images); + }; + + if ($@) + { + $self->{parsingError} = GCUtils::formatOpenSaveError( + $self->{options}->{parent}->{lang}, + $self->{fileName}, + ['SaveError', $@] + ); + } + + eval { + unlink $listFile; + rmdir $imagesSubDir; + chdir '..'; + rmdir $baseDir; + $tarfile =~ s/\\/\//g if ($^O =~ /win32/i); + unlink $tarfile; + }; + return $self->getEndInfo; + } + + sub getOptions + { + my $self = shift; + my @options; + return \@options; + } + + sub getName + { + my $self = shift; + + return ".tar.gz"; + } + + sub getEndInfo + { + my $self = shift; + return ($self->{parsingError}, 'error') + if $self->{parsingError}; + + return ($self->getLang->{Info}.$self->{fileName}, 'info'); + } +} diff --git a/lib/gcstar/GCExport/GCExportTellico.pm b/lib/gcstar/GCExport/GCExportTellico.pm new file mode 100644 index 0000000..2bac594 --- /dev/null +++ b/lib/gcstar/GCExport/GCExportTellico.pm @@ -0,0 +1,512 @@ +package GCExport::GCExportTellico; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterTellico; + + use base qw(GCExport::GCExportBaseClass); + use GCUtils; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + + $self->checkModule('MIME::Base64'); + $self->checkModule('Digest::MD5'); + + #List of collections: http://www.periapsis.org/tellico/doc/collection-type-values.html + # [ entryTitle, type, extra fields ] + $self->{models} = { + GCbooks => ['Books', '2', ''], + GCfilms => ['Videos', '3', ''], + GCmusics => ['Music', '4', ''], + GCcoins => ['Coin', '8', ''], + GCgames => ['Games', '11', ''] + }; + + return $self; + } + + sub getName + { + my $self = shift; + + return "Tellico"; + } + + sub getModels + { + my $self = shift; + + my @models = keys %{$self->{models}}; + return \@models; + } + + sub needsUTF8 + { + my $self = shift; + + return 1; + } + + sub getOptions + { + my $self = shift; + + return []; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub preProcess + { + my $self = shift; + + $self->{imagesInfos} = {}; + return 1; + } + + sub getHeader + { + my ($self, $number) = @_; + my $result; + + my $model = $self->{model}; + my $title = $model->getDescription; + my $info = $self->{models}->{$model->getName}; + + $result = ' + + + + + + '.$info->[2].' + +'; + + return $result; + } + + sub transformData + { + my ($self, $data) = @_; + + $data =~ s/&/&/g; + + return $data; + } + + sub transformList + { + my ($self, $list, $tag) = @_; + + my $result = ''; + if (ref($list) eq 'ARRAY') + { + foreach (@{$list}) + { + $result .= " <$tag>".$self->transformData($_->[0]) + ."\n"; + } + } + else + { + foreach (split ',', $list) + { + s/;.*$//; + $result .= " <$tag>".$self->transformData($_)."\n"; + } + } + return $result; + } + + sub encodeImage + { + my ($self, $file) = @_; + my $image = GCUtils::getDisplayedImage($file, $self->{options}->{defaultImage}, $self->{original}); + (my $suffix = $image) =~ s/.*?\.([^.]*)$/$1/; + $suffix = 'jpeg' if $suffix eq 'jpg'; + open PIC, "<$image" or return (undef,undef,undef); + my $data = do {local $/; }; + close PIC; + my $pictureId = Digest::MD5::md5_hex($data).'.'.$suffix; + my %infos; + $infos{id} = $pictureId; + $infos{format} = uc $suffix; + $infos{width} = 120; + $infos{height} = 160; + $infos{data} = MIME::Base64::encode_base64($data); + return \%infos; + } + + sub getItem + { + my ($self, $item, $number) = @_; + + my $methodName = 'get'.$self->{model}->getName.'Item'; + + return $self->$methodName($item); + } + + sub getGCfilmsItem + { + my ($self, $movie, $number) = @_; + my $result; + + #(my $synopsis = $movie->{synopsis}) =~ s/
/\n/gm; + #(my $comments = $movie->{comment}) =~ s/
/\n/gm; + + use integer; + my $rating = $movie->{rating} / 2; + no integer; + + my $age = $movie->{age}; + my $certification; + + if ($age == 1) + { + $certification = 'U (USA)'; + } + elsif ($age == 2) + { + $certification = 'G (USA)'; + } + elsif ($age <= 5) + { + $certification = 'PG (USA)'; + } + elsif ($age <= 13) + { + $certification = 'PG-13 (USA)'; + } + elsif ($age <= 17) + { + $certification = 'R (USA)'; + } + + my $imageInfos = $self->encodeImage($movie->{image}); + $self->{imagesInfos}->{$imageInfos->{id}} = $imageInfos; + + my $year = GCPreProcess::extractYear($movie->{date}); + + $result = ' + '.$self->transformData($movie->{title}).' + '.$self->transformData($movie->{format}).' + '.$year.' + '.$certification.' + +'; + $result .= $self->transformList($movie->{genre}, 'genre'); + $result .= ' + + '.$self->transformData($movie->{country}).' + + +'; + foreach (split ',', $movie->{actors}) + { + $result .= " ".$self->transformData($_)."\n"; + } + $result .= ' + + '.$self->transformData($movie->{director}).' + + +'; + $result .= $self->transformList($movie->{audio}, 'language'); + $result .= ' + '.$self->transformData($movie->{time}).' + '.$self->transformData($movie->{synopsis}).' + '.$rating.' + '.$self->transformData($movie->{comments}).' +'; + if (($movie->{borrower}) && ($movie->{borrower} ne 'none')) + { + $result .= ' true +'; + } + + $result .= ' '.$imageInfos->{id}.' +'; + + $result .= ' +'; + + return $result; + } + + sub getGCgamesItem + { + my ($self, $item, $number) = @_; + my $result; + + use integer; + my $rating = $item->{rating} / 2; + no integer; + + my $imageInfos = $self->encodeImage($item->{boxpic}); + $self->{imagesInfos}->{$imageInfos->{id}} = $imageInfos; + + my $year = GCPreProcess::extractYear($item->{released}); + + $result = ' + '.$self->transformData($item->{name}).' + '.$self->transformData($item->{platform}).' + '.$self->transformData($item->{description}).' + '.$year.' + '.$self->transformData($item->{added}).' + +'; + $result .= $self->transformList($item->{genre}, 'genre'); + $result .= ' + + '.$self->transformData($item->{editor}).' + + '.$rating.' +'; + if (($item->{borrower}) && ($item->{borrower} ne 'none')) + { + $result .= ' true +'; + } + if ($item->{completion} >= 100) + { + $result .= ' true +'; + } + + $result .= ' '.$imageInfos->{id}.' +'; + + $result .= ' +'; + + return $result; + } + + sub getGCbooksItem + { + my ($self, $item, $number) = @_; + my $result; + + use integer; + my $rating = $item->{rating} / 2; + no integer; + + my $imageInfos = $self->encodeImage($item->{cover}); + $self->{imagesInfos}->{$imageInfos->{id}} = $imageInfos; + + my $year = GCPreProcess::extractYear($item->{publication}); + + $result = ' + '.$self->transformData($item->{title}).' + '.$self->transformData($item->{isbn}).' + '.$self->transformData($item->{serie}).' + '.$self->transformData($item->{edition}).' + '.$self->transformData($item->{format}).' + '.$self->transformData($item->{description}).' + '.$self->transformData($item->{pages}).' + '.$self->transformData($item->{acquisition}).' + '.$year.' + '.$self->transformData($item->{publisher}).' + +'; + $result .= $self->transformList($item->{authors}, 'author'); + $result .= ' + +'; + $result .= $self->transformList($item->{language}, 'language'); + $result .= ' + +'; + $result .= $self->transformList($item->{genre}, 'genre'); + $result .= ' + '.$rating.' +'; + if (($item->{borrower}) && ($item->{borrower} ne 'none')) + { + $result .= ' true +'; + } + if ($item->{read}) + { + $result .= ' true +'; + } + + $result .= ' '.$imageInfos->{id}.' +'; + + $result .= ' +'; + + return $result; + } + + sub getGCmusicsItem + { + my ($self, $item, $number) = @_; + my $result; + + use integer; + my $rating = $item->{rating} / 2; + no integer; + + my $imageInfos = $self->encodeImage($item->{cover}); + $self->{imagesInfos}->{$imageInfos->{id}} = $imageInfos; + + my $year = GCPreProcess::extractYear($item->{release}); + + $result = ' + '.$self->transformData($item->{title}).' + '.$self->transformData($item->{format}).' + '.$year.' + + '.$self->transformData($item->{comment}).' + +'; + $result .= $self->transformList($item->{artist}, 'artist'); + $result .= ' + +'; + $result .= $self->transformList($item->{genre}, 'genre'); + $result .= ' + '.$rating.' + '; + foreach (@{$item->{tracks}}) + { + $result .= ' + + '.$self->transformData($_->[1]).' + '.$self->transformData($item->{artist}).' + '.$self->transformData($_->[2]).' + ' + } + $result .= ' + +'; + + + if (($item->{borrower}) && ($item->{borrower} ne 'none')) + { + $result .= ' true +'; + } + $result .= ' '.$imageInfos->{id}.' +'; + + $result .= ' +'; + + return $result; + } + + sub getGCcoinsItem + { + my ($self, $item, $number) = @_; + my $result; + + my $frontInfos = $self->encodeImage($item->{front}); + $self->{imagesInfos}->{$frontInfos->{id}} = $frontInfos; + my $backInfos = $self->encodeImage($item->{back}); + $self->{imagesInfos}->{$backInfos->{id}} = $backInfos; + + $result = ' + '.$self->transformData($item->{name}).' + '.$self->transformData($item->{currency}).' + '.$self->transformData($item->{value}).' + '.$self->transformData($item->{year}).' + '.$self->transformData($item->{country}).' + '.(($item->{type} eq 'coin') ? 'true' : 'false').' + '.$self->transformData($item->{added}).' + '.$self->transformData($item->{estimate}).' + '.$self->transformData($item->{location}).' + '.$self->transformData($item->{comments}).' + '.$frontInfos->{id}.' + '.$backInfos->{id}.' + +'; + return $result; + } + + sub getFooter + { + my $self = shift; + my $result; + + $result = ' +'; + foreach (values %{$self->{imagesInfos}}) + { + $result .= ' '. + $_->{data}.''; + } + $result .=' +
+
+'; + + return $result; + } + + # postProcess + # Called after all processing. Use it if you need to perform extra stuff on the header. + # $header is a reference to the header string. + sub postProcess + { + my ($self, $header, $body) = @_; + + # Your code here + # As header is a reference, it can be modified on place with $$header + } + + # getEndInfo + # Used to display some information to user when export is ended. + # To localize your message, use $self->{options}->{lang}. + # Returns a string that will be displayed in a message box. + sub getEndInfo + { + my $self = shift; + my $message; + + # Your code here + # Don't do put anything in message if you don't want information to be displayed. + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCExport/GCExportXML.pm b/lib/gcstar/GCExport/GCExportXML.pm new file mode 100644 index 0000000..57236ee --- /dev/null +++ b/lib/gcstar/GCExport/GCExportXML.pm @@ -0,0 +1,287 @@ +package GCExport::GCExportXML; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCExport::GCExportBase; + +{ + package GCExport::GCExporterXML; + use base qw(GCExport::GCExportBaseClass); + + use File::Basename; + use GCUtils 'glob'; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + + bless ($self, $class); + return $self; + } + + sub transformValue + { + my ($self, $value, $field) = @_; + + $value = $self->SUPER::transformValue($value, $field); + $value =~ s/&(\W)/&$1/g; + $value =~ s/"/"/g; + #" + $value =~ s/'/'/g; + #' + return $value; + } + + sub getName + { + my $self = shift; + + return "XML"; + } + + sub getSuffix + { + my $self = shift; + + return ""; + } + + sub needsUTF8 + { + my $self = shift; + + return 1; + } + + sub getOptions + { + my $self = shift; + + $self->{modelsFiles} = ''; + + if ($self->{model}->getName) + { + $self->{modelsDir} = $ENV{GCS_SHARE_DIR}.'/xml_models/'.$self->{model}->getName; + foreach (glob $self->{modelsDir}.'/*') + { + next if $_ =~ /\/CVS$/; + (my $mod = basename($_)) =~ s/_/ /g; + $self->{modelsFiles} .= ','.$mod; + } + } + + return [ + { + name => 'models', + type => 'options', + label => 'Models', + default => 'UseModel', + valuesList => 'UseModel,UseFile'.$self->{modelsFiles} + }, + + { + name => 'templatefile', + type => 'file', + label => 'ModelFile', + default => '' + }, + + { + name => 'model', + type => 'long text', + label => 'ModelText', + default => '', + height => 100 + }, + + ]; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsImagesSelection + { + return 1; + } + + sub preProcess + { + my $self = shift; + + my $model; + + if ($self->{options}->{models} eq 'UseModel') + { + $model = $self->{options}->{model}; + } + else + { + my $file; + if ($self->{options}->{models} eq 'UseFile') + { + $file = $self->{options}->{templatefile}; + } + else + { + (my $fileName = $self->{options}->{models}) =~ s/ /_/g; + $file = $self->{modelsDir}.'/'.$fileName; + $file =~ s/"//g; + #" + } + open FILE, $file; + #Read full file + $model = do { local $/; }; + close FILE; + } + $model =~ m{ + \[HEADER\]\n?(.*?)\n?\[\/HEADER\].*? + \[ITEM\]\n?(.*?)\n?\[\/ITEM\].*? + \[FOOTER\]\n?(.*?)\n?\[\/FOOTER\] + }xms; + $self->{header} = $1; + $self->{item} = $2; + $self->{footer} = $3; + return 1; + } + + sub getHeader + { + my ($self, $number) = @_; + my $result = $self->{header}; + + $result =~ s/\$\{file\}/$self->{options}->{collection}/g; + $result =~ s/\$\{number\}/$number/g; + + return $result."\n"; + } + + sub getItem + { + my ($self, $item, $number) = @_; + my $result = $self->{item}; + + while ($result =~ m/\[LOOP\s+(.*?)\]\n?(.*?)\n\s*\[\/LOOP\]/gms) + { + my $values = $self->transformValue($item->{$1}, $1); + my $motif = $2; + my $string; + foreach my $value(split /,/, $values) + { + $value =~ s/^\s*//; + (my $line = $motif) =~ s/\$\$/$value/gms; + $string .= $line; + } + $result =~ s/(\n?)\s*\[LOOP\s+$1\].*?\[\/LOOP\]/$1$string/gms; + } + + while ($result =~ m/\[SPLIT\s+value=(.*?)\s+sep=(.)\]\n?(.*?)\n\s*\[\/SPLIT\]/gms) + { + my $values = $1; + $values = $item->{$values} if exists $item->{$values}; + $values = $self->transformValue($values, $1); + my $sep = ${2}; + my $motif = ${3}; + my $i = 0; + foreach my $value(split /$sep/, $values) + { + $value =~ s/^\s*//; + $motif =~ s/\$$i/$value/gms; + $i++; + } + do {$motif =~ s/\s*\$[0-9]+//mgs;}; + $result =~ s/(\n?)\s*\[SPLIT\s+value=\Q$1\E\s+sep=($sep)\].*?\[\/SPLIT\]/$1$motif/gms; + } + + foreach (keys %$item) + { + my $value = $self->transformValue($item->{$_}, $_); + $result =~ s/\$\{$_\}/$value/g; + } + + if ($item->{time}) + { + my $min = 0; + my $time = $item->{time}; + $min = ($1 * 60) + $2 if ($time =~ /([0-9]*)h\.?\s+([0-9]*)m/) + || ($time =~ /([0-9]*):([0-9]*)/); + $min = $1 if !$min && ($time =~ /([0-9]*)/); + $result =~ s/\$\{length\}/$min/g; + } + + if ($item->{date}) + { + my $year = 0; + $item->{date} =~ /([0-9]{4})/; + $year = $1; + $result =~ s/\$\{year\}/$year/g; + } + + $result =~ s/\$\{.*?\}//g; + + return $result."\n"; + } + + sub getFooter + { + my $self = shift; + my $result = $self->{footer}; + + return $result."\n"; + } + + # postProcess + # Called after all processing. Use it if you need to perform extra stuff on the header. + # $header is a reference to the header string. + sub postProcess + { + my ($self, $header, $body) = @_; + + # Your code here + # As header is a reference, it can be modified on place with $$header + } + + # getEndInfo + # Used to display some information to user when export is ended. + # To localize your message, use $self->{options}->{lang}. + # Returns a string that will be displayed in a message box. + sub getEndInfo + { + my $self = shift; + my $message; + + # Your code here + # Don't do put anything in message if you don't want information to be displayed. + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCExportImport.pm b/lib/gcstar/GCExportImport.pm new file mode 100644 index 0000000..b73cdd3 --- /dev/null +++ b/lib/gcstar/GCExportImport.pm @@ -0,0 +1,526 @@ +package GCExportImport; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +{ + package GCExportImportBase; + + sub setLangName + { + my ($self, $langName) = @_; + + $self->{langName} = $langName; + } + + sub getLang + { + my ($self) = @_; + if (! $self->{langContainer}) + { + my $langFile = $self->{moduleName}; + my %tmpLang; + eval "use GCLang::".$self->{langName}."::$langFile\n"; + eval "%tmpLang = %GCLang::".$self->{langName}."::".$langFile."::lang"; + $self->{langContainer} = \%tmpLang; + } + return $self->{langContainer}; + } + + sub getName + { + my $self = shift; + + return $self->getLang->{Name}; + } + + sub setModel + { + my ($self, $model) = @_; + $self->{model} = $model; + } + + sub checkModule + { + my ($self, $module, $version) = @_; + + eval "use $module"; + ($self->{errors} .= "$module\n", return 0) if $@; + return 1 if !defined $version; + no strict 'refs'; + ($self->{errors} .= "$module\n", return 0) + if (${$module.'::VERSION'} < $version); + return 1; + } + + sub checkOptionalModule + { + my ($self, $module, $version) = @_; + # Save errors + my $errors = $self->{errors}; + my $code = $self->checkModule($module, $version); + # And restore them so it won't impact detecting if the module is broken + $self->{errors} = $errors; + return $code; + } + + sub hideFileSelection + { + return 0; + } + + sub new + { + my ($proto) = @_; + my $class = ref($proto) || $proto; + + (my $moduleName = $class) =~ s/(GC..port)er/$1/; + + my $self = {moduleName => $moduleName, + errors => ''}; + bless($self, $class); + return $self; + } +} + +use GCDialogs; + +{ + package GCExportImportDialog; + use base 'GCModalDialog'; + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + + $self->{optionsFrame}->hide if ! $self->{nbOptions}; + if (($self->{type} eq 'export') && (! $self->{module}->wantsSort)) + { + $self->{sorter}->hide; + $self->{sortLabel}->hide; + $self->{order}->hide; + $self->{orderLabel}->hide; + } + + if ($self->{module}->hideFileSelection) + { + $self->{file}->hide; + $self->{labelFile}->hide; + } + + $self->resize(1,1); + my $ok = 0; + while (!$ok) + { + my $response = $self->run; + if ($response eq 'ok') + { + if (($self->{module}->wantsFieldsSelection) + && (scalar @{$self->{fields}} == 0)) + { + my $dialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $self->{parent}->{lang}->{ImportExportFieldsEmpty}); + + $dialog->set_position('center-on-parent'); + $dialog->run(); + $dialog->destroy; + next; + } + my $file = $self->{file}->getValue; + if ($file || ! $self->{module}->wantsFileSelection) + { + my %options = $self->getOptions; + $self->addOptions(\%options); + $options{model} = $self->{parent}->{model}; + $options{file} = $file; + $options{lang} = $self->{parent}->{lang}; + $options{fields} = $self->{fields}; + $options{fieldsInfo} = $self->{parent}->{model}->{fieldsInfo}; + $options{originalList} = $self->{parent}->{items}; + $options{parent} = $self->{parent}; + + my ($info, $type) = $self->{module}->process(\%options); + $type ||= 'info'; + if ($info) + { + my $dialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + $type, + 'ok', + $info); + + $dialog->set_position('center-on-parent'); + $dialog->run(); + $dialog->destroy ; + } + $self->{parent}->setNbItems; + } + else + { + my $dialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $self->{parent}->{lang}->{ImportExportFileEmpty}); + + $dialog->set_position('center-on-parent'); + $dialog->run(); + $dialog->destroy; + + next; + } + } + $ok = 1; + } + $self->hide; + } + + sub setModule + { + my ($self, $module) = @_; + $self->set_title($self->{title}." [".$module->getName."]"); + if ($module->wantsDirectorySelection) + { + $self->{labelFile}->set_label($self->{parent}->{lang}->{FileChooserDirectory}); + $self->{file}->setTitle($self->{parent}->{lang}->{FileChooserOpenDirectory}); + $self->{file}->setType('select-folder', 0); + } + else + { + $self->{labelFile}->set_label($self->{parent}->{lang}->{ImportExportFile}); + $self->{file}->setTitle($self->{parent}->{lang}->{FileChooserOpenFile}); + $self->{file}->setType($self->{fileType}, $self->{withFilter}); + } + $self->{file}->setPatternFilter($module->getFilePatterns) + if ($self->{type} eq 'import'); + + $module->setModel($self->{parent}->{model}); + # sorter will only be created for export modules. + # It's initialized with the title field + if ($self->{sorter}) + { + $self->{sorter}->setModel($self->{parent}->{model}); + $self->{sorter}->setValue($self->{parent}->{model}->{commonFields}->{title}); + $self->{sorter}->setValue($module->{options}->{sorter}) + if exists $module->{options}; + } + if ($self->{order}) + { + $self->{order}->setValue(0); + $self->{order}->setValue($module->{options}->{order}) + if exists $module->{options}; + } + foreach ($self->{optionsTable}->get_children) + { + $self->{optionsTable}->remove($_); + $_->destroy; + } + my @optionsList = @{$module->getOptions}; + $self->{optionsTable}->resize($#optionsList + 1, 3) + if $#optionsList >= 0; + + $self->{module} = $module; + my %options; + my $option; + my $row = 0; + my @widgetSignals; + foreach $option (@optionsList) + { + my $label = $module->getLang->{$option->{label}}; + if (!$label) + { + if ($self->{parent}->{model}) + { + $label = $self->{parent}->{model}->getDisplayedText($option->{label}); + } + else + { + $label = $self->{parent}->{lang}->{$option->{label}}; + } + } + my $widget; + my @vExpand = ('fill'); + my $type = $option->{type}; + my $value = ((exists $module->{options}) ? $module->{options}->{$option->{name}} : $option->{default}); + if ($type eq 'yesno') + { + $widget = new GCCheckBox($label); + $self->{optionsTable}->attach($widget, 0, 2, $row, $row + 1, 'fill', 'fill', 0, 0); + if ($option->{changedCallback}) + { + $widget->signal_connect('toggled' => $option->{changedCallback}, [$self,$widget]); + push @widgetSignals, [$widget, 'toggled']; + } + } + elsif ( ($type eq 'short text') || + ($type eq 'long text') || + ($type eq 'number') || + ($type eq 'options') || + ($type eq 'file') || + ($type eq 'history text')) + { + my $labelWidget = GCLabel->new($label); + $self->{optionsTable}->attach($labelWidget, 0, 1, $row, $row + 1, 'fill', 'fill', 0, 0); + + if ($type eq 'short text') + { + $widget = new GCShortText; + } + elsif ($type eq 'long text') + { + $widget = new GCLongText; + $widget->set_size_request(-1,$option->{height}); + push @vExpand, 'expand'; + } + elsif ($type eq 'number') + { + $widget = new GCNumeric($value, $option->{min}, $option->{max}, $option->{step}); + + } + elsif ($type eq 'file') + { + $widget = new GCFile($self, $label, 'open'); + } + elsif ($type eq 'options') + { + $widget = new GCMenuList; + my @valuesList; + if (UNIVERSAL::isa( $option->{valuesList}, "HASH" )) + { + foreach $value(keys %{$option->{valuesList}}) + { + my $item = { + value => $value, + displayed => $option->{valuesList}->{$value} + }; + $item->{displayed} = $module->getLang->{$item->{displayed}} + if ($module->getLang->{$item->{displayed}}); + push @valuesList, $item; + } + } + else + { + my @values; + @values=split m/,/,$option->{valuesList} if(scalar($option->{valuesList})); + @values=@{$option->{valuesList}} if (UNIVERSAL::isa( $option->{valuesList}, "ARRAY" )); + foreach $value(@values) + { + my $item = { + value => $value, + displayed => $value + }; + $item->{displayed} = $module->getLang->{$item->{displayed}} + if ($module->getLang->{$item->{displayed}}); + push @valuesList, $item; + } + } + $widget->setValues(\@valuesList); + if ($option->{changedCallback}) + { + $widget->signal_connect('changed' => $option->{changedCallback}, [$self,$widget]); + push @widgetSignals, [$widget, 'changed']; + } + } + elsif ($type eq 'history text') + { + $widget = new GCHistoryText; + my @initValues = @{$option->{initValues}} if $option->{initValues}; + $widget->setValues(\@initValues); + } + if ($option->{changedCallback}) + { + $widget->signal_connect('changed' => $option->{changedCallback}, [$self,$widget]); + push @widgetSignals, [$widget, 'changed']; + } + if ($option->{buttonLabel}) + { + my $button = Gtk2::Button->new($module->getLang->{$option->{buttonLabel}}); + $button->signal_connect('clicked' => $option->{buttonCallback}, [$self,$widget]); + $self->{optionsTable}->attach($button, 2, 3, $row, $row + 1, 'fill', 'fill', 0, 0); + } + $self->{optionsTable}->attach($widget, 1, 2, $row, $row + 1, ['expand', 'fill'], \@vExpand, 0, 0); + } +# elsif ($type eq 'colorSelection') +# { +# $widget = new Gtk2::HBox(0,0); +# $widget->pack_start(new Gtk2::Label($label), 0,0,0); +# my $entry = new Gtk2::Entry; +# $entry->set_text($value); +# $widget->pack_start($entry, 1,1,5); +# +# my $button = Gtk2::Button->new_from_stock('gtk-select-color'); +# $button->signal_connect('clicked' => sub { +# my $dialog = new Gtk2::ColorSelectionDialog($label); +# my $previous = Gtk2::Gdk::Color->parse($entry->get_text); +# $dialog->colorsel->set_current_color($previous) if $previous; +# my $response = $dialog->run; +# if ($response eq 'ok') +# { +# my $color = $dialog->colorsel->get_current_color; +# my $red = $color->red / 257; +# my $blue = $color->blue / 257; +# my $green = $color->green / 257; +# my $colorString = sprintf ("#%X%X%X", $red, $blue, $green); +# $entry->set_text($colorString); +# } +# $dialog->destroy; +# }); +# $widget->pack_start($button, 0,0,0); +# } + $widget->set_sensitive(0) if $option->{insensitive}; + $widget->setValue($value); + $self->{parent}->{tooltips}->set_tip($widget, + $module->getLang->{$option->{tooltip}}) + if $option->{tooltip}; + $options{$option->{name}} = $widget; + $row++; + } + + $options{withPictures} = new GCCheckBox($self->{parent}->{lang}->{ExportWithPictures}); + if ($module->wantsImagesSelection) + { + $self->{optionsTable}->resize($row, 3); + my $value = ((exists $module->{options}) ? $module->{options}->{withPictures} : 1); + $options{withPictures}->set_active($value); + $self->{optionsTable}->attach($options{withPictures}, 0, 2, $row, $row + 1, 'fill', 'fill', 0, 0); + $row++; + $options{withPictures}->show; + } + else + { + $options{withPictures}->set_active(0); + } + + if ($module->wantsFieldsSelection) + { + $self->{optionsTable}->resize($row, 3); + $self->{fieldsSelection} = new Gtk2::Button($self->{fieldsButtonLabel}); + $self->{parent}->{tooltips}->set_tip($self->{fieldsSelection}, + $self->{fieldsTip}); + $self->{optionsTable}->attach($self->{fieldsSelection}, + 0, 1, $row, $row + 1, 'fill', 'fill', 0, 0); + $self->{fieldsSelection}->signal_connect('clicked' => sub { + $self->{fields} = $self->{fieldsDialog}->getSelectedIds + if $self->{fieldsDialog}->show; + }); + $row++; + } + + $self->{nbOptions} = $row; + $self->{options} = \%options; + $self->{optionsFrame}->show_all; + foreach my $pair(@widgetSignals) + { + $pair->[0]->signal_emit($pair->[1]) + } + + } + + sub getOptions + { + my $self = shift; + my %result; + + foreach (keys %{$self->{options}}) + { + my $value; + my $widget = $self->{options}->{$_}; + $result{$_} = $widget->getValue; + } + + return %result; + } + + sub new + { + my ($proto, $parent, $title, $type) = @_; + my $class = ref($proto) || $proto; + + my $okLabel; + #$okLabel = $parent->{lang}->{'Menu'.ucfirst($type)}; + $okLabel = ($type eq 'import') ? 'gtk-convert' : 'gtk-revert-to-saved'; + $okLabel =~ s/_//g; + + my $self = $class->SUPER::new($parent, + $title, + $okLabel + ); + + $self->{parent} = $parent; + $self->{title} = $title; + $self->{lang} = $parent->{lang}; + $self->{fields} = []; + $self->{type} = $type; + + $self->{optionsFrame} = new GCGroup($parent->{lang}->{OptionsTitle}); + $self->{optionsTable} = new Gtk2::Table(0,3); + $self->{optionsTable}->set_border_width($GCUtils::halfMargin); + $self->{optionsTable}->set_col_spacings($GCUtils::margin); + $self->{optionsTable}->set_row_spacings($GCUtils::halfMargin); + $self->{optionsFrame}->addWidget($self->{optionsTable}); + + #$self->{fileVbox} = new Gtk2::VBox(0,0); + + #my $sep = new Gtk2::HSeparator; + #my $hbox = new Gtk2::HBox(0,0); + + $self->{dataFrame} = new GCGroup($parent->{lang}->{ImportExportData}); + $self->{dataTable} = new Gtk2::Table(1,2); + $self->{dataTable}->set_border_width($GCUtils::halfMargin); + $self->{dataTable}->set_col_spacings($GCUtils::margin); + $self->{dataTable}->set_row_spacings($GCUtils::halfMargin); + $self->{dataFrame}->addWidget($self->{dataTable}); + + $self->{labelFile} = new GCLabel($parent->{lang}->{ImportExportFile}); + #$hbox->pack_start($labelFile,0,0,5); + + $self->{fileType} = ($type eq 'import') ? 'open' : 'save'; + $self->{withFilter} = ($self->{type} eq 'import') ? 1 : 0; + $self->{file} = new GCFile($self, + $parent->{lang}->{FileChooserOpenFile}, + $self->{fileType}, + $self->{withFilter}); + + #$hbox->pack_start($self->{file},1,1,5); + + #$self->{fileVbox}->pack_start($sep, 0, 0, 2); + #$self->{fileVbox}->pack_start($hbox, 0, 0, 10); + + $self->vbox->set_homogeneous(0); + $self->vbox->pack_start($self->{optionsFrame},1,1,0); + $self->vbox->pack_start($self->{dataFrame},0,0,0); + #$self->vbox->pack_start($self->{fileVbox}, 0, 0, 0); + + bless ($self, $class); + return $self; + } + +} + +1; diff --git a/lib/gcstar/GCExtract.pm b/lib/gcstar/GCExtract.pm new file mode 100644 index 0000000..fb0c251 --- /dev/null +++ b/lib/gcstar/GCExtract.pm @@ -0,0 +1,150 @@ +package GCExtract; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use Gtk2; + +{ + package GCExtractDialog; + + use base qw 'Gtk2::Dialog'; + + sub show + { + my $self = shift; + return if $self->{cancelled}; + $self->show_all; + my $code = $self->run; + if ($code eq 'ok') + { + foreach (@{$self->{extractedArray}}) + { + $self->{info}->{$_} = '' + if (! $self->{$_.'Cb'}->get_active); + $self->{panel}->$_($self->{info}->{$_}->{value}) + if $self->{info}->{$_} && $self->{info}->{$_}->{value}; + } + } + $self->hide; + } + + sub setInfo + { + my ($self, $infoExtractor, $panel) = @_; + my $info = $infoExtractor->getInfo; + if (!defined $info) + { + $self->{cancelled} = 1; + return; + } + $self->{cancelled} = 0; + ($self->{info}, $self->{panel}) = ($info, $panel); + foreach (@{$self->{extractedArray}}) + { + next if ! $self->{$_}; + if ($info->{$_}) + { + $self->{$_}->set_text($info->{$_}->{displayed}); + $self->{$_}->set_selectable(1); + } + else + { + $self->{$_}->set_text('-'); + $self->{$_}->set_selectable(0); + } + } + } + + sub new + { + my ($proto, $parent, $model, $infoExtractor) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent->{lang}->{ExtractTitle}, + $parent, + [qw/modal destroy-with-parent/], + 'gtk-cancel' => 'cancel' + ); + bless($self, $class); + + $self->{extractedArray} = $infoExtractor->getFields; + #['length', 'size', 'type', 'audioEncoding']; + + my $table = new Gtk2::Table(4,2); + $table->set_col_spacings(10); + $table->set_row_spacings(10); + $table->set_border_width(10); + + my $i = 0; + foreach (@{$self->{extractedArray}}) + { + (my $capsField = $_) =~ s/^(.)/\U$1\E/; + $self->{$_.'Cb'} = new Gtk2::CheckButton($model->getDisplayedLabel($_).$parent->{lang}->{Separator}); + $self->{$_.'Cb'}->set_active(1); + $self->{$_} = new Gtk2::Label; + $table->attach($self->{$_.'Cb'}, 0, 1, $i, $i+1, 'fill', 'fill', 0, 0); + $table->attach($self->{$_}, 1, 2, $i, $i+1, 'fill', 'fill', 0, 0); + $i++; + } + + $self->vbox->pack_start($table,1,1,0); + + $self->{importButton} = new Gtk2::Button($parent->{lang}->{ExtractImport}); + $self->add_action_widget($self->{importButton}, 'ok'); + + $self->{parent} = $parent; + return $self; + } +} + +use GCExportImport; + +{ + package GCItemExtracter; + use base 'GCExportImportBase'; + + sub getInfo + { + } + + sub new + { + my ($proto, $parent, $fileName, $panel, $model) = @_; + my $class = ref($proto) || $proto; + + my $fileSize = -s $fileName; + + my $self = {fileName => $fileName, + fileSize => $fileSize, + parent => $parent, + panel => $panel, + model => $model}; + + bless($self, $class); + + return $self; + } +} + + +1; diff --git a/lib/gcstar/GCExtract/GCExtractFilms.pm b/lib/gcstar/GCExtract/GCExtractFilms.pm new file mode 100644 index 0000000..fbcd9f5 --- /dev/null +++ b/lib/gcstar/GCExtract/GCExtractFilms.pm @@ -0,0 +1,476 @@ +package GCExtract::GCExtractFilms; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCExtract; + +{ + package GCExtract::GCfilmsExtracter; + use base 'GCItemExtracter'; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(@_); + bless ($self, $class); + + return $self; + } + + sub readInt + { + my ($self, $size) = @_; + my $buf; + + $size = 4 if !$size; + + read $self->{file},$buf,$size; + return unpack "i",$buf; + } + + sub getAviInfo + { + my $self = shift; + + my $info = {}; + + my @audioCodecs; + $audioCodecs[0x0001] = 'PCM'; + $audioCodecs[0x0002] = 'ADPCM'; + $audioCodecs[0x0030] = 'Dolby AC2'; + $audioCodecs[0x0050] = 'MPEG'; + $audioCodecs[0x0055] = 'MP3'; + $audioCodecs[0x0092] = 'Dolby AC3 SPDIF'; + $audioCodecs[0x2000] = 'Dolby AC3'; + $audioCodecs[0x2001] = 'Dolby DTS'; + $audioCodecs[0x2002] = 'WAVE'; + $audioCodecs[0x2003] = 'WAVE'; + $audioCodecs[0x2004] = 'WAVE'; + $audioCodecs[0x2005] = 'WAVE'; + $audioCodecs[0x674F] = 'Ogg Vorbis', + $audioCodecs[0x6750] = 'Ogg Vorbis', + $audioCodecs[0x6751] = 'Ogg Vorbis', + $audioCodecs[0x676F] = 'Ogg Vorbis', + $audioCodecs[0x6770] = 'Ogg Vorbis', + $audioCodecs[0x6771] = 'Ogg Vorbis', + + my $chunkName; + seek $self->{file},8,0; + read $self->{file},$chunkName,8; + return $info if ($chunkName ne 'AVI LIST'); + seek $self->{file},4,1; + read $self->{file},$chunkName,8; + + $self->readInt; + my $dwMicroSecPerFrame = $self->readInt; + my $dwMaxBytesPerSec = $self->readInt; + my $dwReserved1 = $self->readInt; + my $dwFlags = $self->readInt; + my $dwTotalFrames = $self->readInt; + my $dwInitialFrames = $self->readInt; + my $dwStreams = $self->readInt; + my $dwSuggestedBufferSize = $self->readInt; + $info->{width} = $self->readInt; + $info->{height} = $self->readInt; + my $dwScale = $self->readInt; + my $dwRate = $self->readInt; + my $dwStart = $self->readInt; + my $dwLength = $self->readInt; + + $info->{length} = ($dwTotalFrames * $dwMicroSecPerFrame) / 60000000; + $info->{length} = GCUtils::round($info->{length}); + + my $buff; + my ($gotVids, $gotAuds) = (0,0); + while (! eof($self->{file})) + { + read $self->{file},$chunkName,4; + if ($chunkName eq 'strl') + { + seek $self->{file},8,1; + read $self->{file},$buff,4; + if ($buff eq 'vids') + { + read $self->{file},$info->{type},4; + $gotVids = 1; + } + elsif ($buff eq 'auds') + { + read $self->{file},$info->{audioEncoding},4; + $info->{audioEncoding} =~ s/^.*?\w*\W*?$/$1/g; + if (!$info->{audioEncoding}) + { + read $self->{file},$chunkName,4 while ($chunkName ne 'strf'); + seek $self->{file},4,1; + my $codec; + read $self->{file}, $codec, 2; + $codec = unpack "v",$codec; + $codec = $audioCodecs[$codec]; + seek $self->{file}, 2, 1; + my $hz = $self->readInt; + $info->{audioEncoding} = $codec if $codec; + $info->{audioEncoding} .= " ($hz Hz)" if $hz; + } + $gotAuds = 1; + } + last if $gotVids && $gotAuds; + } + last if ($chunkName eq 'movi'); + } + + return {} if ($buff ne 'vids') && ($buff ne 'auds'); + + return $info; + } + + sub getMovAtom + { + my ($self, $wanted, $subAtom) = @_; + + my $copy = $subAtom; + + my ($header, $type, $length); + my $atom = 0; + + if ($subAtom) + { + while ($copy) + { + $header = substr($copy, 0, 8, ''); + ($length, $type) = unpack("Na4", $header); + last if $type eq $wanted; + substr($copy, 0 , $length - 8, ''); + } + if ($copy) + { + $atom = substr($copy, 0 , $length - 8, ''); + } + } + else + { + while (!eof ($self->{file})) + { + read $self->{file}, $header, 8; + ($length, $type) = unpack("Na4", $header); + last if $type eq $wanted; + seek $self->{file},$length - 8, 1; + } + if ($self->{file}) + { + read $self->{file}, $atom, $length - 8; + } + } + + return $atom; + } + + sub getMovInfo + { + #Inspired from Video::Info::Quicktime_PL + + my $self = shift; + + my $info = {}; + + seek $self->{file},0,0; + + my $header; + + my $atom = $self->getMovAtom('moov'); + + + if ($atom) + { + while (length($atom) > 0) + { + my ($sublen) = unpack("Na4", substr( $atom, 0, 4, '') ); + my ($subatom) = substr($atom, 0, $sublen-4, ''); + my($type) = substr($subatom, 0, 4, ''); + + if ($type eq 'mvhd') + { + my $timeScale = unpack( "Na4", substr($subatom,12,4)); + my $duration = unpack( "Na4", substr($subatom,16,4)); + $info->{length} = GCUtils::round($duration / ($timeScale * 60)); + } + elsif ($type eq 'trak') + { + my $tkhd = $self->getMovAtom('tkhd', $subatom); + my $mdia = $self->getMovAtom('mdia', $subatom); + next if !$mdia; + my $minf = $self->getMovAtom('minf', $mdia); + next if !$minf; + my $vmhd = $self->getMovAtom('vmhd', $minf); + my $smhd = $self->getMovAtom('smhd', $minf); + if ($vmhd || $smhd) + { + my $stbl = $self->getMovAtom('stbl', $minf); + my $stsd = $self->getMovAtom('stsd', $stbl); + + if ($vmhd) + { + my $width = unpack("Na4", substr($tkhd,74,4)); + my $height = unpack("Na4", substr($tkhd,78,4)); + ($info->{width}, $info->{height}) = ($width, $height); + ($info->{type} = substr($stsd,12,8)) =~ s/\W(.*?)\W/$1/g; + } + else + { + ($info->{audioEncoding}= substr($stsd,12,8)) =~ s/\W(.*?)\W/$1/g; + } + } + } + } + } + return $info; + } + + sub getMpgInfo + { + #Inspired from MPEG::Info + + my $self = shift; + + my @frameRates = ( + 0, + 24000/1001, + 24, + 25, + 30000/1001, + 30, + 50, + 60000/1001, + 60, + ); + + my $info = {}; + $info->{type} = 'MPEG'; + $info->{audioEncoding} = 'MPEG'; + + my $magic; + my $numMagic = unpack("N",$self->{magic}); + while (!eof($self->{file}) && $numMagic != 0x000001b3) + { + read $self->{file},$magic,4; + $numMagic = unpack("N",$magic); + seek $self->{file},-3, 1; + } + seek $self->{file},3, 1; + my $size; + read $self->{file},$size,3; + + $info->{width} = ((unpack "n",substr($size,0,2)) >> 4); + $info->{height} = ((unpack "n",substr($size,1,2)) & 0x0fff); + + my $fps; + read $self->{file},$fps,1; + $fps = $frameRates[ord($fps) & 0x0f]; + + my ($buff1, $buff2); + read $self->{file}, $buff1, 2; + $buff1 = unpack 'n', $buff1; + $buff1 <<= 2; + read $self->{file}, $buff2, 1; + $buff2 = unpack 'C', $buff2; + $buff2 >>=6; + my $bitRate = ( ( $buff1 | $buff2 ) * 400); + + $info->{length} = GCUtils::round((($self->{fileSize} * 8 ) / $bitRate) / 60) if $bitRate; + + return $info; + } + + sub findOgmPage + { + #Inspired from Ogg::Vorbis::Header::PurePerl + + my $self = shift; + my $char; + my $curStr = ''; + + my $i = 0; + while (read($self->{file}, $char, 1)) + { + $curStr = $char . $curStr; + $curStr = substr($curStr, 0, 4); + if ($curStr eq 'SggO') + { + seek $self->{file}, 8, 1; + my $serial = $self->readInt(4); + return $serial; + } + } + return -1; + } + + sub findLastOgmPage + { + my $self = shift; + my $buff; + my $curStr = ''; + + seek $self->{file}, -5, 2; + + my $i = 0; + while (read($self->{file}, $buff, 4)) + { + if ($buff eq 'OggS') + { + seek $self->{file}, 2, 1; + my $granulePos = $self->readInt; + return $granulePos; + } + seek $self->{file}, -5, 1; + } + return -1; + } + + sub getOgmInfo + { + my $info = {}; + my $self = shift; + + my $buff; + my ($gotAudio, $gotVideo) = (0,0); + seek $self->{file}, 0, 0; + my $serial = 0; + my $videoSerial = -1; + my $fps; + my $iteration = 0; + while ($serial != -1) + { + $serial = $self->findOgmPage; + + seek $self->{file}, 13, 1; + read $self->{file}, $buff, 8; + if ($buff =~ /^video/) + { + read $self->{file}, $info->{type}, 4; + my $size = $self->readInt; + my $timeUnit = $self->readInt(8); + my $spu = $self->readInt(8); + $fps = (10000000.0 * $spu) / $timeUnit; + my $defaultLen = $self->readInt; + my $bufferSize = $self->readInt; + my $bbp = $self->readInt; + $info->{width} = $self->readInt; + $info->{height} = $self->readInt; + + $gotVideo = 1; + $videoSerial = $serial; + } + elsif ($buff =~ /vorbis/) + { + $info->{audioEncoding} = 'Vorbis'; + seek $self->{file}, 3, 1; + my $hz = $self->readInt; + $info->{audioEncoding} .= " ($hz Hz)" if $hz; + $gotAudio = 1; + } + else + { + last if $iteration > 5; + } + last if $gotAudio && $gotVideo; + $iteration++; + } + if ($gotVideo) + { + my $biggestGranulePos = $self->findLastOgmPage; + $info->{length} = GCUtils::round(($biggestGranulePos / $fps) / 60); + } + + return $info; + } + + sub getInfo + { + my $self = shift; + + open FILE, '<'.$self->{fileName}; + binmode FILE; + + my $info = {}; + + $self->{file} = \*FILE; + my $magic; + $self->{magic} = $magic; + read FILE,$magic,4; + my $numMagic = unpack("N",$magic); + + if ($magic eq 'RIFF') + { + $info = $self->getAviInfo; + } + elsif ($magic eq 'OggS') + { + $info = $self->getOgmInfo; + } + elsif (($numMagic == 0x000001ba) || ($numMagic == 0x000001b3)) + { + $info = $self->getMpgInfo; + } + else + { + my $magic2; + read FILE,$magic2,4; + if ($magic2 =~ /(moov|notp|wide|ftyp)/) + { + $info = $self->getMovInfo; + } + } + + close FILE; + my $result; + + $result->{time} = {displayed => $info->{length}, value => $info->{length}}; + $result->{video} = {displayed => $info->{type}, value => $info->{type}}; + my $currentAudio = $self->{panel}->audio; + if ($info->{audioEncoding}) + { + $currentAudio->[0]->[1] = $info->{audioEncoding}; + $result->{audio}->{value} = $currentAudio; + $result->{audio}->{displayed} = $info->{audioEncoding}; + } + if ($info->{width} && $info->{height}) + { + my $comment = $self->{panel}->comment; + $comment .= "\n" if $comment && ($comment !~ /\n$/m); + $result->{comment}->{displayed} = + $self->{model}->getDisplayedText('ExtractSize').$self->{parent}->{lang}->{Separator}. + $info->{width}.'*'.$info->{height}; + $result->{comment}->{value} = $comment . $result->{comment}->{displayed}; + } + + return $result; + } + + sub getFields + { + return ['time', 'video', 'audio', 'comment']; + } +} + +1; diff --git a/lib/gcstar/GCExtract/GCExtractMusics.pm b/lib/gcstar/GCExtract/GCExtractMusics.pm new file mode 100644 index 0000000..1a56b68 --- /dev/null +++ b/lib/gcstar/GCExtract/GCExtractMusics.pm @@ -0,0 +1,370 @@ +package GCExtract::GCExtractMusics; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCExtract; + +use GCDialogs; +{ + package GCExtractMusicsResultsDialog; + use base 'GCModalDialog'; + + sub show + { + my $self = shift; + + $self->SUPER::show(); + $self->show_all; + my $response = $self->run; + my $idx = ($self->{results}->get_selected_indices)[0]; + $self->hide; + return -1 if $response ne 'ok'; + return $idx; + } + + sub setData + { + my ($self, @cddbData) = @_; + my @listData; + foreach(@cddbData) + { + push @listData, [$_->{genre}, $_->title, $_->artist, $_->year]; + } + @{$self->{results}->{data}} = @listData; + $self->{results}->select(0); + $self->{results}->columns_autosize; + } + + sub new + { + my ($proto, $parent, $model) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new($parent, + $model->getDisplayedText('ResultsDialog')); + $self->{parent} = $parent; + + my $hbox = new Gtk2::HBox(0,0); + + $self->{results} = new Gtk2::SimpleList( + $model->getDisplayedText('Genre') => 'text', + $model->getDisplayedText('Title') => 'text', + $model->getDisplayedText('Artist') => 'text', + $model->getDisplayedText('Release') => 'text', + ); + + $self->{results}->set_rules_hint(1); + $self->{results}->set_headers_clickable(1); + for my $i (0..3) + { + my $column = $self->{results}->get_column($i); + $column->set_resizable(1); + $column->set_sort_column_id($i); + } + $self->{results}->signal_connect(row_activated => sub { + $self->response('ok'); + }); + + my $scrollPanelList = new Gtk2::ScrolledWindow; + $scrollPanelList->set_policy ('never', 'automatic'); + $scrollPanelList->set_shadow_type('etched-in'); + $scrollPanelList->set_border_width($GCUtils::margin); + $scrollPanelList->add($self->{results}); + + $self->vbox->pack_start($scrollPanelList,1,1,0); + + $self->set_default_size(-1,300); + return $self; + } +} + +{ + package GCExtract::GCmusicsExtracter; + use base 'GCItemExtracter'; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(@_); + bless ($self, $class); + + $self->{hasMP3Info} = $self->checkModule('MP3::Info'); + $self->{hasMP3Tag} = $self->checkModule('MP3::Tag'); + $self->{hasOggVorbisHeader} = $self->checkModule('Ogg::Vorbis::Header::PurePerl'); + $self->{hasNetFreeDB} = $self->checkModule('Net::FreeDB'); + # Even if previous check fails, we want to use it for tracks feature + $self->{errors} = 0; + + $self->{fields} = ['title', 'artist', 'release', 'genre', 'running', 'tracks']; + return $self; + } + + sub resetTracks + { + my $self = shift; + $self->{tracks} = []; + $self->{totalTime} = 0; + $self->{currentTrack} = 0; + $self->{firstTrack} = ''; + } + + sub addTrack + { + my ($self, $title, $time, $number) = @_; + $self->{currentTrack}++; + $self->{totalTime} += $time; + $number = $self->{currentTrack} if !defined $number; + push @{$self->{tracks}}, + [$number, $title, $self->secondsToString($time)]; + } + + sub getTracks + { + my $self = shift; + return $self->{tracks}; + } + + sub secondsToString + { + my ($self, $time) = @_; + return int($time / 60) .':'. sprintf '%02d', ($time %60); + } + + sub getTotalTime + { + my $self = shift; + return $self->secondsToString($self->{totalTime}); + } + + sub getM3UInfo + { + my ($self) = @_; + my $file = $self->{file}; + my $info = {}; + while (<$file>) + { + chomp; + s/\r//; + if (/^#/) + { + next if ! /^#EXTINF:(.*)/; + my @values = split /,/, $1; + $self->addTrack($values[1], $values[0]); + } + else + { + $self->{firstTrack} = $_ + if !$self->{firstTrack}; + } + } + $info->{tracks} = $self->getTracks; + $info->{running} = $self->getTotalTime; + return $info; + } + + sub getPLSInfo + { + my ($self) = @_; + my $file = $self->{file}; + my $info = {}; + my @tracks; + while (<$file>) + { + chomp; + s/\r//; + next if ! /(File|Title|Length)(\d+)=(.*)$/; + $tracks[$2]->{$1} = $3; + $tracks[$2]->{Number} = $2; + } + foreach (@tracks) + { + next if !$_->{Title}; + $self->addTrack($_->{Title}, $_->{Length}, $_->{Number}); + } + + $info->{tracks} = $self->getTracks; + $info->{running} = $self->getTotalTime; + $self->{firstTrack} = $tracks[1]->{File}; + return $info; + } + + sub getFreeDB + { + my @genres = qw(blues classical country data folk jazz newage reggae rock soundtrack misc); + my ($self) = @_; + my $file = $self->{fileName}; + my $info = {}; + return $info if ! -e $file; + return $info if ! $self->{hasNetFreeDB}; + + my $freedb = Net::FreeDB->new; + my $discdata = $freedb->getdiscdata($file); + return if !$discdata; + my $cddb_file_object; + my @results; + + foreach (@genres) + { + my $tmpCddb = $freedb->read($_, $discdata->{ID}); + if ($tmpCddb) + { + $tmpCddb->{genre} = $tmpCddb->genre || $_; + push @results, $tmpCddb; + } + } + + if ($#results == -1) + { + return; + } + elsif ($#results == 0) + { + $cddb_file_object = $results[0]; + } + else + { + my $dialog = new GCExtractMusicsResultsDialog( + $self->{parent}, + $self->{model} + ); + $dialog->setData(@results); + my $selected = $dialog->show; + $dialog->destroy; + return if $selected == -1; + $cddb_file_object = $results[$selected]; + } + + foreach my $track ($cddb_file_object->tracks) + { + $self ->addTrack($track->title,$track->length,$track->number); + } + + $info->{tracks} = $self->getTracks; + $info->{running} = $self->getTotalTime; + $info->{title} = $cddb_file_object->title; + $info->{artist} = $cddb_file_object->artist; + $info->{release} = $cddb_file_object->year; + $info->{genre} = $cddb_file_object->{genre}; + + return $info; + } + + sub addFirstTrackInfo + { + my ($self, $info) = @_; + + if ($^O =~ /win32/i) + { + $self->{firstTrack} =~ s|\\|/|g; + $self->{fileName} =~ /^(.{2})/; + my $drive = $1; + $self->{firstTrack} = $drive.$self->{firstTrack} + if $self->{firstTrack} =~ m|^/|; + } + + if ($self->{firstTrack} =~ /mp3$/i) + { + if ($self->{hasMP3Info}) + { + MP3::Info::use_mp3_utf8(1); + my $song = MP3::Info::get_mp3tag($self->{firstTrack}); + $info->{title} = $song->{ALBUM}; + $info->{artist} = $song->{ARTIST}; + $info->{release} = $song->{YEAR}; + $info->{genre} = $song->{GENRE}; + } + elsif ($self->{hasMP3Tag}) + { + my $song = MP3::Tag->new($self->{firstTrack}); + (undef, undef, $info->{artist}, $info->{title}) = $song->autoinfo; + } + } + elsif ($self->{firstTrack} =~ /ogg$/i) + { + if ($self->{hasOggVorbisHeader}) + { + my $song = Ogg::Vorbis::Header::PurePerl->new($self->{firstTrack}); + $info->{title} = ($song->comment('album'))[0]; + $info->{artist} .= $_.', ' foreach $song->comment('artist'); + $info->{artist} =~ s/, $//; + ($info->{release} = ($song->comment('date'))[0]) =~ s|^(\d{4})-(\d{2})-(\d{2}).*$|$3/$2/$1|; + $info->{genre} .= $_.', ' foreach $song->comment('genre'); + $info->{genre} =~ s/, $//; + } + } + } + + sub getInfo + { + my $self = shift; + my $info = {}; + $self->resetTracks; + + if ((!$self->{fileName}) || ($self->{fileName} =~ /\/dev\//)) + { + if (!$self->{fileName}) + { + $self->{fileName} = $self->{parent}->{options}->cdDevice; + } + $info = $self->getFreeDB; + } + else + { + + open FILE, '<'.$self->{fileName}; + binmode FILE; + + $self->{file} = \*FILE; + my $header = ; + + $info = $self->getM3UInfo + if ($self->{fileName} =~ /m3u$/) || ($header =~ /^#EXTM3U/); + $info = $self->getPLSInfo + if ($self->{fileName} =~ /pls$/) || ($header =~ /^\[playlist\]/); + close FILE; + } + + $self->addFirstTrackInfo($info); + + return if !defined $info; + my $result; + my $firstTrackName = $info->{tracks}->[0]->[1]; + $result->{tracks} = {displayed => $firstTrackName, value => $info->{tracks}}; + foreach (@{$self->{fields}}) + { + next if /^tracks$/; + $result->{$_} = {displayed => $info->{$_}, value => $info->{$_}}; + } + return $result; + } + + sub getFields + { + my $self = shift; + return $self->{fields}; + } +} + +1; diff --git a/lib/gcstar/GCGenres.pm b/lib/gcstar/GCGenres.pm new file mode 100644 index 0000000..8f89e24 --- /dev/null +++ b/lib/gcstar/GCGenres.pm @@ -0,0 +1,404 @@ +package GCGenres; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use Gtk2; + +{ + package GCGenresGroupsDialog; + use base "Gtk2::Dialog"; + use utf8; + + sub initValues + { + use locale; + + my $self = shift; + my $keepPrevious = shift; + + my %directory; + + if ($keepPrevious) + { + foreach my $line(@{$self->{categories}->{data}}) + { + $directory{$line->[0]} = $line->[1]; + } + } + else + { + foreach (keys %{$self->{convertor}->{groups}}) + { + $directory{$_} = join ',', @{$self->{convertor}->{groups}->{$_}}; + } + } + + @{$self->{categories}->{data}} = (); + + my @keys = sort keys %directory; + @keys = reverse @keys if $self->{reverse}; + foreach (@keys) + { + my @infos = [$_, $directory{$_}]; + push @{$self->{categories}->{data}}, @infos; + } + $self->{categories}->select(0); + + } + + sub generateString + { + my $self = shift; + my $genresString; + + foreach (@{$self->{categories}->{data}}) + { + $genresString .= $_->[0]; + $genresString .= '|'.$_->[1].';'; + } + $genresString =~ s/.$//; + return $genresString; + } + + sub saveValues + { + my $self = shift; + + my $genresString = $self->generateString; + + $self->{options}->genres($genresString); + $self->{convertor}->loadValues; + $self->{options}->save; + } + + sub show + { + my $self = shift; + + $self->initValues; + + $self->SUPER::show(); + $self->show_all; + + if ($self->run eq 'ok') + { + $self->saveValues; + } + $self->hide; + } + + sub removeCurrent + { + my $self = shift; + my @idx = $self->{categories}->get_selected_indices; + + splice @{$self->{categories}->{data}}, $idx[0], 1; + + $self->{categories}->select((($idx[0] - 1) > 0) ? ($idx[0] - 1) : 0); + } + + sub add + { + my $self = shift; + + unshift @{$self->{categories}->{data}}, ['','']; + } + + sub editCurrent + { + my $self = shift; + + my @idxtmp = $self->{categories}->get_selected_indices; + my $idx = $idxtmp[0]; + my $line = $self->{categories}->{data}->[$idx]; + + my $dialog = new Gtk2::Dialog($self->{parent}->{lang}->{GenresModify}, + $self, + [qw/modal destroy-with-parent/], + @GCDialogs::okCancelButtons + ); + + my $table = new Gtk2::Table(3,2,0); + + my $labelCategory = new Gtk2::Label($self->{parent}->{lang}->{GenresCategoryName}); + $table->attach($labelCategory, 0, 1, 0, 1, 'fill', 'fill', 5, 5); + my $category = new Gtk2::Entry; + $category->set_text($line->[0]); + my $hbox1 = new Gtk2::HBox(0,0); + $hbox1->pack_start($category,1,1,0); + $table->attach($hbox1, 1, 2, 0, 1, 'fill', 'fill', 5, 5); + + my $labelMembers = new Gtk2::Label($self->{parent}->{lang}->{GenresCategoryMembers}); + $table->attach($labelMembers, 0, 1, 1, 2, 'fill', 'fill', 5, 5); + my $members = new Gtk2::Entry; + $members->set_text($line->[1]); + my $hbox2 = new Gtk2::HBox(0,0); + $hbox2->pack_start($members,1,1,0); + $table->attach($hbox2, 1, 2, 1, 2, 'fill', 'fill', 5, 5); + + my $labelFoo = new Gtk2::Label(''); + my $labelBar = new Gtk2::Label(''); + $table->attach($labelFoo, 0, 1, 2, 3, 'fill', 'fill', 0, 0); + $table->attach($labelBar, 1, 2, 2, 3, 'expand', 'expand', 0, 0); + + $dialog->set_default_size(500,1); + + $dialog->vbox->pack_start($table,1,1,0); + $dialog->vbox->show_all; + + if ($dialog->run eq 'ok') + { + splice @{$self->{categories}->{data}}, $idx, 1, [$category->get_text, $members->get_text]; + } + + $dialog->destroy; + } + + sub load + { + my $self = shift; + + my $response = $self->{loadDialog}->run; + if ($response eq 'ok') + { + my $fileName = $self->{loadDialog}->get_filename; + $self->{convertor}->loadValues(undef, $fileName); + $self->initValues; + } + $self->{loadDialog}->hide; + } + + sub export + { + my $self = shift; + + my $response = $self->{exportDialog}->run; + if ($response eq 'ok') + { + my $fileName = $self->{exportDialog}->get_filename; + $self->{convertor}->saveValues($self->generateString, $fileName); + $self->initValues; + } + $self->{exportDialog}->hide; + } + + sub clear + { + my $self = shift; + @{$self->{categories}->{data}} = (); + } + + sub sort + { + my $self = shift; + + $self->{reverse} = 1 - $self->{reverse}; + + $self->{categories}->get_column(0)->set_sort_indicator(1); + $self->{categories}->get_column(0)->set_sort_order($self->{reverse} ? 'descending' : 'ascending'); + $self->initValues(1); + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent->{lang}->{GenresTitle}, + $parent, + [qw/modal destroy-with-parent/], + @GCDialogs::okCancelButtons + ); + + bless ($self, $class); + + $self->set_modal(1); + $self->set_position('center'); + $self->set_default_size(600,400); + + $self->{reverse} = 0; + + $self->{parent} = $parent; + $self->{lang} = $parent->{lang}; + $self->{options} = $parent->{options}; + + my $hbox = new Gtk2::HBox(0,0); + + $self->{categories} = new Gtk2::SimpleList($parent->{lang}->{GenresCategoryName} => "text", + $parent->{lang}->{GenresCategoryMembers} => "text"); + $self->{categories}->set_column_editable(0, 1); + $self->{categories}->set_column_editable(1, 1); + $self->{categories}->set_rules_hint(1); + $self->{categories}->get_column(0)->signal_connect('clicked' => sub { + $self->sort; + }); + $self->{categories}->get_column(0)->set_sort_indicator(1); + $self->{categories}->get_column(0)->set_clickable(1); + for my $i (0..1) + { + $self->{categories}->get_column($i)->set_resizable(1); + } + $self->{order} = 1; + $self->{sort} = -1; + + my $scrollPanelList = new Gtk2::ScrolledWindow; + $scrollPanelList->set_policy ('never', 'automatic'); + $scrollPanelList->set_shadow_type('etched-in'); + $scrollPanelList->add($self->{categories}); + + my $vboxButtons = new Gtk2::VBox(0,0); + my $addButton = Gtk2::Button->new_from_stock('gtk-add'); + $addButton->signal_connect('clicked' => sub { + $self->add; + }); + my $removeButton = Gtk2::Button->new_from_stock('gtk-remove'); + $removeButton->signal_connect('clicked' => sub { + $self->removeCurrent; + }); + + my $clearButton = Gtk2::Button->new_from_stock('gtk-clear'); + $clearButton->signal_connect('clicked' => sub { + $self->clear; + }); + + my $editButton = Gtk2::Button->new_from_stock('gtk-properties'); + $editButton->signal_connect('clicked' => sub { + $self->editCurrent; + }); + my $openButton = Gtk2::Button->new_from_stock('gtk-open'); + $openButton->signal_connect('clicked' => sub { + $self->load; + }); + my $exportButton = Gtk2::Button->new_from_stock('gtk-save-as'); + $exportButton->signal_connect('clicked' => sub { + $self->export; + }); + + $vboxButtons->pack_start($addButton,0,0,5); + $vboxButtons->pack_start($removeButton,0,0,5); + $vboxButtons->pack_start($clearButton,0,0,5); + $vboxButtons->pack_start($editButton,0,0,5); + $vboxButtons->pack_start($openButton,0,0,5); + $vboxButtons->pack_start($exportButton,0,0,5); + + $hbox->pack_start($scrollPanelList,1,1,10); + $hbox->pack_start($vboxButtons,0,0,10); + + $self->vbox->pack_start($hbox,1,1,10); + + $self->{convertor} = new GCGenresConvertor($self->{options}); + + $self->{loadDialog} = new GCFileChooserDialog($self->{lang}->{GenresLoad}, $self, 'open', 1); + $self->{loadDialog}->set_pattern_filter((['*.genres', '*.genres'])); + $self->{loadDialog}->set_filename($ENV{GCS_SHARE_DIR}.'/genres/'); + + $self->{exportDialog} = new GCFileChooserDialog($self->{lang}->{GenresExport}, $self, 'save'); + + return $self; + } +} + +{ + package GCGenresConvertor; + + sub new + { + my ($proto, $options) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + bless ($self, $class); + + $self->{options} = $options; + $self->loadValues($options->genres); + + return $self; + } + + sub loadValues + { + my ($self, $values, $file) = @_; + + $self->{groups} = {}; + $self->{genres} = {}; + + my @groups; + + if ($file) + { + open FG, "< $file" or return -1; + binmode( FG, ':utf8' ); + foreach() + { + chomp; + s/(.*?)\W*$/$1/; + push (@groups,$_); + } + close FG; + } + else + { + $values = $self->{options}->genres unless $values; + @groups = split /;/, $values; + } + + foreach my $group(@groups) + { + my @details = split /\|/, $group; + my $groupName = $details[0]; + my @groupList; + foreach my $genre(split /,/,$details[1]) + { + push @groupList, $genre; + $self->{genres}->{uc $genre} = $groupName; + } + $self->{genres}->{uc $groupName} = $groupName; + $self->{groups}->{$groupName} = \@groupList; + } + } + + sub saveValues + { + my ($self, $value, $file) = @_; + + open FG, "> $file" or return -1; + my @values = split /;/, $value; + + foreach (@values) + { + print FG "$_\n"; + } + close FG; + } + + sub convert + { + my ($self, $genre) = @_; + + my $ucGenre = uc $genre; + + return $genre if ! exists $self->{genres}->{$ucGenre}; + return $self->{genres}->{$ucGenre}; + } +} + +1; diff --git a/lib/gcstar/GCGraphicComponents/GCBaseWidgets.pm b/lib/gcstar/GCGraphicComponents/GCBaseWidgets.pm new file mode 100644 index 0000000..f2f9af4 --- /dev/null +++ b/lib/gcstar/GCGraphicComponents/GCBaseWidgets.pm @@ -0,0 +1,4023 @@ +package GCBaseWidgets; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### +use utf8; +use Gtk2; +#use GCBorrowings; +use Encode; + + +use strict; + +our @videoExtensions = ('.aaf','.3gp','.asf','.avi','.flv','.m1v','.m2v','.m4v','.mkv','.mov', + '.mp4','.mpeg','.mpg','.mpe','.mxf','.nsv','.ogg','.ogv','.rm','.swf','.wmv', + '.iso'); + +our @ebookExtensions = ('.txt','.htm','.html','.azw','.opf','.tr2','.tr3','.aeh','.fb2','.chm', + '.pdf','.ps','.djvu','.lit','.pdb','.dnl','.xeb','.ceb','.lbr','.prc', + '.mobi','.epub','.lrf','.lrx','.pdg','.doc','.odt','.cbr','.cbz','.djvu'); + +our @audioExtensions = ('.m3u','.pls','.asx','.wax','.wvx','.b4s','.kpl','.ram','.smil','.iso', + '.cue','.bin','.mp3','.ogg','.oga','.flac'); + +sub createWidget +{ + my ($parent, $info, $comparison) = @_; + my $widget; + my $withComparisonLabel = 1; + + if ($info->{type} eq 'short text') + { + if ($comparison eq 'range') + { + $widget = new GCRange('text', $parent->{lang}); + $widget->setWidth(16); + $withComparisonLabel = 0; + } + else + { + $widget = new GCShortText; + } + } + elsif ($info->{type} eq 'number') + { + #If we want to have values that are less to the specified one, + #we use max as default to be sure everything will be returned + #in that case. + my $default = $info->{min}; + $default = $info->{max} + if $comparison =~ /^l/; + if (exists $info->{min}) + { + if ($comparison eq 'range') + { + $widget = new GCRange('number', + $parent->{lang}, + $info->{min}, + $info->{max}, + $info->{step}); + $widget->setWidth(16); + $withComparisonLabel = 0; + } + else + { + $widget = new GCNumeric($default, + $info->{min}, + $info->{max}, + $info->{step}); + } + } + else + { + if ($comparison eq 'range') + { + $widget = new GCRange('numeric text', $parent->{lang}); + $widget->setWidth(16); + $withComparisonLabel = 0; + } + else + { + $widget = new GCCheckedText('0-9.'); + } + } + } + elsif ($info->{type} eq 'checked text') + { + $widget = new GCCheckedText($info->{format}); + } + elsif (($info->{type} eq 'history text') + || (($info->{type} =~ /list/) + && ($info->{history} ne 'false'))) + { + $widget = new GCHistoryText; + } + elsif ($info->{type} eq 'options') + { + $widget = new GCMenuList; + $widget->setValues($parent->{model}->getValues($info->{values}), $info->{separator}); + } + elsif ($info->{type} eq 'yesno') + { + $widget = new GCCheckBoxWithIgnore($parent); + $withComparisonLabel = 0; + } + elsif ($info->{type} eq 'date') + { + if ($comparison eq 'range') + { + $widget = new GCRange('date', $parent->{lang}, undef, undef, undef, $parent); + $widget->setWidth(16); + $withComparisonLabel = 0; + } + else + { + $widget = new GCDate($parent->{window}, $parent->{lang}, 1, + $parent->{options}->dateFormat); + } + } + else + { + $widget = new GCShortText; + } + + return ($widget, $withComparisonLabel); +} + +{ + package GCGraphicComponent; + + use base 'Exporter'; + our @EXPORT = qw($somethingChanged); + our $somethingChanged = 0; + + sub expand + { + } + + sub lock + { + } + + sub getMainParent + { + my $self = shift; + return if ! $self->{parent}; + my $tmpWidget = $self; + $tmpWidget = $tmpWidget->{parent} while $tmpWidget && (! $tmpWidget->isa('GCFrame')); + $self->{mainParent} = $tmpWidget; + } + + sub acceptMarkup + { + my $self = shift; + return 0; + } + + sub cleanMarkup + { + my ($self, $text, $encodeSubset) = @_; + $text =~ s|
|\n|g; + if ($encodeSubset) + { + # Encode only the characters set_markup has issues with + $text =~ s|&|&|g; + $text =~ s|<|<|g; + $text =~ s|>|>|g; + } + else + { + $text = GCUtils::encodeEntities($text) + } + return $text; + } + + sub selectAll + { + } + + sub getTagFromSpan + { + my ($self, $desc) = @_; + my @result = (); #('background' => $self->{background}); + my @keyvalues = split / /, $desc; + foreach (@keyvalues) + { + /([^=]*)=(.*)/; + my $key = $1; + my $value = $2; + $value =~ s/('|")//g; + #"' + next if $key =~ /=/; + push @result, ($key, $value); + } + return @result; + } + + sub valueToDisplayed + { + # 1st parameter is self + shift; + # Here we don't change the value; + return shift; + } + + sub hasChanged + { + my $self = shift; + + return $self->{hasChanged}; + } + + sub setChanged + { + my $self = shift; + $self->{hasChanged} = 1; + $somethingChanged = 1; + } + + sub setWidth + { + my ($self, $value) = @_; + } + + sub setHeight + { + my ($self, $height) = @_; + } + + sub resetChanged + { + my $self = shift; + $self->{hasChanged} = 0; + } + + sub activateStateTracking + { + # We do nothing by default + # Other widget should connect a signal handler or something similar + # when the content has been changed + } + + sub getLinkedValue + { + } + sub setLinkedValue + { + } + + sub setLinkedComponent + { + my ($self, $linked) = @_; + $self->{linkedComponent} = $linked; + } +} + +{ + package GCPseudoHistoryComponent; + # + # This is an abstract package handling a little history + # + sub initHistory + { + my ($self, $listType) = @_; + $self->{history} = {}; + + # listType contains the type of the original field: + # 0: No list + # >0: Multiple list (number of columns) + $self->{listType} = $listType; + + $self->{history}->{0} = {'' => 1}; + for(my $i = 1; $i < $listType; $i++) + { + $self->{history}->{$i} = {'' => 1}; + } + $self->{listType} = $listType; + } + + sub addHistory + { + my $self = shift; + + my $value = shift; + my $i; + if (ref($value) eq 'ARRAY') + { + foreach (@$value) + { + $i = 0; + foreach my $item(@$_) + { + $self->{history}->{$i}->{$item} = 1; + $i++; + } + } + } + else + { + # The separator used was ; instead of , + $value =~ s/;/,/g if $value !~ /,/; + my @values = split m/,/, $value; + foreach (@values) + { + my @items = split m/;/; + $i = 0; + foreach my $item(@items) + { + $self->{history}->{$i}->{$item} = 1; + $i++; + } + } + } + } + + sub setDropDown + { + } + + sub getValues + { + my $self = shift; + my @array; + + + if ($self->{listType} < 1) + { + @array = sort keys %{$self->{history}->{0}}; + } + else + { + foreach (sort keys %{$self->{history}}) + { + my @tmpArray = sort keys %{$self->{history}->{$_}}; + push @array, \@tmpArray; + } + } + return \@array; + } + + sub setValues + { + my ($self, $values) = @_; + if ($self->{listType} == 0) + { + $self->{history} = {}; + $self->{history}->{0}->{$_} = 1 foreach (@$values); + } + else + { + $self->{history} = {}; + for (my $i = 0; $i < $self->{listType}; $i++) + { + $self->{history}->{$i}->{$_} = 1 foreach (@{$values->[$i]}); + } + } + } +} + +{ + package GCLinkedComponent; + @GCLinkedComponent::ISA = ('GCGraphicComponent'); + + sub new + { + my ($proto, $linked) = @_; + my $class = ref($proto) || $proto; + + my $self = {linked => $linked}; + bless ($self, $class); + $linked->setLinkedComponent($self); + return $self; + } + + sub setValue + { + my $self = shift; + $self->setChanged; + return $self->{linked}->setLinkedValue(@_); + } + + sub getValue + { + my $self = shift; + return $self->{linked}->getLinkedValue(@_); + } + + sub resetChanged + { + my $self = shift; + $self->{hasChanged} = 0; + return $self->{linked}->resetChanged(@_); + } + + sub hide + { + my $self = shift; + $self->{linked}->setLinkedActivated(0); + } + + sub show + { + my $self = shift; + $self->{linked}->setLinkedActivated(1); + } +} + +{ + package GCShortText; + + use Glib::Object::Subclass + Gtk2::Entry:: + ; + + @GCShortText::ISA = ('Gtk2::Entry', 'GCGraphicComponent'); + + sub new + { + my ($proto) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + + bless ($self, $class); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + return if $self->{readOnly}; + $self->signal_connect('changed' => sub {; + $self->setChanged; + }); + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub selectAll + { + my $self = shift; + $self->select_region(0, length($self->getValue)); + $self->grab_focus; + } + + sub getValue + { + my $self = shift; + return $self->get_text; + } + + sub setValue + { + my ($self, $value) = @_; + $self->set_text($value); + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub setWidth + { + my ($self, $value) = @_; + $self->set_width_chars($value); + } + + sub setReadOnly + { + my $self = shift; + $self->set_editable(0); + $self->{readOnly} = 1; + } + + sub lock + { + my ($self, $locked) = @_; + return if $self->{readOnly}; + #$self->can_focus(!$locked); + $self->set_editable(!$locked); + } +} + +our $hasSpellChecker; +BEGIN { + eval 'use Gtk2::Spell'; + if ($@) + { + $hasSpellChecker = 0; + } + else + { + $hasSpellChecker = 1; + } +} +{ + package GCLongText; + + + use Glib::Object::Subclass + Gtk2::ScrolledWindow:: + ; + + @GCLongText::ISA = ('Gtk2::ScrolledWindow', 'GCGraphicComponent'); + + sub new + { + my ($proto) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + + $self->{text} = new Gtk2::TextView; + $self->{text}->set_editable(1); + $self->{text}->set_wrap_mode('word'); + $self->set_border_width(0); + $self->set_shadow_type('in'); + $self->set_policy('automatic', 'automatic'); + #$self->set_size_request(-1,80); + + $self->add($self->{text}); + $self->{spellChecker} = 0; + + bless ($self, $class); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->{text}->get_buffer->signal_connect('changed' => sub {; + $self->setChanged; + }); + } + + sub setSpellChecking + { + my ($self, $activate, $lang) = @_; + return if ! $GCGraphicComponents::hasSpellChecker; + if ($activate) + { + $lang ||= $ENV{LANG}; + if ($self->{spellChecker}) + { + return if $lang eq $self->{lang}; + $self->setSpellChecking(0) + } + eval { + $self->{spellChecker} = Gtk2::Spell->new_attach($self->{text}); + $self->{spellChecker}->set_language($lang); + $self->{lang} = $lang; + }; + if ($@) + { + $self->setSpellChecking(0); + } + } + else + { + $self->{spellChecker}->detach if $self->{spellChecker}; + $self->{spellChecker} = 0; + } + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub getValue + { + my $self = shift; + my $buffer = $self->{text}->get_buffer; + my $text = $buffer->get_text($buffer->get_start_iter, + $buffer->get_end_iter, 1); + #$text =~s/\n//g; + return $text; + } + + sub setValue + { + my ($self, $text) = @_; + #$text =~s//\n/g; + $text = '' if !defined $text; + $self->{text}->get_buffer->set_text($text); + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->{text}->can_focus(!$locked); + } + + sub setHeight + { + my ($self, $height) = @_; + + # TODO Change height + } +} + +{ + package GCHistoryText; + + + use Glib::Object::Subclass + Gtk2::Combo:: + ; + + @GCHistoryText::ISA = ('Gtk2::Combo', 'GCGraphicComponent'); + + sub new + { + my ($proto) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + $self->{history} = {'' => 1}; + + # Settings for auto-completion + $self->{completionModel} = Gtk2::ListStore->new('Glib::String'); + $self->{completion} = Gtk2::EntryCompletion->new; + $self->{completion}->set_model($self->{completionModel}); + $self->{completion}->set_text_column(0); + $self->entry->set_completion($self->{completion}); + + bless ($self, $class); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->entry->signal_connect('changed' => sub {; + $self->setChanged; + }); + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub selectAll + { + my $self = shift; + $self->entry->select_region(0, -1); + $self->entry->grab_focus; + } + + sub getValue + { + my $self = shift; + my @children = $self->get_children; + return $children[0]->get_text if $children[0]; + } + + sub setValue + { + my ($self, $text) = @_; + my @children = $self->get_children; + $children[0]->set_text($text); + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub setWidth + { + my ($self, $value) = @_; + ($self->get_children)[0]->set_width_chars($value); + } + + sub lock + { + my ($self, $locked) = @_; + + ($self->get_children)[0]->can_focus(!$locked); + ($self->get_children)[1]->set_sensitive(!$locked); + } + + sub addHistory + { + my $self = shift; + my $value = (scalar @_) ? shift : $self->getValue; + my $noUpdate = shift; + $value =~ s/^\s*//; + if (!exists $self->{history}->{$value}) + { + $self->{history}->{$value} = 1; + if (!$noUpdate) + { + $self->setDropDown(sort keys %{$self->{history}}); + } + } + } + + sub setDropDown + { + my $self = shift; + my @values = (scalar @_) ? @_ : sort keys %{$self->{history}}; + my $previousValue = $self->getValue; + + # Update history list + $self->set_popdown_strings(@values); + + # Restore value as it is lost when updating list + $self->setValue($previousValue); + + # Update auto-completion list + $self->{completionModel}->clear; + foreach (@values) + { + my $iter = $self->{completionModel}->append; + $self->{completionModel}->set($iter, + 0 => $_); + } + } + + sub getValues + { + my $self = shift; + my @array = sort keys %{$self->{history}}; + return \@array; + } + + sub setValues + { + my ($self, $values) = @_; + $self->{history} = {}; + $self->setDropDown(@$values); + $self->{history}->{$_} = 1 foreach (@$values); + } + + sub setActivate + { + my $value + } + + sub popup + { + my $self = shift; + ($self->get_children)[1]->grab_focus; + ($self->get_children)[1]->signal_emit('activate'); + } +} + +{ + package GCNumeric; + + + use Glib::Object::Subclass + Gtk2::SpinButton:: + ; + + @GCNumeric::ISA = ('Gtk2::SpinButton', 'GCRatingWidget', 'GCGraphicComponent'); + + use GCWidgets; + use GCLang; + + sub new + { + my ($proto, $default, $min, $max, $step, $format) = @_; + my $class = ref($proto) || $proto; + + my $self; + + if (($format eq 'text') || (!$format)) + { + # Standard numeric field + $step = 1 if !$step; + my $pageStep = 10 * $step; + + my $decimals = 0; + $decimals = length($1) if $step =~ /\.(.*)$/; + + my $accel = 0; + my $values = ($max - $min) / $step; + $accel = 0.2 if $values > 100; + $accel = 0.5 if $values > 500; + $accel = 1.0 if $values > 2000; + $default = 0 if $default eq ''; + my $adj = Gtk2::Adjustment->new($default, $min, $max, $step, $pageStep, 0) ; + $self = $class->SUPER::new($adj, $accel, $decimals); + $self->{default} = $default; + $self->{step} = $step; + $self->{pageStep} = $pageStep; + $self->{accel} = $accel; + $self->{format} = 'text'; + $self->set_numeric(1); + } + elsif ($format eq 'graphical') + { + # Graphical rating widget + $default = 0 if $default eq ''; + $max = 10 if $max eq ''; + + $self = GCRatingWidget->new (maxStars=>$max, rating=>$default, direction=>GCLang::languageDirection($ENV{LANG})); + $self->{format} = 'graphical'; + } + bless ($self, $class); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->signal_connect('changed' => sub {; + $self->setChanged; + }); + } + + sub isEmpty + { + my $self = shift; + + if ($self->{format} eq 'text') + { + return $self->get_text eq ''; + } + elsif ($self->{format} eq 'graphical') + { + return $self->get('rating') eq 0; + } + } + + sub selectAll + { + my $self = shift; + if ($self->{format} eq 'text') + { + $self->select_region(0, length($self->getValue)); + } + } + + sub getValue + { + my $self = shift; + my $value; + + if ($self->{format} eq 'text') + { + $value = $self->get_text; + $value =~ s/,/./; + } + elsif ($self->{format} eq 'graphical') + { + $value = $self->get('rating'); + } + + return $value; + } + + sub setValue + { + my ($self, $text) = @_; + + if ($self->{format} eq 'text') + { + $text = $self->{default} if $text eq ''; + $self->set_value($text); + } + elsif ($self->{format} eq 'graphical') + { + $text = $self->{default} if $text eq ''; + $self->set_rating($text); + } + } + + sub clear + { + my $self = shift; + + if ($self->{format} eq 'text') + { + $self->set_value($self->{default}); + } + elsif ($self->{format} eq 'graphical') + { + $self->set_rating($self->{default}); + } + } + + sub setWidth + { + my ($self, $value) = @_; + $self->set_width_chars($value); + } + + sub lock + { + my ($self, $locked) = @_; + + if ($self->{format} eq 'text') + { + $self->can_focus(!$locked); + my $step = ($locked ? 0 : $self->{step}); + $self->set_increments($step, $self->{pageStep}); + } + elsif ($self->{format} eq 'graphical') + { + $self->set(sensitive=>!$locked); + } + } +} + +{ + package GCCheckedText; + + use Glib::Object::Subclass + Gtk2::Entry:: + ; + + @GCCheckedText::ISA = ('GCShortText'); + + sub new + { + my ($proto, $format) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + bless ($self, $class); + my $forbidden = qr/[^$format]/; + $self->signal_connect('insert-text' => sub { + # Remove forbidden characters + $_[1] =~ s/$forbidden//g; + () # this callback must return either 2 or 0 items. + }); + + return $self; + } +} + +{ + package GCRange; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCRange::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); + + sub new + { + my ($proto, $type, $lang, $min, $max, $step, $parent) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + bless ($self, $class); + + if ($type eq 'text') + { + $self->{from} = new GCShortText; + $self->{to} = new GCShortText; + } + elsif ($type eq 'numeric text') + { + new GCCheckedText('0-9.'); + $self->{to} = new GCCheckedText('0-9.'); + } + elsif ($type eq 'date') + { + $self->{from} = new GCDate($parent->{window}, $lang, 1, + $parent->{options}->dateFormat); + $self->{to} = new GCDate($parent->{window}, $lang, 1, + $parent->{options}->dateFormat); + } + else + { + $self->{from} = new GCNumeric($min, $min, $max, $step); + $self->{to} = new GCNumeric($max, $min, $max, $step); + } + $self->pack_start(Gtk2::Label->new($lang->{PanelFrom}), 0, 0, 12); + $self->pack_start($self->{from}, 1, 1, 0); + $self->pack_start(Gtk2::Label->new($lang->{PanelTo}), 0, 0, 12); + $self->pack_start($self->{to}, 1, 1, 0); + + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->{from}->activateStateTracking; + $self->{to}->activateStateTracking; + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub getValue + { + my $self = shift; + return $self->{from}->getValue.';'.$self->{to}->getValue; + } + + sub setValue + { + my ($self, $value) = @_; + my @values = split m/;/, $value; + $self->{from}->setValue($values[0]); + $self->{to}->setValue($values[0]); + } + + sub setWidth + { + my ($self, $value) = @_; + $self->{from}->setWidth($value / 2); + $self->{to}->setWidth($value / 2); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->{from}->lock(!$locked); + $self->{to}->lock(!$locked); + } + + sub signal_connect + { + my $self = shift; + $self->{from}->signal_connect(@_); + $self->{to}->signal_connect(@_); + } + + sub AUTOLOAD + { + my $self = shift; + my $name = our $AUTOLOAD; + return if $name =~ /::DESTROY$/; + $name =~ s/.*?::(.*)/$1/; + $self->{from}->$name(@_); + $self->{to}->$name(@_); + } +} + +{ + package GCDate; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCDate::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); + + sub selectValue + { + my $self = shift; + $self->{dialog} = new GCDateSelectionDialog($self->{mainParent}) + if ! $self->{dialog}; + $self->{dialog}->date($self->getRawValue); + if ($self->{dialog}->show) + { + $self->setValue($self->{dialog}->date); + } + $self->{parent}->showMe; + } + + sub new + { + my ($proto, $parent, $lang, $reverseDate, $format) = @_; + $format ||= '%d/%m/%Y'; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + bless ($self, $class); + + $self->{parent} = $parent; + $self->getMainParent; + $self->{reverseDate} = $reverseDate; + $self->{entry} = Gtk2::Entry->new; #_with_max_length(10); + $self->{entry}->set_width_chars(12); + $self->{button} = new Gtk2::Button($lang->{PanelDateSelect}); + $self->{button}->signal_connect('clicked' => sub { + $self->selectValue; + }); + $self->pack_start($self->{entry}, 1, 1, 0); + $self->pack_start($self->{button}, 0, 0, 0); + + $self->{format} = $format; + #$self->{format} = '%d %B %Y'; + return $self; + } + + sub setFormat + { + my ($self, $format) = @_; + my $current = $self->getValue; + $self->{format} = $format; + $self->setValue($current); + } + + sub activateStateTracking + { + my $self = shift; + $self->{entry}->signal_connect('changed' => sub {; + $self->setChanged; + }); + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub getCurrentDate + { + my $self = shift; + + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + return sprintf('%02d/%02d/%4d', $mday, $mon+1, 1900+$year); + } + + + sub getRawValue + { + my $self = shift; + my $value = $self->{entry}->get_text; + $value = GCUtils::strToTime($value, $self->{format}) + if $self->{format} && $value; + return $value; + + } + + sub getValue + { + my $self = shift; + my $value = $self->getRawValue; + return GCPreProcess::reverseDate($value) if $self->{reverseDate}; + return $value; + } + + sub setValue + { + my ($self, $text) = @_; + $text = GCPreProcess::restoreDate($text) if $self->{reverseDate}; + if ($text eq 'current') + { + $text = $self->getCurrentDate; + $self->setChanged; + } + $text = GCUtils::timeToStr($text, $self->{format}) if $self->{format}; + $self->{entry}->set_text($text); + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub setWidth + { + my ($self, $value) = @_; + $self->{entry}->set_width_chars($value); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->{entry}->can_focus(!$locked); + $self->{button}->set_sensitive(!$locked); + } +} + +{ + package GCFile; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCFile::ISA = ('Gtk2::HBox', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); + + use File::Basename; + + sub selectValue + { + my $self = shift; + + my $dialog = GCFileChooserDialog->new($self->{title}, + $self->{mainParent}, + $self->{type}, + $self->{withFilter}); + $dialog->set_filename($self->getValue); + $dialog->set_pattern_filter($self->{patterns}) + if $self->{patterns}; + my $response = $dialog->run; + if ($response eq 'ok') + { + $self->setValue($dialog->get_filename); + } + $dialog->hide; + $self->{parent}->showMe; + } + + sub setPatternFilter + { + my ($self, $patterns) = @_; + $self->{patterns} = $patterns; + } + + sub setType + { + my ($self, $type, $withFilter) = @_; + + if (($type ne $self->{type}) || ($withFilter != $self->{withFilter})) + { + $self->{dialog}->destroy + if $self->{dialog}; + $self->{dialog} = undef; + $self->{type} = $type; + $self->{withFilter} = $withFilter; + } + } + + sub setTitle + { + my ($self, $title) = @_; + $self->{title} = $title; + if ($self->{dialog}) + { + $self->{dialog}->setTitle($title); + } + } + + sub new + { + my ($proto, $parent, $title, $type, $withFilter, $defaultValue, $allowContextMenu, $fieldType) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + bless ($self, $class); + $self->{parent} = $parent; + $self->getMainParent; + + $title ||= $self->{parent}->{lang}->{PanelSelectFileTitle}; + $self->{title} = $title; + $type ||= 'open'; + $self->{type} = $type; + $withFilter = 0 if !$withFilter; + $self->{withFilter} = $withFilter; + + $self->{entry} = Gtk2::Entry->new; + + $self->{button} = new GCButton($self->{parent}->{lang}->{PanelSelectFileTitle}); + + # For video/ebook/audio files, set file pattern filters + if (($fieldType eq 'video') || ($fieldType eq 'ebook') || ($fieldType eq 'audio')) + { + $self->setPatternFilter([$self->{parent}->{lang}->{FileVideoFiles}, \@videoExtensions]) + if ($fieldType eq 'video'); + $self->setPatternFilter([$self->{parent}->{lang}->{FileEbookFiles}, \@ebookExtensions]) + if ($fieldType eq 'ebook'); + $self->setPatternFilter([$self->{parent}->{lang}->{FileAudioFiles}, \@audioExtensions]) + if ($fieldType eq 'audio'); + $self->{withFilter} = 1; + } + + $self->{button}->signal_connect('clicked' => sub { + $self->selectValue; + }); + + if ($allowContextMenu) + { + my @subMenuFileChoose; + $subMenuFileChoose[0] = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseFile}); + $subMenuFileChoose[0]->signal_connect("activate" , sub {$self->selectValue}); + $subMenuFileChoose[1] = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseFolder}); + $subMenuFileChoose[1]->signal_connect("activate" , sub { + $self->{type} = 'select-folder'; + $self->{title} = $parent->{lang}->{ContextChooseFolder}; + $self->selectValue; + }); + $self->{button}->setContextMenu(\@subMenuFileChoose); + $self->{button}->enableContextMenu; + } + + $self->pack_start($self->{entry}, 1, 1, 0); + + if ($defaultValue) + { + $self->{defaultButton} = GCButton->newFromStock('gtk-undo', 0, $parent->{lang}->{PanelRestoreDefault}); + $parent->{tooltips}->set_tip($self->{defaultButton}, + $parent->{lang}->{PanelRestoreDefault}.$parent->{lang}->{Separator}.$defaultValue) + if $parent->{tooltips}; + $self->pack_start($self->{defaultButton}, 0, 0, 0); + $self->{defaultButton}->signal_connect('clicked' => sub { + $self->setValue($defaultValue); + }); + } + + # Add the 'select file' button only if the field is not a url field + $self->pack_start($self->{button}, 0, 0, 0) + if ($fieldType ne 'url'); + + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->{entry}->signal_connect('changed' => sub {; + $self->setChanged; + }); + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub getValue + { + my $self = shift; + return $self->{entry}->get_text; + } + + sub setValue + { + my ($self, $text) = @_; + $self->{entry}->set_text($text); + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub setWidth + { + my ($self, $value) = @_; + $self->{entry}->set_width_chars($value); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->{entry}->can_focus(!$locked); + $self->{button}->set_sensitive(!$locked); + } +} + +{ + package GCUrl; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCUrl::ISA = ('GCFile'); + + sub selectValue + { + my $self = shift; + my $url = $self->getValue; + return if !$url; + $self->{opener}->launch($url, 'url'); + } + + sub new + { + my ($proto, $opener) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new(undef, '', 'url'); + $self->{opener} = $opener; + bless ($self, $class); + return $self; + } +} + +{ + package GCButton; + + use Glib::Object::Subclass + Gtk2::Button:: + ; + + @GCButton::ISA = ('Gtk2::Button', 'GCGraphicComponent'); + + sub new + { + my ($proto, $label) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new($label); + + bless ($self, $class); + return $self; + } + + sub newFromStock + { + my ($proto, $stock, $nolabel, $label) = @_; + my $class = ref($proto) || $proto; + + $nolabel = 0 if ($^O =~ /win32/i); + + my $self = $class->SUPER::new_from_stock($stock); + + if ($nolabel) + { + my $tmpWidget = $self; + $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox'); + ($tmpWidget->get_children)[1]->destroy; + } + elsif ($label) + { + my $tmpWidget = $self; + $tmpWidget = $tmpWidget->child while ! $tmpWidget->isa('Gtk2::HBox'); + ($tmpWidget->get_children)[1]->set_label($label); + } + + bless ($self, $class); + return $self; + } + + sub isEmpty + { + my $self = shift; + + return 0; + } + + sub getValue + { + my $self = shift; + } + + sub setValue + { + my ($self, $value) = @_; + $self->setChanged; + return $value; + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->set_sensitive(!$locked); + } + + sub setWidth + { + my ($self, $value) = @_; + $self->set_size_request($value, -1); + } + sub setContextMenu + { + my ($self, $menu) = @_; + $self->{popupContextMenu}=new Gtk2::Menu if !$self->{popupContextMenu}; + for my $i(0..$#{$self->{popupContextMenu}->{items}}) + { + $self->{popupContextMenu}->remove($self->{popupContextMenu}->{items}->[$i]); + $self->{popupContextMenu}->{items}->[$i]->destroy; + } + delete $self->{popupContextMenu}->{items}; + for my $i(0..$#$menu) + { + $self->{popupContextMenu}->{items}->[$i]=$menu->[$i]; + $self->{popupContextMenu}->append($self->{popupContextMenu}->{items}->[$i]); + } + $self->{popupContextMenu}->show_all; + } + sub enableContextMenu + { + my ($self,$button) = @_; + #TODO enabled context for one button mouses + $button=3 if !$button; + $self->{contextMenuSignalHandler}=$self->signal_connect('button_press_event' => sub { + my ($widget, $event) = @_; + return 0 if $event->button ne $button; + $self->{popupContextMenu}->popup(undef, undef, undef, undef, $event->button, $event->time); + return 0; + }) if !$self->{contextMenuSignalHandler}; + } + sub disableContextMenu + { + my $self = shift; + if($self->{contextMenuSignalHandler}) + { + $self->signal_handler_disconnect($self->{contextMenuSignalHandler}); + delete $self->{contextMenuSignalHandler}; + } + } +} + +{ + package GCUrlButton; + + use Glib::Object::Subclass + Gtk2::Button:: + ; + + @GCUrlButton::ISA = ('GCButton', 'GCGraphicComponent'); + + sub new + { + my ($proto, $label, $opener) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new($label); + $self->{opener}=$opener; + $self->{defaultLabel} = $label; + $self->{clicSignalHandler}=$self->signal_connect('clicked' => sub { + $self->{opener}->launch($self->{url}, 'url'); + }); + + + bless ($self, $class); + return $self; + } + + sub getValue + { + my $self = shift; + + return $self->{value}; + } + + sub setValue + { + my ($self, $value) = @_; + + $self->setChanged; + my @urls=split ';',$value; + if(scalar(@urls)>1) + { + my (@menu,$i,$url); + foreach my $urlName(@urls) + { + $urlName =~ /^(.*?)##(.*)$/; + my $name=$2; + my $menuItem=Gtk2::MenuItem->new_with_label($name); + $menuItem->signal_connect("activate" ,sub { + $self->{opener}->launch($_[1], 'url') + },$1); + push @menu,$menuItem; + } + $self->{url} =''; + $self->setContextMenu(\@menu); + $self->enableContextMenu(1); + $self->lock(0); + #$self->signal_handler_block($self->{clicSignalHandler}); + $self->setLabel($self->{defaultLabel}); + } + else + { + if ($value =~ /^(.*?)##(.*)$/) + { + $self->{url} = $1; + $self->setLabel($2); + } + else + { + $self->{url} = $value; + $self->setLabel($self->{defaultLabel}); + } + $self->lock(!$self->{url}); + $self->disableContextMenu; + #$self->signal_handler_unblock($self->{clicHandler}); + } + $self->{value} = $value; + } + + sub setLabel + { + my ($self, $label) = @_; + $self->set_label($label); + } +} + +{ + package GCCheckBox; + + use Glib::Object::Subclass + Gtk2::CheckButton:: + ; + + @GCCheckBox::ISA = ('Gtk2::CheckButton', 'GCGraphicComponent'); + + sub new + { + my ($proto, $label) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new($label); + + bless ($self, $class); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + + $self->signal_connect('toggled' => sub { + $self->setChanged; + }); + } + + sub isEmpty + { + my $self = shift; + + return 0; + } + + sub getValue + { + my $self = shift; + return 1 if ($self->get_active); + return 0; + } + + sub getValueAsText + { + my $self = shift; + return 'true' if ($self->get_active); + return 'false'; + } + + sub setValue + { + my ($self, $value) = @_; + $self->set_active($value); + } + + sub clear + { + my $self = shift; + $self->setValue(0); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->set_sensitive(!$locked); + } +} + +{ + package GCCheckBoxWithIgnore; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCCheckBoxWithIgnore::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new(0,0); + bless ($self, $class); + + $self->{check}->[0] = new Gtk2::RadioButton(undef,$parent->{lang}->{CheckUndef}); + $self->{group} = $self->{check}->[0]->get_group; + $self->{check}->[1] = new Gtk2::RadioButton($self->{group},$parent->{lang}->{CheckNo}); + $self->{check}->[2] = new Gtk2::RadioButton($self->{group},$parent->{lang}->{CheckYes}); + + $self->pack_start($self->{check}->[0], 0, 0, 10); + $self->pack_start($self->{check}->[1], 0, 0, 10); + $self->pack_start($self->{check}->[2], 0, 0, 10); + + return $self; + } + + sub activateStateTracking + { + my $self = shift; + + foreach (@{$self->{check}}) + { + $_->signal_connect('toggled' => sub { + $self->setChanged; + }); + } + } + + sub isEmpty + { + my $self = shift; + + return $self->{check}->[0]->get_active; + } + + sub getValue + { + my $self = shift; + my $i = 0; + foreach (@{$self->{check}}) + { + last if $self->{check}->[$i]->get_active; + $i++; + } + $i--; + return $i; + } + + sub setValue + { + my ($self, $value) = @_; + $self->{check}->[$value + 1]->set_active(1); + } + + sub clear + { + my $self = shift; + $self->setValue(-1); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->set_sensitive(!$locked); + } +} + +{ + package GCMultipleList; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCMultipleList::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); + + sub new + { + my ($proto, $parent, $number, $labels, $withHistory, $readonly, $useFiles,$types) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new(0,0); + + $self->{number} = $number; + $self->{readonly} = $readonly; + $self->{withHistory} = $withHistory; + + my $hboxActions = new Gtk2::HBox(0,0); + + my @histories; + my @listColumns; + my $i; + for $i (0..($number - 1)) + { + push @histories, {'' => 1}; + push @listColumns, ($labels->[$i] => 'text'); + next if $readonly; + if ($useFiles) + { + $self->{entries}->[$i] = GCFile->new($parent); + $self->{entries}->[$i]->{entry}->signal_connect('activate' => sub { + $self->addValues; + }); + } + else + { + if($types && $types->[$i] eq 'date') + { + $self->{entries}->[$i] = GCDate->new($parent, $parent->{lang}, 1,$parent->{options}->dateFormat); + $self->{entries}->[$i]->{entry}->signal_connect('activate' => sub { + $self->addValues; + $self->{entries}->[0]->grab_focus; + }); + $self->{withHistoryField}->[$i]=0; + } + elsif ($withHistory && (!$types || $types->[$i] eq 'history')) + { + $self->{entries}->[$i] = GCHistoryText->new; + $self->{entries}->[$i]->entry->signal_connect('activate' => sub { + my $widget = $self->{entries}->[$i]; + if ($widget->getValue) + { + $self->addValues; + # FIXME. It seems this does nothing: + $self->{entries}->[0]->grab_focus; + } + $widget->entry->signal_stop_emission_by_name('activate'); + }); + $self->{withHistoryField}->[$i]=1; + } + else + { + $self->{entries}->[$i] = GCShortText->new; + $self->{entries}->[$i]->signal_connect('activate' => sub { + $self->addValues; + $self->{entries}->[0]->grab_focus; + }); + $self->{withHistoryField}->[$i]=0 if $withHistory; + } + } + $self->{entries}->[$i]->setWidth(12); + $hboxActions->pack_start($self->{entries}->[$i], 1, 1, 6); + } + + $self->{histories} = [\@histories]; + + $self->{box} = new Gtk2::VBox(0,0); + + # If list belongs to an expander, set box size to a reasonable size + $self->{box}->{signalHandler} = $self->{box}->signal_connect('size-allocate' => sub { + if (($self->{realParent}) && ($self->{realParent}->isa('GCExpander'))) + { + my $width = $self->allocation->width - ( 2 * $GCUtils::margin) ; + $self->{box}->set_size_request(($width >= -1) ? $width : -1 , -1); + return 0; + } + }); + + $self->{list} = new Gtk2::SimpleList(@listColumns); + for $i (0..($number - 1)) + { + $self->{list}->set_column_editable($i, 1); + } + $self->{list}->unset_rows_drag_source; + $self->{list}->unset_rows_drag_dest; + $self->{list}->set_reorderable(1); + #($self->{list}->get_column(0)->get_cell_renderers)[0]->set('wrap-mode' => 'word'); + + for $i (0..($number - 1)) + { + $self->{list}->get_column($i)->set_resizable(1); + } + my $scroll = new Gtk2::ScrolledWindow; + $scroll->set_policy ('automatic', 'automatic'); + $scroll->set_shadow_type('etched-in'); + $scroll->set_size_request(-1, 120); + $scroll->add($self->{list}); + $self->{box}->pack_start($scroll, 1, 1, 2); + if (!$readonly) + { + $self->{addButton} = GCButton->newFromStock('gtk-add', 0); + $self->{addButton}->signal_connect('clicked' => sub { + $self->addValues; + }); + $self->{removeButton} = GCButton->newFromStock('gtk-remove', 0); + $hboxActions->pack_start($self->{addButton}, 0, 0, 6); + $hboxActions->pack_start($self->{removeButton}, 0, 0, 6); + } + else + { + $self->{removeButton} = GCButton->newFromStock('gtk-remove', 0); + $self->{clearButton} = GCButton->newFromStock('gtk-clear', 0); + $self->{clearButton}->signal_connect('clicked' => sub { + $self->clear; + }); + $hboxActions->pack_start($self->{removeButton}, 1, 0, 6); + $hboxActions->pack_start($self->{clearButton}, 1, 0, 6); + } + $self->{box}->pack_start($hboxActions, 0, 0, 6) + if $readonly < 2; + + $self->{removeButton}->signal_connect('clicked' => sub { + my @idx = $self->{list}->get_selected_indices; + my $selected = $idx[0]; + splice @{$self->{list}->{data}}, $selected, 1; + $selected-- if ($selected >= scalar(@{$self->{list}->{data}})); + $selected = 0 if $selected < 0 ; + $self->{list}->select($selected); + }); + + $self->{list}->signal_connect('key-press-event' => sub { + my ($widget, $event) = @_; + my $key = Gtk2::Gdk->keyval_name($event->keyval); + if ((!$self->{readonly}) && ($key eq 'Delete')) + { + $self->{removeButton}->activate; + return 1; + } + # Let key be managed by Gtk2 + return 0; + }); + + + $self->pack_start($self->{box},1,$self->{readonly},0); + + bless ($self, $class); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->{list}->get_model->signal_connect('row-inserted' => sub { + $self->setChanged; + }); + $self->{list}->get_model->signal_connect('row-deleted' => sub { + $self->setChanged; + }); + $self->{list}->get_model->signal_connect('row-changed' => sub { + $self->setChanged; + }); + } + + sub expand + { + my $self = shift; + $self->set_child_packing($self->{box},1,1,0,'start'); + } + + sub addValues + { + my ($self, @values) = @_; + if (!$self->{readonly}) + { + for my $i (0..($self->{number} - 1)) + { + $values[$i] = $self->{entries}->[$i]->getValue if !$values[$i]; + $self->{entries}->[$i]->addHistory($values[$i]) + if $self->{withHistory} && $self->{withHistoryField}->[$i]; + $self->{entries}->[$i]->clear; + } + } + # Check that at least one value is not empty + my $isEmpty = 1; + for my $val (@values) + { + if ($val) + { + $isEmpty = 0; + last; + } + } + if (!$isEmpty) + { + push @{$self->{list}->{data}}, \@values; + $self->{list}->select($#{$self->{list}->{data}}); + my $path = $self->{list}->get_selection->get_selected_rows; + $self->{list}->scroll_to_cell($path) if $path; + } + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub getValue + { + my $self = shift; + my $formated = shift; + + if ($formated) + { + return GCPreProcess::multipleList($self->{list}->{data}, $self->{number}); + } + else + { + # As data in list is a tied array, we need to copy all the values + my @value; + foreach (@{$self->{list}->{data}}) + { + push @value, []; + foreach my $col(@$_) + { + push @{$value[-1]}, $col; + } + } + return \@value; + } + } + + sub setValue + { + my ($self, $value) = @_; + + if (ref($value) eq 'ARRAY') + { + @{$self->{list}->{data}} = @{$value}; + } + else + { + # The separator used was ; instead of , + $value =~ s/;/,/g if $value !~ /,/; + @{$self->{list}->{data}} = (); + my @values = split m/,/, $value; + foreach my $entry (@values) + { + my @items = split m/;/, $entry; + s/^\s*// foreach(@items); + push @{$self->{list}->{data}}, \@items; + } + } + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub lock + { + my ($self, $locked) = @_; + return if $self->{readonly}; + $self->{addButton}->set_sensitive(!$locked); + $self->{removeButton}->set_sensitive(!$locked); + foreach (@{$self->{entries}}) + { + $_->lock($locked); + } + } + + sub addHistory + { + my $self = shift; + my $value = (scalar @_) ? shift : $self->getValue; + my $noUpdate = shift; + + return if $self->{readonly}; + my $i; + my $item; + if (ref($value) eq 'ARRAY') + { + foreach (@$value) + { + $i = 0; + foreach $item(@$_) + { + $self->{entries}->[$i]->addHistory($item, $noUpdate) if $self->{withHistoryField}->[$i]; + $i++; + } + } + } + else + { + # The separator used was ; instead of , + $value =~ s/;/,/g if $value !~ /,/; + my @values = split m/,/, $value; + foreach (@values) + { + my @items = split m/;/; + $i = 0; + foreach my $item(@items) + { + $self->{entries}->[$i]->addHistory($item, $noUpdate) if $self->{withHistoryField}->[$i]; + $i++; + } + #push @{$self->{list}->{data}}, \@items; + } + } + } + + sub setDropDown + { + my $self = shift; + + my $i = 0; + foreach (@{$self->{entries}}) + { + $_->setDropDown if $self->{withHistoryField}->[$i]; + $i++; + } + } + + sub getValues + { + my $self = shift; + return [] if $self->{readonly}; + my @array; + my $i=0; + foreach (@{$self->{entries}}) + { + my $val=[]; + $val=$_->getValues if ($self->{withHistoryField}->[$i++]); + push @array, $val; + } + # = sort keys %{$self->{history}}; + return \@array; + } + + sub setValues + { + my ($self, $values) = @_; + return if $self->{readonly}; + my $i = 0; + foreach (@$values) + { + $self->{entries}->[$i]->setValues($_) if ($self->{withHistoryField}->[$i]); + $i++; + } + } +} + + +{ + package GCItemImage; + + use Glib::Object::Subclass + Gtk2::Image:: + ; + + @GCItemImage::ISA = ('Gtk2::Image', 'GCGraphicComponent'); + + use File::Spec; + use File::Basename; + + sub new + { + my ($proto, $options, $parent, $fixedSize, $width, $height) = @_; + my $class = ref($proto) || $proto; +# my $self = Gtk2::Image->new_from_file($parent->{defaultImage}); + my $self = Gtk2::Image->new; + $self->{options} = $options; + #$self->{defaultImage} = $defaultImage; + $self->{parent} = $parent; + $self->{displayedImage} = ''; + $self->{fixedSize} = $fixedSize; + bless ($self, $class); + if ($width && $height) + { + $self->{width} = $width; + $self->{height} = $height; + } + else + { + $self->{width} = 120; + $self->{height} = 160; + } + $self->{immediate} = 0; + return $self; + } + + sub setImmediate + { + my ($self) = @_; + $self->{immediate} = 1; + } + sub activateStateTracking + { + my $self = shift; + $self->{trackState} = 1; + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub setValue + { + my ($self, $displayedImage, $placer) = @_; + $self->{displayedImage} = $displayedImage; + $self->setChanged if $self->{trackState}; + if ($self->{immediate}) + { + $self->setPicture; + } + else + { + Glib::Source->remove($self->{timer}) + if $self->{timer}; + $self->{timer} = Glib::Timeout->add(100, sub { + $self->setPicture; + $placer->placeImg if $placer; + return 0; + }); + } + } + + sub setPicture + { + my $self = shift; + $self->{timer} = 0; + my $displayedImage = GCUtils::getDisplayedImage($self->{displayedImage}, + $self->{parent}->{defaultImage}, + $self->{options}->file); + my $pixbuf; + eval + { + $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage); + }; + if ($@) + { + $displayedImage = $self->{parent}->{defaultImage}; + $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($displayedImage); + } + $self->{realImage} = $displayedImage; + $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf, $self->{width}, $self->{height}); + $self->set_from_pixbuf($pixbuf); + $self->set_size_request($self->{width}, $self->{height}) if $self->{fixedSize}; + } + + sub getValue + { + my $self = shift; + return $self->{displayedImage}; + } + + sub getFile + { + my $self = shift; + return $self->{realImage}; + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub lock + { + my ($self, $locked) = @_; + } + + sub setWidth + { + my ($self, $value) = @_; + $self->{width} = $value; + } + + sub setHeight + { + my ($self, $value) = @_; + $self->{height} = $value; + } + + sub getSize + { + my $self = shift; + my $pixbuf = $self->get_pixbuf; + return ($pixbuf->get_width, $pixbuf->get_height); + } +} + + +our $hasGnome2VFS; +BEGIN { + eval 'use Gnome2::VFS'; + if ($@) + { + $hasGnome2VFS = 0; + } + else + { + $hasGnome2VFS = 1; + Gnome2::VFS->init(); + } +} + +{ + package GCImageButton; + + use Glib::Object::Subclass + Gtk2::Button:: + ; + + @GCImageButton::ISA = ('Gtk2::Button', 'GCGraphicComponent'); + + use File::Basename; + use Encode; + + sub animateImg + { + my ($self, $from, $to) = @_; + my $pixbuf1 = Gtk2::Gdk::Pixbuf->new_from_file($from); + $pixbuf1 = GCUtils::scaleMaxPixbuf($pixbuf1, $self->{img}->{width}, $self->{img}->{height}); + my $pixbuf2 = Gtk2::Gdk::Pixbuf->new_from_file($to); + $pixbuf2 = GCUtils::scaleMaxPixbuf($pixbuf2, $self->{img}->{width}, $self->{img}->{height}); + my $height = $pixbuf2->get_height; + my $width = $pixbuf2->get_width; + foreach my $i(0..20) + { + Glib::Timeout->add(30*$i, sub { + my $pixbufA = $pixbuf1->copy; + my $pixbufB = $pixbuf2->copy; + $pixbufA->composite($pixbufB, 0, 0, int($width - (($i/20)*$width)), $height, 0, 0, 1, 1, 'nearest', 255); + $self->{img}->set_from_pixbuf($pixbufB); + }); + } + } + + sub setImg + { + my ($self, $value) = @_; + $self->{img}->setValue($value, $self); + } + + sub placeImg + { + my ($self) = @_; + my ($picWidth, $picHeight) = $self->{img}->getSize; + my ($buttonWidth, $buttonHeight) = ($self->allocation->width, $self->allocation->height); + my $x = ($buttonWidth - $picWidth - $GCUtils::margin)/ 2; + my $y = ($buttonHeight -$picHeight - $GCUtils::margin)/ 2; + + # Don't allow negative positions, can happen when button has not been allocated a width/height yet + $x = 0 if ($x < 0); + $y = 0 if ($y < 0); + + $self->{layout}->move($self->{img}, $x, $y); + } + + sub changeState + { + my $self = shift; + if ($self->{trackState}) + { + if ($self->{flipped}) + { + $self->{linkedComponent}->setChanged; + } + else + { + $self->setChanged; + } + } + } + + sub clearImage + { + my $self = shift; + + $self->changeState; + $self->{mainParent}->checkPictureToBeRemoved($self->{imageFile}); + $self->setValueWithParent(''); + } + + sub changeImage + { + my ($self, $fileName) = @_; + return 0 if $self->{locked}; + if (!$fileName) + { + my $imageDialog = GCFileChooserDialog->new($self->{parent}->{lang}->{PanelImageTitle}, $self->{mainParent}, 'open'); + $imageDialog->setWithImagePreview(1); + + my $currentFile = $self->{img}->getValue; + if ($currentFile) + { + $imageDialog->set_filename($currentFile); + } + else + { + $imageDialog->set_filename($self->{previousDirectory}); + } + my $response = $imageDialog->run; + $fileName = $imageDialog->get_filename; + $imageDialog->destroy; + + $self->{parent}->showMe; + if ($response eq 'ok') + { + $self->{previousDirectory} = dirname($fileName); + $self->setChanged if $self->{trackState}; + } + else + { + return; + } + } + + my $ref = ($self->{flipped} ? $self->{backPic} : $self->{imageFile}); + if ($fileName ne $ref) + { + $self->{mainParent}->checkPictureToBeRemoved($ref); + $self->changeState; + } + my $image = $self->{mainParent}->transformPicturePath($fileName); + $self->setValueWithParent($image); + return; + } + + sub isEmpty + { + my $self = shift; + + return $self->getValue eq ''; + } + + sub setValue + { + my ($self, $value) = @_; + $self->setChanged if $self->{trackState}; + $self->setActualValue($value); + } + + sub setValueWithParent + { + my ($self, $value, $keepWatcher) = @_; + + $self->setActualValue($value, $keepWatcher, $self->{flipped}); + if ($self->{isCover} && !$self->{flipped}) + { + $self->{mainParent}->{items}->updateSelectedItemInfoFromPanel(0, [$self->{name}]); + $self->{hasChanged} = 0 if $self->{parent} eq $self->{mainParent}->{panel} && !$keepWatcher; + } + } + + sub setActualValue + { + my ($self, $value, $keepWatcher, $flipped) = @_; + Glib::Source->remove($self->{fileWatcher}) + if $self->{fileWatcher} && !$keepWatcher; + if ($flipped) + { + $self->{backPic} = $value; + } + else + { + $self->{imageFile} = $value; + } + $self->setImg($value); + } + + sub getValue + { + my $self = shift; + if ($self->{flipped}) + { + return $self->{imageFile}; + } + return $self->{img}->getValue; + } + + sub setLinkedActivated + { + my ($self, $value) = @_; + $self->flipImage(1) if $self->{flipped}; + $self->{flipActivated} = $value; + } + + sub flipImage + { + my ($self, $noButton) = @_; + my $newLabel; + if ($self->{flipped}) + { + $self->setImg($self->{imageFile}); + $self->{frontFlipImage}->show if !$noButton; + $self->{backFlipImage}->hide; + #$self->animateImg($self->{backPic}, $self->{imageFile}); + } + else + { + $self->setImg($self->{backPic}); + $self->{backFlipImage}->show if !$noButton; + $self->{frontFlipImage}->hide; + #$self->animateImg($self->{imageFile}, $self->{backPic}); + } + $self->{flipped} = !$self->{flipped}; + } + + sub setLinkedValue + { + my ($self, $linkedValue) = @_; + $self->setChanged if $self->{trackState}; + $self->{backPic} = $linkedValue; + $self->setImg($linkedValue) if $self->{flipped}; + } + + sub getLinkedValue + { + my ($self, $linkedValue) = @_; + return $self->{backPic}; + } + + sub setLinkedComponent + { + my ($self, $linked) = @_; + $self->{linkedComponent} = $linked; + + $self->{flipActivated} = 1; + $self->{frontFlipImage} = Gtk2::Image->new_from_file($ENV{GCS_SHARE_DIR}.'/overlays/flip.png'); + $self->{frontFlipImage}->set_no_show_all(1); + $self->{backFlipImage} = Gtk2::Image->new_from_file($ENV{GCS_SHARE_DIR}.'/overlays/flip2.png'); + $self->{backFlipImage}->set_no_show_all(1); + my $pixbuf = $self->{frontFlipImage}->get_pixbuf; + my ($picWidth, $picHeight) = ($pixbuf->get_width, $pixbuf->get_height); + + $self->{addedFlipButton} = 0; + $self->signal_connect('enter' => sub { + return if ! $self->{flipActivated}; + if (!$self->{addedFlipButton}) + { + $self->{flipX} = $self->{width} - $picWidth - $GCUtils::margin; + $self->{flipY} = $self->{height} - $picHeight - $GCUtils::margin; + $self->{layout}->put($self->{frontFlipImage}, + $self->{flipX}, + $self->{flipY}); + $self->{layout}->put($self->{backFlipImage}, + $self->{flipX}, + $self->{flipY}); + $self->{addedFlipButton} = 1; + } + if ($self->{flipped}) + { + $self->{backFlipImage}->show; + } + else + { + $self->{frontFlipImage}->show; + } + }); + $self->signal_connect('leave' => sub { + $self->{frontFlipImage}->hide; + $self->{backFlipImage}->hide; + }); + $self->signal_connect('button-release-event' => sub { + return 0 if ! $self->{flipActivated}; + my ($button, $event) = @_; + my ($x, $y) = $event->get_coords; + if (($x > $self->{flipX}) && ($y > $self->{flipY})) + { + $self->flipImage; + $self->set_sensitive(0); + $self->released; + $self->set_sensitive(1); + return 1; + } + else + { + return 0; + } + }); + + $self->signal_connect('key-press-event' => sub { + return 0 if ! $self->{flipActivated}; + my ($widget, $event) = @_; + my $key = Gtk2::Gdk->keyval_name($event->keyval); + + if (($key eq 'f') || ($key eq 'BackSpace')) + { + $self->flipImage; + return 1; + } + return 0; + }); + + $self->signal_connect('query_tooltip' => sub { + my ($window, $x, $y, $keyboard_mode, $tip) = @_; + return if $self->{settingTip}; + $self->{settingTip} = 1; + if ($self->{flipActivated} && ($x > $self->{flipX}) && ($y > $self->{flipY})) + { + $self->{tooltips}->set_tip($self, $self->{flipped} ? + $self->{parent}->{lang}->{ContextImgFront} : + $self->{parent}->{lang}->{ContextImgBack}); + } + else + { + $self->{tooltips}->set_tip($self, $self->{tip}); + } + $self->{settingTip} = 0; + return 0; + }); + + } + + sub clear + { + my $self = shift; + $self->setValue(''); + } + + sub new + { + my ($proto, $parent, $img, $isCover, $default) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + bless ($self, $class); + + $default = 'view' if !$default; + + $self->{layout} = new Gtk2::Fixed; + $self->{layout}->put($img, 0, 0); + $self->add($self->{layout}); + + $self->{img} = $img; + $self->{default} = $default; + #$self->set_size_request(130,170); + $self->{width} = -1; + $self->{height} = -1; + $self->{imageFile} = $img->getValue; + + # True if this is the cover used in image mode + $self->{isCover} = $isCover; + + $self->{parent} = $parent; + $self->getMainParent; + $self->{tooltips} = $self->{mainParent}->{tooltips}; + + $self->{tip} = ($default eq 'open') ? $parent->{lang}->{PanelImageTipOpen} : $parent->{lang}->{PanelImageTipView}; + $self->{tip} .= $parent->{lang}->{PanelImageTipMenu}; + $self->{tooltips}->set_tip($self, $self->{tip}); + + $self->signal_connect('button_press_event' => sub { + my ($widget, $event) = @_; + return 0 if $event->button ne 3; + $self->createContextMenu(); + $self->{imgContext}->popup(undef, undef, undef, undef, $event->button, $event->time); + return 0; + }); + + $self->signal_connect('clicked' => sub { + $self->changeImage if $self->{default} eq 'open'; + $self->showImage if $self->{default} eq 'view'; + return 1; + }); + + #Drag and drop a picture on a button + $self->drag_dest_set('all', ['copy','private','default','move','link','ask']); + my $target_list = Gtk2::TargetList->new(); + my $atom1 = Gtk2::Gdk::Atom->new('text/uri-list'); + my $atom2 = Gtk2::Gdk::Atom->new('text/plain'); + $target_list->add($atom1, 0, 0); + $target_list->add($atom2, 0, 0); + if ($^O =~ /win32/i) + { + my $atom2 = Gtk2::Gdk::Atom->new('DROPFILES_DND'); + $target_list->add($atom2, 0, 0); + } + $self->drag_dest_set_target_list($target_list); + $self->signal_connect(drag_data_received => sub { + my ($widget, $context, $widget_x, $widget_y, $data, $info,$time) = @_; + my @files = split /\n/, $data->data; + my $fileName = $files[0]; + if ($fileName =~ /^http/) + { + $fileName = $self->{mainParent}->downloadPicture($fileName); + } + else + { + $fileName = Glib::filename_from_uri $fileName; + $fileName = decode('utf8', $fileName); + $fileName =~ s|^file://?(.*)\W*$|$1|; + $fileName =~ s|^/*|| if ($^O =~ /win32/i); + $fileName =~ s/.$//ms; + $fileName =~ s/%20/ /g; + } + $self->changeImage($fileName); + }); + + $self->{previousDirectory} = ''; + $self->{flipped} = 0; + $self->{flipActivated} = 0; + + return $self; + } + + sub createContextMenu + { + my $self = shift; + + my $parent; + $parent = $self->{parent}; + + $self->{imgContext} = new Gtk2::Menu; + + if ($parent->{options}->tearoffMenus) + { + $self->{imgContext}->append(Gtk2::TearoffMenuItem->new()); + } + + $self->{itemOpen} = Gtk2::ImageMenuItem->new_with_mnemonic($parent->{lang}->{ContextChooseImage}); + my $itemOpenImage = Gtk2::Image->new_from_stock('gtk-open', 'menu'); + $self->{itemOpen}->set_image($itemOpenImage); + # This item will be deactivated if the component is locked + $self->{itemOpen}->set_sensitive(!$self->{locked}); + $self->{itemOpen}->signal_connect("activate" , sub { + $self->changeImage; + }); + $self->{imgContext}->append($self->{itemOpen}); + $self->{itemShow} = Gtk2::ImageMenuItem->new_from_stock('gtk-zoom-100',undef); + $self->{itemShow}->signal_connect("activate" , sub { + $self->showImage; + }); + # Disable for default image + $self->{itemShow}->set_sensitive(0) if $self->isDefaultImage(); + $self->{imgContext}->append($self->{itemShow}); + + if ($self->{linkedComponent}) + { + $self->{itemFlip} = Gtk2::MenuItem->new($self->{flipped} ? + $parent->{lang}->{ContextImgFront} : + $parent->{lang}->{ContextImgBack}); + $self->{itemFlip}->signal_connect("activate" , sub { + $self->flipImage; + }); + $self->{imgContext}->append($self->{itemFlip}); + } + + $self->{itemClear} = Gtk2::ImageMenuItem->new_from_stock('gtk-clear',undef); + # This item will be deactivated if the component is locked + $self->{itemClear}->set_sensitive(!$self->{locked}); + $self->{itemClear}->signal_connect("activate" , sub { + $self->clearImage; + }); + $self->{imgContext}->append($self->{itemClear}); + # Disable for default image + $self->{itemClear}->set_sensitive(0) if $self->isDefaultImage(); + $self->{imgContext}->show_all; + + my $itemOpenWith = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextOpenWith}); + $self->{menuOpenWith} = Gtk2::Menu->new; + + if ($hasGnome2VFS && ($parent->{options}->programs eq 'system' || $parent->{options}->imageEditor eq '')) + { + # Get applications for mime types corresponding with image + + # Get all editors/viewers for jpeg file format + my $mimeTest = Gnome2::VFS::Mime::Type->new ("image/jpeg"); + my @mimeList = $mimeTest->get_short_list_applications; + + # Add applications to open with list + foreach (@mimeList) + { + my $launchApp = $_; + my $item = Gtk2::MenuItem->new_with_mnemonic($launchApp->get_name); + $item->signal_connect ('activate' => sub { + $self->openWith($launchApp); + }); + $self->{menuOpenWith}->append($item); + } + + #Gnome2::VFS -> shutdown(); + } + elsif ($parent->{options}->programs eq 'system' || $parent->{options}->imageEditor eq '') + { + # Can't parse applications, so use system default app + my $item = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextImageEditor}); + + my $command; + $command = ($^O =~ /win32/i) ? '' + : ($^O =~ /macos/i) ? '/usr/bin/open' + : $ENV{GCS_SHARE_DIR}.'/helpers/xdg-open'; + + # Not sure if this is correct, haven't tested with Windows: + if ($^O =~ /win32/i) + { + $command = '"'.$command.'"' if $command; + } + + $item->signal_connect ('activate' => sub { + $self->openWithImageEditor($command); + }); + + $self->{menuOpenWith}->append($item); + } + else + { + # Use user defined app + my $item = Gtk2::MenuItem->new_with_mnemonic($parent->{lang}->{ContextImageEditor}); + $item->signal_connect ('activate' => sub { + $self->openWithImageEditor($parent->{options}->imageEditor); + }); + + $self->{menuOpenWith}->append($item); + } + + + $itemOpenWith->set_submenu($self->{menuOpenWith}); + + # Disable for default image + $itemOpenWith->set_sensitive(0) if $self->isDefaultImage(); + + $self->{imgContext}->append($itemOpenWith); + $self->{imgContext}->show_all; + + } + + sub activateStateTracking + { + my $self = shift; + $self->{trackState} = 1; + } + + sub lock + { + my ($self, $locked) = @_; + + $self->{locked} = $locked; + } + + sub showImage + { + my $self = shift; + $self->{mainParent}->launch($self->{img}->getValue, 'image'); + } + + sub isDefaultImage + { + my ($self) = @_; + + if ($self->{img}->getFile eq $self->{parent}->{defaultImage}) + { + return 1; + } + else + { + return 0; + } + } + + sub openWith + { + my ($self, $app) = @_; + my $cmd; + my $escFileName; + + # Ultra hacky workaround, because $app->{launch} segfaults. See http://bugzilla.gnome.org/show_bug.cgi?id=315049 + # Probably should change to gvfs when perl modules are available + + if ($app->{command} =~ m/(\w*)/) + { + $cmd = $1; + } + + $escFileName = $self->{img}->getFile; + $escFileName =~ s/\ /%20/g; + $self->editPicture("$cmd file://$escFileName"); + } + + sub openWithImageEditor + { + my ($self, $editor) = @_; + my $file = $self->{img}->getFile; + $file =~ s|/|\\|g if ($^O =~ /win32/i); + $self->editPicture("$editor \"$file\""); + } + + sub editPicture + { + my ($self, $commandLine) = @_; + my $file = $self->{img}->getFile; + + my $flipped = $self->{flipped}; + $self->{fileWatchDays} = -M $file; + $self->{fileWatcher} = Glib::Timeout->add(1000, sub { + my $currentDays = -M $file; + if ($currentDays < $self->{fileWatchDays}) + { + $self->changeState; + # We remove it from the pixbuf cache in items view. Useful + # for detailed list to be sure it will be re-loaded + delete $self->{mainParent}->{itemsView}->{cache}->{$file} + if $self->{mainParent}->{itemsView}->{cache}; + $self->setValueWithParent($self->{img}->getValue, 1, $flipped); + $self->{fileWatchDays} = $currentDays; + } + return 1; + }); + $self->{mainParent}->launch($commandLine, 'program', 1); + } + + sub setWidth + { + my ($self, $value) = @_; + $self->{width} = $value; + $self->set_size_request($value, $self->{height}); + $self->{img}->setWidth($value - $GCUtils::margin); + } + + sub setHeight + { + my ($self, $value) = @_; + $self->{height} = $value; + $self->set_size_request($self->{width}, $value); + $self->{img}->setHeight($value - $GCUtils::margin); + } + +} + +{ + package GCMenuList; + + use Glib::Object::Subclass + Gtk2::ComboBox:: + ; + + @GCMenuList::ISA = ('Gtk2::ComboBox', 'GCGraphicComponent'); + + our $separatorValue = 'GCSSeparator'; + + sub isEmpty + { + my $self = shift; + + return 1 if ! defined $self->get_active_iter; + return ($self->{listModel}->get($self->get_active_iter))[1] eq ''; + my $idx = $self->get_history; + $idx-- if $idx >= $self->{separatorPosition}; + $idx = 0 if $idx < 0; + return $self->{'values'}->[$idx]->{displayed} eq ''; + } + + sub valueToDisplayed + { + my ($self, $value) = @_; + + foreach (@{$self->{'values'}}) + { + return $_->{displayed} if $_->{value} eq $value + } + return ''; + } + + sub getValue + { + my ($self, $formatted) = @_; + my $iter = $self->get_active_iter; + my $value = ''; + $value = ($self->{listModel}->get($iter))[0] if $iter; + $value = $self->valueToDisplayed($value) if $formatted; + return $value; + } + + sub getDisplayedValue + { + my $self = shift; + my $iter = $self->get_active_iter; + return ($self->{listModel}->get($iter))[1] if $iter; + return ''; + } + + sub setValue + { + my ($self, $value) = @_; + + $value = 0 if !$value; + my $i = 0; + if ($value) + { + foreach (@{$self->{values}}) + { + last if $_->{value} eq $value; + $i++; + } + } + $i++ if $i >= $self->{separatorPosition}; + $i-- if ($self->{default} == -1) && ($i >= $self->{count}); + $self->set_active($i) + if ($i < scalar(@{$self->{values}})); + } + + sub clear + { + my $self = shift; + $self->set_active(0); + } + + sub lock + { + my ($self, $locked) = @_; + + $self->set_sensitive(!$locked); + } + + sub getValues + { + my $self = shift; + + my @values; + return $self->{values}; + } + + sub setValues + { + my ($self, $values, $separatorPosition, $preserveValue) = @_; + if ($self->{title}) + { + $separatorPosition = 1; + unshift @$values, {value => -1, displayed => $self->{title}}; + } + my $model = $self->{listModel}; + my $previous = $self->getValue if $preserveValue; + $self->{values} = $values; + $self->{separatorPosition} = $separatorPosition || 9999; + + $model->clear; + my $i = 0; + foreach (@$values) + { + if ($i == $self->{separatorPosition}) + { + $model->set($model->append, 0 => $GCMenuList::separatorValue, 1 => ''); + $i++; + } + $model->set($model->append, + 0 => $_->{value}, + 1 => $_->{displayed}); + $i++; + } + + $self->{count} = $i; + $self->setValue($previous) if $preserveValue; + $self->set_active(0) if !$preserveValue; + } + + sub setLastForDefault + { + my $self = shift; + $self->{default} = -1; + } + + sub setTitle + { + my ($self, $title) = @_; + $self->{title} = $title; + } + + sub new + { + my ($proto, $values, $separatorPosition) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + bless ($self, $class); + + $self->{listModel} = Gtk2::ListStore->new('Glib::String', 'Glib::String'); + $self->set_model($self->{listModel}); + my $renderer = Gtk2::CellRendererText->new; + $self->pack_start($renderer, 1); + $self->add_attribute($renderer, text => 1); + $self->set_row_separator_func(sub { + my ($model, $iter) = @_; + my @values = $model->get($iter, 0); + my $val = ''; + $val = $values[0] if defined $values[0]; + return $val eq $GCMenuList::separatorValue; + }); + + $self->setValues($values, $separatorPosition) if $values; + $self->{default} = 0; + $self->set_focus_on_click(1); + return $self; + } + + sub activateStateTracking + { + my $self = shift; + $self->signal_connect('changed' => sub { + $self->setChanged; + }); + } +} + +{ + package GCHeaderLabel; + + use Glib::Object::Subclass + Gtk2::Label:: + ; + + @GCHeaderLabel::ISA = ('Gtk2::Label', 'GCGraphicComponent'); + + sub new + { + my ($proto, $label) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + bless ($self, $class); + + $self->setText($label); + $self->set_alignment(0,1); + + return $self; + } + + sub setText + { + my ($self, $label) = @_; + $self->set_markup(''.$label.''); + } +} + +{ + package GCLabel; + + use Glib::Object::Subclass + Gtk2::Label:: + ; + + @GCLabel::ISA = ('Gtk2::Label', 'GCGraphicComponent'); + + sub new + { + my ($proto, $label, $disableMarkup) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + bless ($self, $class); + $self->set_markup($label) unless $disableMarkup; + $self->set_label($label) if $disableMarkup; + $self->set_alignment(0,0.5); + return $self; + } +} + +{ + package GCColorLabel; + + use Glib::Object::Subclass + Gtk2::EventBox:: + ; + + @GCColorLabel::ISA = ('Gtk2::EventBox', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); + + sub new + { + my ($proto, $color, $listType) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + + bless ($self, $class); + + $listType = 0 if !$listType; + $self->modify_bg('normal', $color); + $self->{label} = Gtk2::Label->new; + $self->{label}->show; + $self->{hboxFill} = new Gtk2::HBox(0,0); + $self->{hboxFill}->pack_start($self->{label},1,1,0); + $self->add($self->{hboxFill}); + $self->set_alignment(0,0.5); + $self->initHistory($listType); + + return $self; + } + + sub acceptMarkup + { + my $self = shift; + return 1; + } + + sub setMarkup + { + my ($self, $text) = @_; + + $self->{label}->set_markup($text); + } + + sub getValue + { + my $self = shift; + + (my $label = $self->{label}->get_label) =~ s/<.*?>(.*?)<\/.*?>/$1/g; + return $label; + } + + sub setBgColor + { + my ($self, $color) = @_; + $self->modify_bg('normal', $color); + } + + sub set_justify + { + my ($self, $value) = @_; + $self->{label}->set_justify($value); + $self->{label}->set_alignment(0.5,0) if $value eq 'center'; + $self->{label}->set_alignment(1,0) if $value eq 'right'; + } + + sub AUTOLOAD + { + my $self = shift; + my $name = our $AUTOLOAD; + return if $name =~ /::DESTROY$/; + $name =~ s/.*?::(.*)/$1/; + $self->{label}->$name(@_); + } +} + +{ + package GCColorLink; + + use Glib::Object::Subclass + Gtk2::EventBox:: + ; + + @GCColorLink::ISA = ('GCColorLabel'); + + sub new + { + my ($proto, $color, $opener) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($color); + bless ($self, $class); + $self->{opener} = $opener; + $self->signal_connect('button-release-event' => sub { + my $value = $self->getValue; + return if !$value; + $self->{opener}->launch($value, 'url'); + }); + $self->signal_connect('enter-notify-event' => sub { + $self->window->set_cursor(Gtk2::Gdk::Cursor->new('hand2')) + if $self->getValue; + }); + #$self->window->set_cursor(Gtk2::Gdk::Cursor->new('watch')); + return $self; + } +} + +{ + package GCColorLongLabel; + + use Glib::Object::Subclass + Gtk2::TextView:: + ; + + @GCColorLongLabel::ISA = ('Gtk2::TextView', 'GCGraphicComponent'); + + sub new + { + my ($proto, $color) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + bless ($self, $class); + + $self->set_editable(0); + $self->set_wrap_mode('word'); + $self->{background} = $color; + $self->modify_base('normal', $color); + $self->modify_bg('normal', $color); + $self->set_border_width($GCUtils::halfMargin); + + my $layout = $self->create_pango_layout('G'); + my (undef, $rect) = $layout->get_pixel_extents; + $self->{em} = 1.5 * $rect->{height}; + + + return $self; + } + + sub acceptMarkup + { + my $self = shift; + return 1; + } + + sub setMarkup + { + my ($self, $text) = @_; + + #$text =~ s/&/&/g; + if ($self->{resize}) + { + $self->resize; + } + else + { + my $buffer = $self->get_buffer; + $self->get_buffer->set_text(''); + $text =~ s|]*?)>([^<]*?)|$2|; + if ($self->{spanTag}) + { + $buffer->get('tag-table')->remove($self->{spanTag}); + } + $self->{spanTag} = $buffer->create_tag('span', $self->getTagFromSpan($1)); + $buffer->insert_with_tags_by_name ($buffer->get_start_iter, $text, 'span'); + } + } + + sub getValue + { + my $self = shift; + + (my $label = $self) =~ s/<.*?>(.*?)<\/.*?>/$1/g; + return $label; + } + +} + +{ + package GCColorTable; + + use Glib::Object::Subclass + Gtk2::Table:: + ; + + @GCColorTable::ISA = ('Gtk2::Table', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); + + sub new + { + my ($proto, $columns, $labels, $headerStyle, $contentStyle, $topHeader) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(1, $columns); + + bless ($self, $class); + + $self->set_col_spacings(3); + $self->set_row_spacings(3); + $self->{number} = $columns; + $self->{style} = $contentStyle; + $self->{firstRow} = 0; + if ($topHeader) + { + $self->{firstRow}++; + my $top = new GCColorLabel($headerStyle->{bgColor}); + $top->set_padding($GCUtils::halfMargin,$GCUtils::halfMargin); + $top->setMarkup('{style}.'>'.$topHeader.''); + $self->attach($top, 0, $columns, 0, 1, ['expand', 'fill'], 'fill', 0, 0); + } + my $i; + if ($columns > 1) + { + for $i(0..($columns - 1)) + { + my $header = new GCColorLabel($headerStyle->{bgColor}); + $header->set_padding($GCUtils::halfMargin,$GCUtils::halfMargin); + $header->setMarkup('{style}.'>'.$labels->[$i].''); + $self->attach($header, $i, $i + 1, $self->{firstRow}, $self->{firstRow} + 1, ['expand', 'fill'], 'fill', 0, 0); + } + $self->{firstRow}++; + } + $self->initHistory($columns); + return $self; + } + + sub setValue + { + my ($self, $value) = @_; + $self->{value} = $value; + + foreach (@{$self->{labels}}) + { + $self->remove($_); + $_->destroy; + } + + $self->{labels} = []; + my @lines; + if (ref($value) eq 'ARRAY') + { + @lines = @$value; + } + else + { + $value =~ s/^\s*//; + @lines = split m/,/, $value; + } + if ($#lines < 0) + { + $self->hide_all; + return; + } + my $i = $self->{firstRow}; + $self->resize($#lines + 1 + $self->{firstRow}, $self->{number}); + foreach (@lines) + { + my @cols; + if (ref($value) eq 'ARRAY') + { + @cols = @$_; + } + else + { + @cols = split m/;/, $_; + } + my $j = 0; + for my $col(@cols) + { + # TODO Optimize GCColorLongLabel. It offers a better display (no scrollbar) + # but it slows down the display. + my $label = new GCColorLabel($self->{style}->{bgColor}); + $label->set_padding($GCUtils::halfMargin,$GCUtils::halfMargin); + #my $label = new GCColorLongLabel($self->{style}->{bgColor}, '2em'); + $label->setMarkup('{style}->{style}.'>'.$self->cleanMarkup($col, 1).''); + $self->attach($label, $j, $j + 1, $i, $i + 1, ['expand', 'fill'], 'fill', 0, 0); + push @{$self->{labels}}, $label; + $j++; + } + $i++; + } + $self->show_all; + } + + sub getValue + { + my $self = shift; + return $self->{value}; + } + + sub setBgColor + { + my ($self, $color) = @_; + return; + } + + sub set_justify + { + my ($self, $value) = @_; + return; + } + +} + +{ + package GCColorText; + + use Glib::Object::Subclass + Gtk2::ScrolledWindow:: + ; + + @GCColorText::ISA = ('Gtk2::ScrolledWindow', 'GCGraphicComponent', 'GCPseudoHistoryComponent'); + + sub new + { + my ($proto, $color, $height, $listType) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new; + bless ($self, $class); + + $listType = 0 if !$listType; + $self->{text} = new Gtk2::TextView; + $self->{text}->set_editable(0); + $self->{text}->set_wrap_mode('word'); + $self->{background} = $color; + $self->{text}->modify_base('normal', $color); + $self->{text}->modify_bg('normal', $color); + $self->{text}->set_border_width($GCUtils::halfMargin); + $self->set_border_width(0); + $self->set_shadow_type('none'); + $self->set_policy('automatic', 'automatic'); + $self->add($self->{text}); + $self->initHistory($listType); + + my $layout = $self->create_pango_layout('G'); + my (undef, $rect) = $layout->get_pixel_extents; + $self->{em} = 1.5 * $rect->{height}; + + $self->setHeight($height) if $height; + + return $self; + } + + sub acceptMarkup + { + my $self = shift; + return 1; + } + + sub setMarkup + { + my ($self, $text) = @_; + + if ($self->{resize}) + { + $self->resize; + } + else + { + my $buffer = $self->{text}->get_buffer; + $self->{text}->get_buffer->set_text(''); + $text =~ s|]*?)>([^<]*?)|$2|; + if ($self->{spanTag}) + { + $buffer->get('tag-table')->remove($self->{spanTag}); + } + $self->{spanTag} = $buffer->create_tag('span', $self->getTagFromSpan($1)); + $buffer->insert_with_tags_by_name ($buffer->get_start_iter, $text, 'span'); + } + } + + sub getValue + { + my $self = shift; + + (my $label = $self->{text}) =~ s/<.*?>(.*?)<\/.*?>/$1/g; + return $label; + } + + sub setHeight + { + my ($self, $height) = @_; + $height =~ s/^([0-9]+)em$/$1*$self->{em}/e; + $self->set_size_request(-1, $height); + } + + sub AUTOLOAD + { + my $self = shift; + my $name = our $AUTOLOAD; + return if $name =~ /::DESTROY$/; + $name =~ s/.*?::(.*)/$1/; + $self->{text}->$name(@_); + } +} + +{ + package GCColorExpander; + + use Glib::Object::Subclass + Gtk2::Expander:: + ; + + @GCColorExpander::ISA = ('GCExpander'); + + sub new + { + my ($proto, $label, $bgColor, $fgStyle) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($label); + + bless ($self, $class); + $self->{label} = new GCColorLabel($bgColor); + $self->set_label_widget($self->{label}); + $self->{fgStyle} = $fgStyle; + + return $self; + } + + sub setValue + { + my ($self, $label, $description) = @_; + + $label = '{fgStyle}.">$label"; + + $self->{label}->set_markup($label); + if ($description) + { + $self->{description}->set_label($description); + $self->{description}->show; + } + else + { + $self->{description}->set_label(''); + $self->{description}->hide; + } + + + } +} + +{ + package GCDialogHeader; + + use Glib::Object::Subclass + Gtk2::HBox:: + ; + + @GCDialogHeader::ISA = ('Gtk2::HBox', 'GCGraphicComponent'); + + sub new + { + my ($proto, $text, $imageStock, $logosDir) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + + bless ($self, $class); + + $self->{label} = new Gtk2::Label; + $self->{label}->set_markup("$text"); + $self->{label}->set_alignment(0,0.5); + + if (-f $logosDir.$imageStock.'.png') + { + $self->{image} = Gtk2::Image->new_from_file($logosDir.$imageStock.'.png'); + $self->pack_start($self->{image},0,1,5); + } + + $self->pack_start($self->{label},0,1,5); + + return $self; + } +} + +{ + package GCImageBox; + + use Glib::Object::Subclass + Gtk2::VBox:: + ; + + @GCImageBox::ISA = ('Gtk2::VBox', 'GCGraphicComponent'); + + sub new_from_file + { + my ($proto, $imageFile, $label) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + + my $image = Gtk2::Image->new_from_file($imageFile); + $self->init($image, $label); + + return $self; + } + sub new_from_stock + { + my ($proto, $stockId, $label) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + + my $image = Gtk2::Image->new_from_stock($stockId, 'large-toolbar'); + $self->init($image, $label); + + return $self; + } + + sub init + { + my ($self, $image, $label) = @_; + + $self->{label} = new Gtk2::Label($label); + + $self->pack_start($image, 0, 0, 0); + $self->pack_start($self->{label}, 0, 0, 0); + + $self->show_all; + } +} + +{ + package GCGroup; + + use Glib::Object::Subclass + Gtk2::Frame:: + ; + + @GCGroup::ISA = ('Gtk2::Frame', 'GCGraphicComponent'); + + sub new + { + my ($proto, $title) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + + bless ($self, $class); + + $self->set_shadow_type('none'); + $self->set_border_width($GCUtils::margin); + $self->{label} = new Gtk2::Label; + $self->{label}->set_padding(0,0); + #$label->set_border_width(0); + $self->setLabel($title); + $self->set_label_widget($self->{label}); + $self->set_label_align(0,0); + + $self->{marginBox} = new Gtk2::HBox(0,0); + $self->{marginBox}->set_border_width($GCUtils::halfMargin); + $self->add($self->{marginBox}); + + return $self; + } + + sub setLabel + { + my ($self, $label) = @_; + + $self->{label}->set_markup(''.$label.''); + } + + sub addWidget + { + my ($self, $widget, $margin) = @_; + $margin = $GCUtils::halfMargin if !$margin; + $self->{marginBox}->pack_start($widget, 1, 1, $margin); + } + + sub setPadding + { + my ($self, $value) = @_; + + $self->{marginBox}->set_border_width($value); + } +} + +{ + package GCExpander; + + use Glib::Object::Subclass + Gtk2::Expander:: + ; + + @GCExpander::ISA = ('Gtk2::Expander', 'GCGraphicComponent'); + + sub new + { + my ($proto, $label, $bold) = @_; + my $class = ref($proto) || $proto; + + my $self = $class->SUPER::new($label); + + bless ($self, $class); + $self->{hbox} = new Gtk2::HBox(0,0); + $self->{label} = new Gtk2::Label($label); + $self->{label}->set_alignment(0,0.5); + $self->{label}->set_markup("$label") + if $bold; + $self->{description} = new Gtk2::Label; + $self->{description}->set_alignment(0,0); + eval {$self->{description}->set_line_wrap_mode('word');}; + $self->{hbox}->pack_start($self->{label}, 0, 0, 0); + $self->{hbox}->pack_start($self->{description}, 1, 1, 0); + $self->set_label_widget($self->{hbox}); + $self->{signalHandler} = undef; + return $self; + } + + sub setMode + { + my ($self, $mode) = @_; + if ($mode eq 'asis') + { + $self->{description}->set_ellipsize('none'); + $self->{description}->set_line_wrap(0); + if ($self->{signalHandler}) + { + $self->signal_handler_disconnect($self->{signalHandler}); + $self->{signalHandler} = undef; + $self->{description}->set_size_request(-1, -1); + } + } + else + { + $self->{signalHandler} = $self->signal_connect('size-allocate' => sub { + my $width = $self->allocation->width + - $self->{label}->allocation->width + - ( 4 * $GCUtils::margin); + $self->{description}->set_size_request(($width >= -1) ? $width : -1 , -1); + return 0; + }); + if ($mode eq 'wrap') + { + $self->{description}->set_ellipsize('none'); + $self->{description}->set_line_wrap(1); + } + else + { + $self->{description}->set_ellipsize('end'); + $self->{description}->set_line_wrap(0); + } + } + } + + sub setValue + { + my ($self, $label, $description) = @_; + + $self->{label}->set_label($label); + if ($description) + { + $self->{description}->set_label($description); + $self->{description}->show; + } + else + { + $self->{description}->set_label(''); + $self->{description}->hide; + } + } +} + +{ + package GCFieldSelector; + + use Glib::Object::Subclass + Gtk2::ComboBox:: + ; + + @GCFieldSelector::ISA = ('GCMenuList'); + our $anyFieldValue = 'GCAnyField'; + + sub valueToDisplayed + { + my ($self, $value) = @_; + return ''; + } + + sub setValue + { + my ($self, $value) = @_; + + $self->{listModel}->foreach(sub { + my ($model, $path, $iter) = @_; + if ($model->get_value($iter, 0) eq $value) + { + $self->set_active_iter($iter); + return 1; + } + return 0; + }); + } + + sub getValues + { + my $self = shift; + return []; + } + + sub setValues + { + my ($self, $values, $separatorPosition, $preserveValue) = @_; + return; + } + + sub new + { + my ($proto, $withImages, $model, $advancedFilter, $withAnyField, $withoutEmpty, $uniqueType) = @_; + my $class = ref($proto) || $proto; + my $self = Gtk2::ComboBox->new; + bless($self, $class); + $self->{withImages} = $withImages; + $self->{advancedFilter} = $advancedFilter; + $self->{withAnyField} = $withAnyField; + $self->{withoutEmpty} = $withoutEmpty; + $self->{uniqueType} = $uniqueType; + + $self->{listModel} = Gtk2::TreeStore->new('Glib::String', 'Glib::String'); + $self->set_model($self->{listModel}); + my $renderer = Gtk2::CellRendererText->new; + $self->pack_start($renderer, 1); + $self->add_attribute($renderer, text => 1); + + $self->set_cell_data_func($renderer, sub { + my ($layout, $cell, $model, $iter) = @_; + my $sensitive = !$model->iter_has_child($iter); + $cell->set('sensitive', $sensitive); + }); + + $self->{default} = 0; + $self->set_focus_on_click(1); + + $self->setModel($model) if $model; + + return $self; + } + + sub setModel + { + my ($self, $model) = @_; + + $self->{listModel}->clear; + my $field; + my @fieldsInfo = @{$model->getDisplayedInfo}; + + if (! $self->{withoutEmpty}) + { + $self->{listModel}->set($self->{listModel}->append(undef), + 0 => '', + 1 => ''); + } + if ($self->{withAnyField}) + { + $self->{listModel}->set($self->{listModel}->append(undef), + 0 => $anyFieldValue, + 1 => $model->getDisplayedText('AdvancedSearchAnyField')); + } + + foreach my $group(@fieldsInfo) + { + my @fields; + foreach $field (@{$group->{items}}) + { + my $id = $field->{id}; + next if ($model->{fieldsInfo}->{$id}->{type} eq 'image') && (!$self->{withImages}); + next if ($self->{advancedFilter} + && ( + ($id eq $model->{commonFields}->{id}) + || ($id eq $model->{commonFields}->{title}) + || ($id eq $model->{commonFields}->{url}) + ) + ); + next if ! $field->{label}; + next if $self->{uniqueType} && ($self->{uniqueType} ne $model->{fieldsInfo}->{$id}->{type}); + push @fields, $field; + } + if (scalar @fields) + { + my $groupIter = $self->{listModel}->append(undef); + $self->{listModel}->set($groupIter, + 0 => undef, + 1 => $group->{title}); + foreach $field(sort {$a->{label} cmp $b->{label}} @fields) + { + my $fieldIter = $self->{listModel}->append($groupIter); + $self->{listModel}->set($fieldIter, + 0 => $field->{id}, + 1 => $field->{label}); + } + } + } + $self->{model} = $model; + } + sub activateStateTracking + { + my $self = shift; + } + + # Create a widget suitable to enter a value according to current field type + # It will destroy the current widget if it exists + sub createEntryWidget + { + # $parent is the class that contains needed information + # $comparison is the kind of comparison. Mainly useful if it is 'range' to create 2 fields + # $currentWidget is the one we are replacing + my ($self, $parent, $comparison, $currentWidget) = @_; + my $value; + if ($currentWidget) + { + $value = $currentWidget->getValue; + $currentWidget->destroy; + } + my $widget; + my $field = $self->getValue; + my $info = $self->{model}->{fieldsInfo}->{$field}; + + # These ones are required for createWidget + $self->{lang} = $parent->{lang}; + $self->{options} = $parent->{options}; + $self->{window} = $parent; + + ($widget, undef) = GCBaseWidgets::createWidget($self, $info, $comparison); + + if ($info->{type} eq 'history text') + { + $widget->setValues($parent->{parent}->{panel}->getValues($field)); + } + elsif ($info->{type} eq 'single list') + { + $widget->setValues($parent->{parent}->{panel}->getValues($field)->[0]); + } + $widget->setValue($value); + + return $widget; + } +} + +{ + package GCComparisonSelector; + + use Glib::Object::Subclass + Gtk2::ComboBox:: + ; + + @GCComparisonSelector::ISA = ('GCMenuList'); + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + bless($self, $class); + $self->setValues([ + {value => 'contain', displayed => $parent->{lang}->{ModelFilterContain}}, + {value => 'notcontain', displayed => $parent->{lang}->{ModelFilterDoesNotContain}}, + {value => 'regexp', displayed => $parent->{lang}->{ModelFilterRegexp}}, + {value => 'range', displayed => $parent->{lang}->{ModelFilterRange}}, + {value => 'eq', displayed => '='}, + {value => 'lt', displayed => '<'}, + {value => 'le', displayed => '≤'}, + {value => 'gt', displayed => '>'}, + {value => 'ge', displayed => '≥'}, + ]); + return $self; + } +} + +1; diff --git a/lib/gcstar/GCGraphicComponents/GCDoubleLists.pm b/lib/gcstar/GCGraphicComponents/GCDoubleLists.pm new file mode 100644 index 0000000..c59cae1 --- /dev/null +++ b/lib/gcstar/GCGraphicComponents/GCDoubleLists.pm @@ -0,0 +1,564 @@ +package GCDoubleLists; + +################################################### +# +# Copyright 2005-2011 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### +use utf8; +use Gtk2; + +use GCUtils; + +{ + #Class that is used to let user select + #item from a list and order them. + package GCDoubleListWidget; + + use base 'Gtk2::HBox'; + + sub init + { + my $self = shift; + $self->setListData($self->{dataHandler}->getData) if !$self->{initialized}; + $self->{initialized} = 1; + } + + sub compareItems + { + my ($self, $item1, $item2) = @_; + if ($self->{withPixbuf} && (ref $item1 eq 'ARRAY')) + { + return $item1->[1] cmp $item2->[1]; + } + else + { + return $item1 cmp $item2; + } + } + + sub moveFromTo + { + my ($self, $from, $to) = @_; + my $fromId = ($self->{$from}->get_selected_indices)[0]; + my $fromItem = $self->{$from.'Array'}->[$fromId]; + my $fromString; + if ($self->{withPixbuf}) + { + $fromString = $fromItem->[1]; + } + else + { + $fromString = $fromItem; + } + return if !$fromString; + my $toId = ($self->{$to}->get_selected_indices)[0]; + my $toTotal = scalar @{$self->{$to.'Array'}}; + $toId = $toTotal if $toId eq ''; + $toId++ if $toId < $toTotal; + $toId = 0 if ($toId < 0); + + if (($to eq 'unused') || (!$self->{permanent}->{$fromString})) + { + splice(@{$self->{$from}->{data}}, $fromId, 1); + splice(@{$self->{$from.'Array'}}, $fromId, 1); + } + if (($to eq 'used') || (!$self->{permanent}->{$fromString})) + { + splice(@{$self->{$to}->{data}}, $toId, 0, $fromItem); + splice(@{$self->{$to.'Array'}}, $toId, 0, $fromItem); + } + + if ($to eq 'unused') + { + my @tmpSortedArray = sort + {$self->compareItems($a, $b)} + @{$self->{unusedArray}}; + $self->{unusedArray} = \@tmpSortedArray; + @{$self->{unused}->{data}} = (); + my $i = 0; + $toId = 0; + foreach (@tmpSortedArray) + { + $toId = $i if $_ eq $fromString; + my @item = ($self->{withPixbuf} ? $_ : [$_]); + push @{$self->{unused}->{data}}, @item; + $i++; + } + } + $self->{$to}->select($toId); + $self->{$from}->select($fromId); + $self->{$from}->grab_focus; + } + + sub moveDownUp + { + my ($self, $dir) = @_; + my $currentId = ($self->{used}->get_selected_indices)[0]; + my $newId = $currentId + $dir; + return if ($newId < 0) || ($newId >= scalar @{$self->{usedArray}}); + ($self->{usedArray}->[$currentId], $self->{usedArray}->[$newId]) + = ($self->{usedArray}->[$newId], $self->{usedArray}->[$currentId]); + @{$self->{used}->{data}} = (); + foreach (@{$self->{usedArray}}) + { + if ($self->{withPixbuf}) + { + push @{$self->{used}->{data}}, $_; + } + else + { + push @{$self->{used}->{data}}, [$_]; + } + } + $self->{used}->select($newId); + } + + sub setListData + { + my ($self, $new) = @_; + my $initial = $self->{dataHandler}->getInitData; + $self->{initialized} = 1; + my %tmpMap; + if ($self->{withPixbuf}) + { + $tmpMap{$_->[1]} = $_ foreach (@$initial); + } + else + { + $tmpMap{$_} = 1 foreach (@$initial); + } + $self->{usedArray} = $new; + my $label; + foreach (@$new) + { + my $label = ($self->{withPixbuf} ? $_->[1] : $_); + delete $tmpMap{$label} if !$self->{permanent}->{$label}; + } + my @tmpArray = sort {$self->compareItems($a, $b)} keys %tmpMap; + if ($self->{withPixbuf}) + { + my @unusedArray = map {$tmpMap{$_}} @tmpArray; + $self->{unusedArray} = \@unusedArray; + } + else + { + $self->{unusedArray} = \@tmpArray; + } + @{$self->{unused}->{data}} = (); + + push @{$self->{unused}->{data}}, $_ foreach (@{$self->{unusedArray}}); + @{$self->{used}->{data}} = (); + push @{$self->{used}->{data}}, $_ foreach (@{$self->{usedArray}}); + } + + sub setListFromIds + { + my ($self, $new) = @_; + my $count = scalar(@$new) - 1; + for my $i (0..$count) + { + $new->[$i] = $self->{fieldIdToName}->{$new->[$i]}; + } + $self->setListData($new); + } + + sub clearList + { + my $self = shift; + + $self->setListData(()); + } + sub fillList + { + my $self = shift; + my @array = grep !$self->{permanent}->{$_}, + sort {$self->compareItems($a, $b)} @{$self->{dataHandler}->getInitData}; + $self->setListData(\@array); + } + + sub addToPermanent + { + my ($self, $id) = @_; + $self->{permanent}->{$id} = 1; + } + + sub removeFromPermanent + { + my ($self, $id) = @_; + delete $self->{permanent}->{$id}; + } + + sub getUsedItems + { + my $self = shift; + return $self->{usedArray}; + } + + sub addBottomButtons + { + my ($self, $unusedButton, $usedButton) = @_; + $self->{vboxUnused}->pack_start($unusedButton, 0, 0, 0); + $self->{vboxUsed}->pack_start($usedButton, 0, 0, 0); + + } + + sub addRightButtons + { + my ($self, $button1, $button2) = @_; + $self->{vboxRight}->pack_start($button1, 0, 0, $GCUtils::halfMargin); + $self->{vboxRight}->pack_start($button2, 0, 0, $GCUtils::halfMargin); + + } + + sub setDataHandler + { + my ($self, $dataHandler) = @_; + $self->{dataHandler} = $dataHandler; + } + + sub new + { + my ($proto, $withPixbuf, $unusedLabel, $usedLabel) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(0,0); + + bless ($self, $class); + + $self->{initialized} = 0; + $self->{withPixbuf} = $withPixbuf; + $self->{unusedLabel} = $unusedLabel; + $self->{usedLabel} = $usedLabel; + + if ($withPixbuf) + { + $self->{unused} = new Gtk2::SimpleList( + '' => 'pixbuf', + $self->{unusedLabel} => 'text' + ); + $self->{used} = new Gtk2::SimpleList( + '' => 'pixbuf', + $self->{usedLabel} => 'text' + ); + } + else + { + $self->{unused} = new Gtk2::SimpleList( + $self->{unusedLabel} => "text" + ); + $self->{used} = new Gtk2::SimpleList( + $self->{usedLabel} => "text" + ); + } + $self->{scrollPanelUnused} = new Gtk2::ScrolledWindow; + $self->{scrollPanelUnused}->set_policy ('never', 'automatic'); + $self->{scrollPanelUnused}->set_shadow_type('etched-in'); + $self->{scrollPanelUnused}->add($self->{unused}); + $self->{vboxUnused} = new Gtk2::VBox(0,0); + $self->{vboxUnused}->pack_start($self->{scrollPanelUnused}, 1, 1, 0); + + my $vboxChange = new Gtk2::VBox(1,1); + my $tmpVbox = new Gtk2::VBox(0,0); + my $toRight = new Gtk2::Button('->'); + $toRight->remove($toRight->child); + $toRight->add(Gtk2::Image->new_from_stock('gtk-go-forward', 'button')); + $toRight->signal_connect('clicked' => sub { + $self->moveFromTo('unused', 'used'); + }); + my $toLeft = new Gtk2::Button('<-'); + $toLeft->remove($toLeft->child); + $toLeft->add(Gtk2::Image->new_from_stock('gtk-go-back', 'button')); + $toLeft->signal_connect('clicked' => sub { + $self->moveFromTo('used', 'unused'); + }); + $tmpVbox->pack_start($toRight,0,0,$GCUtils::margin); + $tmpVbox->pack_start($toLeft,0,0,$GCUtils::margin); + $vboxChange->pack_start($tmpVbox,1,0,0); + + $self->{scrollPanelUsed} = new Gtk2::ScrolledWindow; + $self->{scrollPanelUsed}->set_policy ('never', 'automatic'); + $self->{scrollPanelUsed}->set_shadow_type('etched-in'); + $self->{scrollPanelUsed}->add($self->{used}); + $self->{vboxUsed} = new Gtk2::VBox(0,0); + $self->{vboxUsed}->pack_start($self->{scrollPanelUsed}, 1, 1, 0); + + $self->{unused}->signal_connect ('row-activated' => sub { + $self->moveFromTo('unused', 'used'); + }); + $self->{used}->signal_connect ('row-activated' => sub { + $self->moveFromTo('used', 'unused'); + }); + + $self->{vboxRight} = new Gtk2::VBox(0,0); + my $toUp = new Gtk2::Button('^'); + $toUp->remove($toUp->child); + $toUp->add(Gtk2::Image->new_from_stock('gtk-go-up', 'button')); + $toUp->signal_connect('clicked' => sub { + $self->moveDownUp(-1); + }); + my $toDown = new Gtk2::Button('_'); + $toDown->remove($toDown->child); + $toDown->add(Gtk2::Image->new_from_stock('gtk-go-down', 'button')); + $toDown->signal_connect('clicked' => sub { + $self->moveDownUp(1); + }); + $self->{vboxRight}->pack_start($toUp, 0, 0, $GCUtils::margin); + $self->{vboxRight}->pack_start($toDown, 0, 0, $GCUtils::margin); + + $self->pack_start(new Gtk2::HBox,0,0,$GCUtils::margin); + $self->pack_start($self->{vboxUnused},1,1,$GCUtils::halfMargin); + $self->pack_start($vboxChange,0,0,$GCUtils::halfMargin); + $self->pack_start($self->{vboxUsed},1,1,$GCUtils::halfMargin); + $self->pack_start($self->{vboxRight},0,0,$GCUtils::halfMargin); + $self->pack_start(new Gtk2::HBox,0,0,$GCUtils::quarterMargin); + + $self->{scrollPanelUnused}->set_size_request(150,-1); + $self->{scrollPanelUsed}->set_size_request(150,-1); + + return $self; + } +} + +{ + package GCFieldsSelectionWidget; + + use base 'GCDoubleListWidget'; + + sub getInitData + { + my $self = shift; + my @array; + @array = keys %{$self->{fieldNameToId}}; + return \@array; + } + + sub getData + { + my $self = shift; + + my @array; + foreach (@{$self->{selectedFields}}) + { + push @array, $self->{fieldIdToName}->{$_}; + } + + return \@array; + } + + sub saveList + { + my ($self, $list) = @_; + + my @array; + foreach (@{$list}) + { + push @array, $self->{fieldNameToId}->{$_}; + } + $self->{selectedFields} = \@array; + } + + sub getSelectedIds + { + my $self = shift; + $self->saveList($self->getUsedItems); + return $self->{selectedFields}; + } + + sub loadFromFile + { + my $self = shift; + my $fileDialog = new GCFileChooserDialog($self->{parent}->{lang}->{FieldsListOpen}, $self, 'open', 1); + $fileDialog->set_filename($self->{filename}); + my $response = $fileDialog->run; + if ($response eq 'ok') + { + $self->{filename} = $fileDialog->get_filename; + open FILE, '<'.$self->{filename}; + my $model = ; + chop $model; + if ($model eq $self->{model}->getName) + { + $self->clearList; + my @data; + while () + { + chop; + push @data, $self->{fieldIdToName}->{$_}; + } + $self->setListData(\@data); + } + else + { + my $dialog = Gtk2::MessageDialog->new($self, + [qw/modal destroy-with-parent/], + 'error', + 'ok', + $self->{parent}->{lang}->{FieldsListError}); + $dialog->set_position('center-on-parent'); + $dialog->run(); + $dialog->destroy ; + } + close FILE; + } + $fileDialog->destroy; + } + + sub saveToFile + { + my $self = shift; + my $fileDialog = new GCFileChooserDialog($self->{parent}->{lang}->{FieldsListSave}, $self, 'save', 1); + $fileDialog->set_filename($self->{filename}); + my $response = $fileDialog->run; + if ($response eq 'ok') + { + $self->{filename} = $fileDialog->get_filename; + open FILE, '>'.$self->{filename}; + print FILE $self->{model}->getName, "\n" if $self->{model}; + foreach (@{$self->{usedArray}}) + { + print FILE $self->{fieldNameToId}->{$_}, "\n"; + } + close FILE; + } + $fileDialog->destroy; + } + + sub compareItems + { + my ($self, $item1, $item2) = @_; + use locale; + my @values1 = split $self->{separator}, $item1; + my @values2 = split $self->{separator}, $item2; + if ($values1[0] eq $values2[0]) + { + return $values1[1] cmp $values2[1]; + } + else + { + return $self->{groupsOrder}->{$values1[0]} <=> $self->{groupsOrder}->{$values2[0]}; + } + } + + sub addIgnoreField + { + my ($self, $ignoreField) = @_; + $self->{ignoreString} = $self->{parent}->{lang}->{FieldsListIgnore}; + $self->{fieldNameToId}->{$self->{ignoreString}} = $ignoreField; + $self->{fieldIdToName}->{$ignoreField} = $self->{ignoreString}; + $self->addToPermanent($self->{ignoreString}); + } + + sub removeIgnoreField + { + my ($self) = @_; + $self->removeFromPermanent($self->{ignoreString}); + } + + sub new + { + my ($proto, $parent, $preList, $isIdList, $ignoreField) = @_; + + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new( + 0, + $parent->{lang}->{ImportExportFieldsUnused}, + $parent->{lang}->{ImportExportFieldsUsed} + ); + bless $self, $class; + + $self->{parent} = $parent; + + $self->{ignoreField} = $ignoreField; + $self->{lang} = $parent->{lang}; + $self->{tooltips} = Gtk2::Tooltips->new(); + + $self->setDataHandler($self); + my $fillButton = new Gtk2::Button($parent->{lang}->{ImportExportFieldsFill}); + $fillButton->set_border_width($GCUtils::margin); + $fillButton->signal_connect('clicked' => sub { + $self->fillList; + }); + my $clearButton = new Gtk2::Button($parent->{lang}->{ImportExportFieldsClear}); + $clearButton->set_border_width($GCUtils::margin); + $clearButton->signal_connect('clicked' => sub { + $self->clearList; + }); + + $self->addBottomButtons($fillButton, $clearButton); + + my $loadButton = new Gtk2::Button('open'); + $self->{tooltips}->set_tip($loadButton, + $parent->{lang}->{FieldsListOpen}); + $loadButton->remove($loadButton->child); + $loadButton->add(Gtk2::Image->new_from_stock('gtk-open', 'button')); + $loadButton->signal_connect('clicked' => sub { + $self->loadFromFile; + }); + my $saveButton = new Gtk2::Button('save'); + $self->{tooltips}->set_tip($saveButton, + $parent->{lang}->{FieldsListSave}); + $saveButton->remove($saveButton->child); + $saveButton->add(Gtk2::Image->new_from_stock('gtk-save', 'button')); + $saveButton->signal_connect('clicked' => sub { + $self->saveToFile; + }); + + $self->addRightButtons($loadButton, $saveButton); + + $self->{fieldNameToId} = {}; + $self->{groupsOrder} = {}; + + my $model = $self->{parent}->{model}; + if ($model) + { + my $groups = $model->getGroups; + $self->{separator} = $model->getDisplayedText('Separator'); + while (my ($key, $value) = each %{$model->{fieldsInfo}}) + { + next if !$value->{displayed}; + my $displayed = $groups->{$value->{group}}->{displayed} + . $self->{separator} + . $value->{displayed}; + $self->{fieldNameToId}->{$displayed} = $key; + $self->{fieldIdToName}->{$key} = $displayed; + } + my $order = 0; + foreach (@{$model->{groups}}) + { + $self->{groupsOrder}->{$groups->{$_->{id}}->{displayed}} = $order++; + } + $self->{model} = $model; + } + + if ($preList) + { + $self->setListData($preList) if !$isIdList; + $self->setListFromIds($preList) if $isIdList; + } + else + { + $self->fillList + } + $self->saveList(\@{$self->{usedArray}}); + + return $self; + } +} + +1; diff --git a/lib/gcstar/GCImport.pm b/lib/gcstar/GCImport.pm new file mode 100644 index 0000000..2ff40d3 --- /dev/null +++ b/lib/gcstar/GCImport.pm @@ -0,0 +1,139 @@ +package GCImport; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use File::Basename; +use GCUtils 'glob'; + +use base 'Exporter'; +our @EXPORT = qw(@importersArray); + +our @importersArray; + +sub loadImporters +{ + foreach (glob $ENV{GCS_LIB_DIR}.'/GCImport/*.pm') + { + my $import = basename($_, '.pm')."\n"; + next if $import =~ /GCImportBase/; + eval "use GCImport::$import"; + (my $importer = $import) =~ s/^GCImport/GCImporter/; + my $obj; + eval "\$obj = new GCImport::$importer"; + die "Fatal error with importer $import\n$@" if $@; + push @importersArray, $obj if ! $obj->{errors}; + } +} + +use Gtk2; +use GCExportImport; + + +{ + package GCImportDialog; + use Glib::Object::Subclass + Gtk2::Dialog:: + ; + + @GCImportDialog::ISA = ('GCExportImportDialog'); + + sub addOptions + { + my ($self, $options) = @_; + $options->{newList} = ($self->{newList}->get_active) ? 1 : 0; + $options->{parent} = $self->{parent}; + } + + sub setModule + { + my ($self, $module) = @_; + + $self->SUPER::setModule($module); + $self->{currentList}->set_sensitive(scalar @{$module->getModels} == 0); + $self->{newList}->set_active(1); + if ($self->{parent}->{model}) + { + foreach (@{$module->getModels}) + { + if ($self->{parent}->{model}->getName eq $_) + { + $self->{currentList}->set_sensitive(1); + last; + } + } + } + if ($self->{fieldsDialog}) + { + if ($module->wantsIgnoreField) + { + $self->{fieldsDialog}->addIgnoreField($self->{parent}->{ignoreString}); + } + else + { + $self->{fieldsDialog}->removeIgnoreField; + } + } + } + + sub setModel + { + my $self = shift; + $self->{fieldsDialog} = new GCFieldsSelectionDialog($self, + $self->{parent}->{lang}->{ImportFieldsTitle}); + } + + sub new + { + my ($proto, $parent) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, $parent->{lang}->{ImportListTitle}, 'import'); + bless ($self, $class); + + my $vboxInsert = new Gtk2::VBox(0,0); + $vboxInsert->set_border_width(0); + + $self->{newList} = new Gtk2::RadioButton(undef, $parent->{lang}->{ImportNewList}); + $self->{currentList} = new Gtk2::RadioButton($self->{newList}->get_group, $parent->{lang}->{ImportCurrentList}); + +# $vboxInsert->pack_start($self->{newList},1,1,0); +# $vboxInsert->pack_start($self->{currentList},1,1,0); +# +# $self->vbox->pack_start(new Gtk2::HSeparator, 1, 1, 5); +# $self->vbox->pack_start($vboxInsert,0,0,0); + + $self->{dataTable}->resize(4, 2); + $self->{dataTable}->attach($self->{newList}, 0, 2, 0, 1, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{currentList}, 0, 2, 1, 2, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{labelFile}, 0, 1, 3, 4, 'fill', 'fill', 0, 0); + $self->{dataTable}->attach($self->{file}, 1, 2, 3, 4, ['fill', 'expand'], 'fill', 0, 0); + + $self->{fieldsButtonLabel} = $parent->{lang}->{ImportFieldsTitle}; + $self->{fieldsTip} = $parent->{lang}->{ImportFieldsTip}; + + return $self; + } + +} + + +1; diff --git a/lib/gcstar/GCImport/GCImportAMC.pm b/lib/gcstar/GCImport/GCImportAMC.pm new file mode 100644 index 0000000..c5d38c9 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportAMC.pm @@ -0,0 +1,234 @@ +package GCImport::GCImportAMC; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterAMC; + use base qw(GCImport::GCImportBaseClass); + + use File::Basename; + use File::Copy; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + $self->{fileId} = " AMC_X.Y Ant Movie Catalog 3.5.x www.buypin.com www.antp.be "; + + bless ($self, $class); + return $self; + } + + sub getName + { + return "Ant Movie Catalog (.amc)"; + } + + sub getFilePatterns + { + return (['Ant Movie Catalog (.amc)', '*.amc']); + } + + #Return supported models name + sub getModels + { + return ['GCfilms']; + } + + #Return current model name + sub getModelName + { + return 'GCfilms'; + } + + sub getOptions + { + my $self = shift; + my @options; + return \@options; + } + + # Ignored for the moment + sub wantsFieldsSelection + { + return 0; + } + sub getEndInfo + { + return ""; + } + + sub readBool + { + my $self = shift; + + my $value; + read $self->{file}, $value, 1; + return unpack('C',$value); + } + + sub readInt + { + my $self = shift; + + my $value; + read $self->{file}, $value, 4; + $value = unpack('L',$value); + return undef if $value == 4294967295; + return $value; + } + + sub readString + { + my $self = shift; + my $binary = shift; + + my $length = $self->readInt; + my $string; + + return '' if $length == 0; + read $self->{file}, $string, $length; + + #$string =~ s/\n//gm if !$binary; + $string =~ s/\|/,/gm if !$binary; + + return $string; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my @result; + + open ITEMS, $file; + binmode ITEMS; + $self->{file} = \*ITEMS; + + my $identifier; + read ITEMS, $identifier, length($self->{fileId}); + ($self->{AMCVersion} = $identifier) =~ s/.*?AMC_(\d+)\.(\d+).*/$1.$2/; + my @versions = split m/\./, $self->{AMCVersion}; + $self->{AMCMajorVersion} = $versions[0]; + $self->{AMCMinorVersion} = $versions[1]; + + $self->readString; # name + $self->readString; # mail + if (($self->{AMCMinorVersion} <= 3) && ($self->{AMCMinorVersion} < 5)) + { + $self->readString; # icq + } + $self->readString; # site + $self->readString; # description + + my $baseDir = dirname($file); + + my $i = 0; + + while (! eof ITEMS) + { + $result[$i]->{identifier} = $self->readInt; #Id + $self->readInt; #Add date + $result[$i]->{rating} = $self->readInt; + + if (($self->{AMCMinorVersion} >= 3) && ($self->{AMCMinorVersion} >= 5)) + { + use integer; + $result[$i]->{rating} /= 10; + } + + $result[$i]->{date} = $self->readInt; + $result[$i]->{time} = $self->readInt; + my $ vb = $self->readInt; #Video bitrate + my $ab = $self->readInt; #Audio bitrate + $result[$i]->{number} = $self->readInt; + + $self->readBool; #Checked + + $result[$i]->{place} = $self->readString; #Media label + $result[$i]->{format} = $self->readString; + $self->readString; #Source + $result[$i]->{borrower} = $self->readString; + $result[$i]->{borrower} = 'none' if ! $result[$i]->{borrower}; + $result[$i]->{original} = $self->readString; + $result[$i]->{title} = $self->readString; + $result[$i]->{title} = $result[$i]->{original} if !$result[$i]->{title}; + + $result[$i]->{director} = $self->readString; + $self->readString; #Producer + $result[$i]->{country} = $self->readString; + $result[$i]->{genre} = [[$self->readString]]; + $result[$i]->{actors} = $self->readString; + $result[$i]->{webPage} = $self->readString; + $result[$i]->{synopsis} = $self->readString; + $result[$i]->{comment} = $self->readString; + $result[$i]->{video} = $self->readString; + my $encodings = $self->readString; #Audio format + my $res = $self->readString; #Resolution + $self->readString; #Framerate + $result[$i]->{audio} = $self->readString; + if ($result[$i]->{audio}) + { + my @encodings = split /,/,$encodings; + $result[$i]->{audio} =~ s/(^|,)([^;]*?)(,|$)/$1$2;$_$3/ foreach (@encodings); + $result[$i]->{audio} =~ s/, +/,/g; + $result[$i]->{audio} =~ s/; +/;/g; + } + $result[$i]->{subt} = [[$self->readString]]; + $self->readString; #File size + $result[$i]->{image} = $self->readString; + + my $picture = $self->readString(1); + + if ($result[$i]->{image} =~ /^\..{3}/) + { + my $pictureName = $self->{options}->{parent}->getUniqueImageFileName($result[$i]->{image}, + $result[$i]->{title}); + open PIC, "> $pictureName"; + binmode PIC; + print PIC $picture; + close PIC; + $result[$i]->{image} = $self->{options}->{parent}->transformPicturePath($pictureName); + } + else + { + if (! File::Spec->file_name_is_absolute($result[$i]->{image})) + { + $result[$i]->{image} = $baseDir . $result[$i]->{image}; + } + #copy $result[$i]->{image}, $pictureName; + } + + $i++; + } + + close ITEMS; + + return \@result; + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportAlexandria.pm b/lib/gcstar/GCImport/GCImportAlexandria.pm new file mode 100644 index 0000000..6233ed7 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportAlexandria.pm @@ -0,0 +1,205 @@ +package GCImport::GCImportAlexandria; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterAlexandria; + + use base qw(GCImport::GCImportBaseClass); + use File::Copy; + use Encode; + use GCUtils 'glob'; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + $self->{errors} = ''; + + return $self; + } + + sub getName + { + return "Alexandria"; + } + + sub getOptions + { + my $self = shift; + return [ + { + name => 'where', + type => 'options', + label => 'Where', + default => 'Default', + valuesList => 'Default,Specified' + } + ]; + } + + sub getFilePatterns + { + my $self = shift; + + return (); + } + + #Return supported models name + sub getModels + { + return ['GCbooks']; + } + + sub getModelName + { + my $self = shift; + + return 'GCbooks'; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsFileSelection + { + return 0; + } + + sub wantsDirectorySelection + { + return 1; + } + + sub getEndInfo + { + my $self = shift; + + return ''; + } + + sub getItemsArray + { + my ($self, $directory) = @_; + + my @result = (); + + my @files; + $directory = $ENV{HOME}.'/.alexandria' + if $self->{options}->{where} eq 'Default'; + + foreach (glob "$directory/*") + { + if (-d $_) + { + my @array = glob "$_/*"; + foreach my $file(glob "$_/*") + { + push @files, $file if $file =~ /yaml$/; + } + } + push @files, $_ if /yaml$/; + } + + foreach (@files) + { + push @result, $self->getBook($_); + } + + return \@result; + } + + sub transformValue + { + my ($self, $value) = @_; + + $value =~ s/^"(.*)"$/$1/; + $value =~ s/\\x([0-9a-fA-F]{2})/pack("H2",$1)/ge; + $value = decode('UTF-8', $value); + + return $value; + } + + sub getBook + { + my ($self, $file) = @_; + + my %book; + open BOOK, "<$file"; + binmode(BOOK, ':utf8'); + # 1st line contain ruby information + my $line = ; + my $current = ''; + my $value = ''; + foreach () + { + next if /^#/; + if (/^([a-z_]*): (.*)$/) + { + $current = $1; + next if $current eq 'saved_ident'; + # Tag conversion + $current = 'lendDate' if $current eq 'loaned_since'; + $current = 'borrower' if $current eq 'loaned_to'; + $book{$current} = $self->transformValue($2); + } + elsif (/^\s*- (.*)$/) + { + $book{$current} ||= []; + push @{$book{$current}}, [$self->transformValue($1)]; + } + } + close BOOK; + #Some adjustments + $book{rating} *= 2; + $book{lendDate} =~ s|^([0-9]{4})-([0-9]{2})-([0-9]{2}).*$|$3/$2/$1|; + if ($book{loaned} eq 'false') + { + $book{borrower} = 'none'; + $book{lendDate} = ''; + } + delete $book{loaned}; + #cover + $file =~ s/yaml$/cover/; + if (-e $file) + { + my $pic = $self->{options}->{parent}->getUniqueImageFileName('jpg', $book{title}); + copy $file, $pic; + $book{cover} = $pic; + } + return \%book; + } + +} + + + + +1; \ No newline at end of file diff --git a/lib/gcstar/GCImport/GCImportBase.pm b/lib/gcstar/GCImport/GCImportBase.pm new file mode 100644 index 0000000..026be19 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportBase.pm @@ -0,0 +1,217 @@ +package GCImport::GCImportBase; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCExportImport; + +{ + package GCImport::GCImportBaseClass; + + use base 'GCExportImportBase'; + use File::Basename; + use File::Copy; + + #Methods to be overriden in specific classes + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + + $self->{parsingError} = ''; + $self->{modelAlreadySet} = 0; + + bless ($self, $class); + return $self; + } + + sub getFilePatterns + { + return (['*.*', '*.*']); + } + + sub getSuffix + { + my $self = shift; + return '' if ! ($self->getFilePatterns)[0]; + (my $pattern = ($self->getFilePatterns)[0]->[1]) =~ s/.*?([[:alnum:]]+)/$1/; + return $pattern; + } + + #Return supported models name + sub getModels + { + return []; + } + + #Return current model name + sub getModelName + { + return 'GCfilms'; + } + + sub getOptions + { + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsIgnoreField + { + return 0; + } + + sub wantsImagesSelection + { + return 0; + } + + sub wantsFileSelection + { + return 1; + } + + sub wantsDirectorySelection + { + return 0; + } + + # Returns true if the module should be hidden from + # the menu when a collection of an incompatible kind is open. + sub shouldBeHidden + { + return 0; + } + + sub generateId + { + return 1; + } + + sub getEndInfo + { + } + + sub getItemsArray + { + } + + #End of methods to be overriden + + # If you need really specific processing, you can instead override the process method + + sub process + { + my ($self, $options) = @_; + $self->{options} = $options; + + $options->{parent}->{items}->updateSelectedItemInfoFromPanel; + my $alreadySaved = 0; + if ($options->{newList}) + { + return if !$options->{parent}->checkAndSave; + $alreadySaved = 1; + $options->{parent}->setFile(''); + } + $self->{options}->{parent}->setWaitCursor($self->{options}->{lang}->{ImportListTitle}.' ('.$options->{file}.')'); + my @tmpArray = @{$self->getItemsArray($options->{file})}; + + if ($self->{parsingError}) + { + $self->{options}->{parent}->restoreCursor; + return $self->getEndInfo; + } + + my $realModel = $self->getModelName; + + # Here we really know the model so we force a new list if needed + if (($options->{newList}) + || ($options->{parent}->{model}->getName ne $realModel)) + { + $options->{parent}->newList($realModel, $self->{modelAlreadySet}, $alreadySaved); + } + + my $generateId = $self->generateId; + foreach (@tmpArray) + { + $options->{parent}->{items}->addItem($_, !$generateId, 1); + } + $options->{parent}->{items}->unselect; + $self->{options}->{parent}->restoreCursor; + + $options->{parent}->checkPanelVisibility; + $options->{parent}->selectFirst; + $options->{parent}->markAsUpdated; + $options->{parent}->viewAllItems; + return $self->getEndInfo; + } + + # This method could only be use if $self->{model} has been initialized + sub copyPictures + { + my ($self, $itemsArray, $file) = @_; + return if !$self->{model}; + foreach my $item(@$itemsArray) + { + my $title = $item->{$self->{model}->{commonFields}->{title}}; + foreach my $field(@{$self->{model}->{fieldsImage}}) + { + (my $suffix = $item->{$field}) =~ s/.*?(\.[^.]*)$/$1/; + my $imageFile = $self->{options}->{parent}->getUniqueImageFileName($suffix, $title); + copy(GCUtils::getDisplayedImage($item->{$field}, '', $file), + $imageFile) + if $item->{$field}; + $item->{$field} = $imageFile; + } + } + } + + # 3 methods below are created to implement interface + # from GCFrame plugins could need to simulate if using + # some backends + sub preloadModel + { + my ($self, $model) = @_; + # Preload the model into the factory cache + $self->{modelsFactory}->getModel($model); + } + + sub setCurrentModel + { + my ($self, $model) = @_; + $self->{model} = $self->{modelsFactory}->getModel($model); + } + + sub setCurrentModelFromInline + { + my ($self, $container) = @_; + $self->{model} = GCModelLoader->newFromInline($self, $container); + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportCSV.pm b/lib/gcstar/GCImport/GCImportCSV.pm new file mode 100644 index 0000000..efb3e5d --- /dev/null +++ b/lib/gcstar/GCImport/GCImportCSV.pm @@ -0,0 +1,313 @@ +package GCImport::GCImportCSV; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use utf8; + +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterCSV; + + use base qw(GCImport::GCImportBaseClass); + use Encode; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub wantsFieldsSelection + { + return 1; + } + + sub wantsIgnoreField + { + return 1; + } + + sub wantsFileSelection + { + return 1; + } + + sub getName + { + my $self = shift; + + return "CSV"; + } + + sub getFilePatterns + { + return (['CSV (*.csv)', '*.csv']); + } + + sub getOptions + { + my $self = shift; + + my $charsets = ''; + my @charsetList = Encode->encodings(':all'); + foreach (@charsetList) + { + $charsets .= $_.','; + } + + my $pluginsList = $self->{model}->{parent}->{lang}->{PluginDisabled}.','; + foreach (@{$self->{model}->getPluginsNames}) + { + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$_}; + $pluginsList .= $plugin->getName . ','; + } + + my $searchFieldsList; + foreach (@{$self->{model}->getSearchFields}) + { + $searchFieldsList->{$_} = $self->{model}->getDisplayedText($self->{model}->{fieldsInfo}->{$_}->{label}); + } + + return [ + { + name => 'sep', + type => 'short text', + label => 'Separator', + default => ';' + }, + + { + name => 'charset', + type => 'options', + label => 'Charset', + valuesList => $charsets, + default => 'utf8', + }, + + { + name => 'withHeader', + type => 'yesno', + label => 'Header', + default => '1' + }, + + { + name => 'plugin', + type => 'options', + label => 'Plugin', + valuesList => $pluginsList + }, + + { + name => 'searchfield', + type => 'options', + label => 'SearchField', + valuesList => $searchFieldsList, + default => $self->{model}->{commonFields}->{title} + }, + + { + name => 'first', + type => 'yesno', + label => 'UseFirst', + default => '1' + }, + + ]; + } + + sub getModelName + { + my $self = shift; + return $self->{model}->getName; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my @result; + + # First we try to get the correct plugin + my $plugin; + my $titleField; + my $pluginEnabled; + $pluginEnabled = 1 if $self->{options}->{plugin} + && ($self->{options}->{plugin} ne $self->{options}->{lang}->{PluginDisabled}); + if ($pluginEnabled) + { + $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$self->{options}->{plugin}}; + $titleField = $self->{options}->{searchfield}; + + # Force values of search field if it's incompatible with current plugin + my $compatible = 1; + $compatible = grep /^$titleField$/, @{$plugin->getSearchFieldsArray} + if $titleField; + if (!$compatible) + { + # If it is not, we use the 1st compatible one + $titleField = $plugin->getSearchFieldsArray->[0]; + } + + } + + open ITEMS, $file; + binmode(ITEMS, ':utf8') + if $self->{options}->{charset} eq 'utf8';; + + my $sep = $self->{options}->{sep}; + my $ignoreFirstLine = $self->{options}->{withHeader}; + + my $resultsDialog; + if ((!$self->{options}->{first}) && ($pluginEnabled)) + { + $resultsDialog = $self->{options}->{parent}->getDialog('Results'); + $resultsDialog->setModel($self->{model}, $self->{model}->{fieldsInfo}); + $resultsDialog->setMultipleSelection(0); + } + + my $i = 0; + + while () + { + if ($ignoreFirstLine) + { + $ignoreFirstLine = 0; + next; + } + + chomp; + # Special characters are escaped + my @values = split m/\Q$sep\E/; + + $result[$i] = {} if (!$pluginEnabled); + + my $j = 0; + my $searchTitle = ''; + foreach (@{$self->{options}->{fields}}) + { + $values[$j] = decode($self->{options}->{charset}, $values[$j]) + if $self->{options}->{charset} ne 'utf8'; + $result[$i]->{$_} = $values[$j] if (!$pluginEnabled); + $searchTitle = $values[$j] if (($_ eq $titleField) && ($pluginEnabled)); + $j++; + } + + if (($pluginEnabled) && ($searchTitle ne '')) + { + $plugin->{title} = $searchTitle; + $plugin->{type} = 'load'; + $plugin->{urlField} = $self->{model}->{commonFields}->{url}; + $plugin->{searchField} = $titleField; + + #Initialize what will be pushed in the array + my $info = {$titleField => $searchTitle}; + + $self->{options}->{parent}->setWaitCursor($self->{options}->{lang}->{StatusSearch}.' ('.$_.')'); + $plugin->load; + + my $itemNumber = $plugin->getItemsNumber; + + if ($itemNumber != 0) + { + $plugin->{type} = 'info'; + if (($itemNumber == 1) || ($self->{options}->{first})) + { + $plugin->{wantedIdx} = 0; + } + else + { + my $withNext = 0; + my @items = $plugin->getItems; + $resultsDialog->setWithNext(0); + $resultsDialog->setSearchPlugin($plugin); + $resultsDialog->setList($_, @items); + $resultsDialog->show; + if ($resultsDialog->{validated}) + { + $plugin->{wantedIdx} = $resultsDialog->getItemsIndexes->[0]; + } + } + $info = $plugin->getItemInfo; + my $title = $info->{$titleField}; + $self->{options}->{parent}->{defaultPictureSuffix} = $plugin->getDefaultPictureSuffix; + foreach my $field(@{$self->{model}->{managedImages}}) + { + $info->{$field} = '' if $info->{$field} eq 'empty'; + next if !$info->{$field}; + ($info->{$field}) = $self->{options}->{parent}->downloadPicture($info->{$field}, $title); + } + $info->{comment} = $self->getLang->{CommentAuto} + . "\n" + . $self->getLang->{CommentSite} + . $plugin->getName() + . "\n" + . $self->getLang->{CommentTitle} + . $_ + . "\n"; + + # Add the default value + my $defaultInfo = $self->{model}->getDefaultValues; + foreach my $field(keys %$defaultInfo) + { + next if exists $info->{$field}; + $info->{$field} = $defaultInfo->{$field}; + } + my $j = 0; + foreach (@{$self->{options}->{fields}}) + { + $values[$j] = decode($self->{options}->{charset}, $values[$j]) + if $self->{options}->{charset} ne 'utf8'; + $info->{$_} = $values[$j]; + $j++; + } + } + + push @result, $info; + $self->{options}->{parent}->restoreCursor; + } + + $i++; + } + close ITEMS; + + + return \@result; + } + + + sub getEndInfo + { + my $self = shift; + my $message; + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportDVDProfiler.pm b/lib/gcstar/GCImport/GCImportDVDProfiler.pm new file mode 100644 index 0000000..6f47d7b --- /dev/null +++ b/lib/gcstar/GCImport/GCImportDVDProfiler.pm @@ -0,0 +1,192 @@ +package GCImport::GCImportDVDProfiler; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterDVDProfiler; + + use base qw(GCImport::GCImportBaseClass); + + use XML::Simple; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + + return $self; + } + + sub getName + { + return "DVDProfiler (.xml)"; + } + + sub getOptions + { + my $self = shift; + my @options; + return \@options; + } + + sub getFilePatterns + { + return (['DVDProfiler (.xml)', '*.xml']); + } + + #Return supported models name + sub getModels + { + return ['GCfilms']; + } + + # Ignored for the moment + sub wantsFieldsSelection + { + return 0; + } + sub getEndInfo + { + return ""; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my $xml; + my $data; + # creer un objet + $xml = XML::Simple->new; # sans keyAttr les dvd seront dans une liste ou chaque dvd sera identifie par l'emplacement qu'il a dans cette liste + $data = $xml->XMLin ("$file"); + + + my @result; + my $film; + + foreach $film(@{$data->{DVD}}){ + my $item; + + $item->{title} = $film->{Title}; + $item->{date} = $film->{ProductionYear}; + $item->{time} = $film->{RunningTime}.' mn'; + $item->{synopsis} = $film->{Overview}; + ####### DIRECTOR ######### + my $director; + + if (ref ($film->{Credits}->{Credit}) eq "ARRAY") { + foreach $director(@{$film->{Credits}->{Credit}}){ + if (($director->{CreditType}) eq 'Direction') { + $item->{director} .= $director->{FirstName}.' '.$director->{LastName}.', '; + + } + } + } + else { + if (($film->{Credits}->{Credit}->{CreditType}) eq 'Direction') { + $item->{director} .= $film->{Credits}->{Credit}->{FirstName}.' '.$film->{Credits}->{Credit}->{LastName}; + } + } + ###### END DIRECTOR ###### + + ####### ACTORS ######### + my $actor; + if (ref ($film->{Actors}->{Actor}) eq "ARRAY") { + foreach $actor(@{$film->{Actors}->{Actor}}){ + $item->{actors} .= $actor->{FirstName}.' '.$actor->{LastName}.' '.'('.$actor->{Role}.')'.', '; + + } + } + else { + $item->{actors} .= $film->{Actors}->{Actor}->{'FirstName'}.' '.$film->{Actors}->{Actor}->{LastName}.' '.'('.$film->{Actors}->{Actor}->{Role}.')'; + } + ###### END ACTORS ###### + + ####### AUDIO ######### + my $audio; + if (ref ($film->{Audio}->{AudioFormat}) eq "ARRAY"){ + foreach $audio(@{$film->{Audio}->{AudioFormat}}){ + $item->{audio} .= $audio->{AudioLanguage}.', '; + + } + } + else { + $item->{audio} .= $film->{Audio}->{AudioFormat}->{'AudioLanguage'}; + } + ###### END AUDIO ###### + ####### SUBT ######### + my $subt; + if (ref ($film->{Subtitles}->{Subtitle}) eq "ARRAY"){ + foreach $subt(@{$film->{Subtitles}->{Subtitle}}){ + $item->{subt} .= $subt.', '; + + } + } + else { + $item->{subt} = $film->{Subtitles}->{Subtitle}; + } + ####### END SUBT ######### + ####### TYPE ######### + my $type; + if (ref ($film->{Genres}->{Genre}) eq "ARRAY"){ + foreach $type(@{$film->{Genres}->{Genre}}){ + $item->{type} .= $type.','; + + } + } + else { + $item->{type} = $film->{Genres}->{Genre}; + } + ####### END TYPE ######### + + #$item->{original} = $film->{Title}; + #$item->{subt} = $film->{Subtitles}->{Subtitle}; + #$item->{borrower} = $film->{Title}; + #$item->{lendDate} = $film->{Title}; + #$item->{history} = $film->{Title}; + #$item->{seen} = $film->{Title};# non par defaut ? + #$item->{comment} = $film->{Title}; + #$item->{image} = $film->{Title}; + #$item->{country} = $film->{Title}; + #$item->{number} = $film->{CollectionNumber}; + #$item->{rating} = $film->{Title};# note par defaut + #$item->{format} = $film->{Title};#DVD par d�faut ? + #$item->{webPage} = $film->{Title}; + #$item->{place} = $film->{Title}; + $item->{director} =~ s/, $//; + $item->{actors} =~ s/, $//; + $item->{audio} =~ s/, $//; + $item->{subt} =~ s/, $//; + $item->{type} =~ s/, $//; + push @result, $item; + } + return \@result; + + } +} + +1; \ No newline at end of file diff --git a/lib/gcstar/GCImport/GCImportFolder.pm b/lib/gcstar/GCImport/GCImportFolder.pm new file mode 100644 index 0000000..10d9fd1 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportFolder.pm @@ -0,0 +1,510 @@ +package GCImport::GCImportFolder; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterFolder; + + use File::Find; + use File::Basename; + use base qw(GCImport::GCImportBaseClass); + + use GCPlugins; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsFileSelection + { + return 1; + } + + sub wantsDirectorySelection + { + return 1; + } + + sub shouldBeHidden + { + return 1; + } + + sub getFilePatterns + { + return (); + } + + #Return supported models name + sub getModels + { + return ['GCfilms', 'GCMusics']; + } + + sub getOptions + { + my $self = shift; + + my $pluginsList; + foreach (@{$self->{model}->getPluginsNames}) + { + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$_}; + push @$pluginsList,$plugin->getName; + } + + + return [ + { + name => 'plugin', + type => 'options', + label => 'Plugin', + valuesList => $pluginsList + }, + { + name => 'multipleResult', + type => 'options', + label => 'MultipleResult', + tooltip => 'MultipleResultTooltip', + valuesList => 'Ask,AskEnd,AddWithoutInfo,DontAdd,TakeFirst', + default => 'Ask', + }, + { + name => 'noResult', + type => 'options', + label => 'NoResult', + tooltip => 'NoResultTooltip', + valuesList => 'AddWithoutInfo,DontAdd', # TODO AskNewName AskNewPlugin at End + default => 'AddEmpty', + }, + { + name => 'recursive', + type => 'yesno', + label => 'Recursive', + default => '1' + }, + + { + name => 'suffixes', + type => 'short text', + label => 'Suffixes', + tooltip => 'SuffixesTooltip', + default => '', + }, + + { + name => 'remove', + type => 'short text', + label => 'Remove', + tooltip => 'RemoveTooltip', + default => '', + }, + { + name => 'removeWholeWord', + type => 'yesno', + label => 'RemoveWholeWord', + tooltip => 'RemoveTooltipWholeWord', + default => '1', + }, + { + name => 'removeRegularExpr', + type => 'yesno', + label => 'RemoveRegularExpr', + tooltip => 'RemoveTooltipRegularExpr', + changedCallback => sub { + my ($self,$widget) =@_; + $widget->[0]->{options}->{removeWholeWord}->lock($self->getValue); + }, + default => '0', + }, + { + name => 'skipFileAlreadyInCollection', + type => 'options', + label => 'SkipFileAlreadyInCollection', + tooltip => 'SkipFileAlreadyInCollectionTooltip', + valuesList => 'SkipFileNo,SkipFileFullPath,SkipFileFileName,SkipFileFileNameAndUpdate', + default => 'SkipFileNo', + }, + { + name => 'infoFromFileNameRegExp', + type => 'history text', + label => 'InfoFromFileNameRegExp', + tooltip => 'InfoFromFileNameRegExpTooltip', + initValues => ['', + '^$A\s*([[\(]part $x( of $y)?[)\]])?\s*([[\(]$Y[)\]])?\s*$', + '^$N\s+[^\w ]\s+S$SE$E\s+[^\w ]\s+$T\s+([[(]part $x( of $y)?[)\]])?\s*$', + ], + default => '', + }, + ]; + + + } + + sub getModelName + { + my $self = shift; + return $self->{model}->getName; + } + + # Required by extracter to make this class acts as a panel + sub AUTOLOAD + { + return []; + } + + sub getItemsArray + { + my ($self, $directory) = @_; + my @result; + my @filesList; + + #First we try to get the correct plugin + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$self->{options}->{plugin}}; + $plugin->{bigPics} = $self->{options}->{parent}->{options}->bigPics; + my $titleField = $self->{model}->{commonFields}->{title}; + my $fileField = $self->{model}->{commonFields}->{play}; + + # Required by extracter + $self->{lang} = $self->{options}->{lang}; + + (my $suffixes = $self->{options}->{suffixes}) =~ s/[,; ]/\|/g; + $suffixes =~ s/\*\.//g; + # Create list of files + if ($self->{options}->{recursive}) + { + find(sub { + return if -d $File::Find::name; + return if ! /$suffixes$/; + my $name=Encode::decode_utf8($File::Find::name); + push @filesList, $name; + }, $directory); + } + else + { + foreach (glob "$directory/*") + { + next if -d $_; + next if ! /$suffixes$/; + push @filesList, $_; + } + } + my $resultsDialog; + # initialize choose good result dialog if needed + if (($self->{options}->{multipleResult} ne 'Ask') || ($self->{options}->{multipleResult} ne 'AskEnd')) + { + $resultsDialog = $self->{options}->{parent}->getDialog('Results'); + $resultsDialog->setModel($self->{model}, $self->{model}->{fieldsInfo}); + $resultsDialog->setMultipleSelection(0); + } + #Initialize stuff to retrieve info from name with regexp + my $infoFromName; + if ($self->{options}->{infoFromFileNameRegExp} ne '') + { + my %knownParam=($titleField=>'T',alphabTitle=>'A',year=>'Y',season=>'S',episode=>'E',alphabSeries=>'N',number=>'x',totNumber=>'y'); + my $orderStr= $self->{options}->{infoFromFileNameRegExp}; + $orderStr=~ s/(?{options}->{infoFromFileNameRegExp}; + # avoid capturing something else than $T,$A ... make already present () not capturing + $myRegExp =~ s/(?{model}->{parent}->{articles}}).')\'?\b'; + our $myRegExpArt=qr/^(.*?)(?:, ?($articles))?$/i; + + $myRegExp =~ s/\$A/(.*?(?:, ?$articles)?)/g; + $myRegExp =~ s/\$N/(.*?(?:, ?$articles)?)/g; + $myRegExp =~ s/\$T/(.*?)/; + $myRegExp =~ s/\$Y/(\\d{2}|\\d{4}?)/; + $myRegExp =~ s/\$x/(\\d{1,2})/; + $myRegExp =~ s/\$y/(\\d{1,2})/; + $myRegExp =~ s/\$E/(\\d{1,4}?)/; + $myRegExp =~ s/\$S/(\\d{1,2}?)/; + sub deAlpha{ + my $s; + $_[0] =~ $myRegExpArt; + $s=$1; + my $a=$2.' ' if $2 && (substr($2,-1) ne '\''); + $s=$a.$s if $a; + return $s; + } + # Check if regexp is good + my $pattern = shift; + my $test = eval { $myRegExp=qr/$myRegExp/i }; + #print $myRegExp; + # + if ($@) + { + $myRegExp= qr/./ ;print $@; + } + my $i=2; + $infoFromName=sub { + my $n=$_[0] ; + $n=~ $myRegExp; + my %info; # TODO Can be more readable in Perl 5.10 by using named capturing + $info{$places{1}}=$1 if $1;$info{$places{2}}=$2 if $2;$info{$places{3}}=$3 if $3;$info{$places{4}}=$4 if $4;$info{$places{5}}=$5 if $5; + $info{$places{6}}=$6 if $6;$info{$places{7}}=$7 if $7;$info{$places{8}}=$8 if $8;$info{$places{9}}=$9 if $9;$info{$places{10}}=$10 if $10; + $info{$places{11}}=$11 if $11;$info{$places{12}}=$12 if $12;$info{$places{13}}=$13 if $13;$info{$places{14}}=$14 if $14;$info{$places{15}}=$15 if $15; + $info{$places{16}}=$16 if $16;$info{$places{17}}=$17 if $17;$info{$places{18}}=$18 if $18;$info{$places{19}}=$19 if $19;$info{$places{20}}=$20 if $20; + + $info{$titleField}=deAlpha($info{alphabTitle}) if($info{alphabTitle}); + $info{series}=deAlpha($info{alphabSeries}) if($info{alphabSeries}); + return \%info; + } + } + # initialize regexp word to remove + my $removed =$self->{options}->{remove}; + if(!$self->{options}->{removeRegularExpr}) + { + $removed =~ s/[,; ]/\|/g; + if($self->{options}->{removeWholeWord}) + { + $removed=~s/\|/\\b\|\\b/g ; + $removed='\b'.$removed.'\b'; + } + } + # if we want to ignore files already in the list + # we initialize a hash with filenames to be fast ! + my %fileNameKnown; + if($self->{options}->{skipFileAlreadyInCollection} ne 'SkipFileNo') + { + if($self->{options}->{skipFileAlreadyInCollection} eq 'SkipFileFullPath') + { + foreach my $originalFilm(@{$self->{options}->{originalList}->{itemArray}}) + { + $fileNameKnown{$originalFilm->{$fileField}}=$originalFilm; + } + } + else + { + foreach my $originalFilm(@{$self->{options}->{originalList}->{itemArray}}) + { + $fileNameKnown{basename($originalFilm->{$fileField})}=$originalFilm; + } + } + } + my $hasFileWaiting=0;my $inWaitingQueue=0; + # Main loop on files entries + file: foreach my $file(@filesList) + { + if($file eq 'WaitingList') + { + $inWaitingQueue=1; + next file; + } + # Skip file already in the collection + next file if(($self->{options}->{skipFileAlreadyInCollection} eq 'SkipFileFullPath') && (exists $fileNameKnown{$file})); + next file if(($self->{options}->{skipFileAlreadyInCollection} eq 'SkipFileFileName') && (exists $fileNameKnown{basename($file)})); + if(($self->{options}->{skipFileAlreadyInCollection} eq 'SkipFileFileNameAndUpdate') && (exists $fileNameKnown{basename($file)})) + { + # if filename already in collection, and collection full path invalid : correct it + if (!(-e $fileNameKnown{basename($file)}->{$fileField})) + { + print "Path updated : ",$fileNameKnown{basename($file)}->{$fileField},"\n"; + print " --> ",$file,"\n"; + $fileNameKnown{basename($file)}->{$fileField}=$file; + } + next file; + } + + # Get info from the file (avi, mp3, ...) + my $extracter = $self->{model}->getExtracter($self, $file, $self, $self->{model}); + my $extracted = $extracter->getInfo; + # Add info from file + my $infoFromFile={$fileField => $file}; + foreach my $field(keys %$extracted) + { + $infoFromFile->{$field} = $extracted->{$field}->{value}; + } + + # Test if subtitle is present + if ($self->{model}->getName eq 'GCfilms') + { + my @subtitlesExt=qw(sub srt); + my @subtitlesFiles; + my $startFileName=$file; + $startFileName=~s/\.[^.]*$//; + for my $ext(@subtitlesExt) + { + my $fileSubsName=$startFileName.'.'.$ext; + if(-e $fileSubsName) + { + #TODO Try to guess the language see cpan + my $lang=["Yes"]; + push @subtitlesFiles,$lang; + } + } + $infoFromFile->{subt}=\@subtitlesFiles if (@subtitlesFiles); + } + my $infoFromFileName; + my $name = basename($file); + # Filter the name + # Remove suffix + $name =~ s/\.[^.]*$//; + # Try to apply regexp on filename + if ($self->{options}->{infoFromFileNameRegExp} ne '') + { + $infoFromFileName=&$infoFromName($name); + $name = $infoFromFileName->{$titleField} if ($infoFromFileName->{$titleField} ne ''); + #TODO: Use this known info to search with plugin + } + # Remove everything between () {} [] + $name =~ s/[\(\[\{].*?[\)\]\}]/ /g; + # Remove special characters + $name =~ s/[-\._,#@"']/ /g; + #'" + # Remove info from extracter for movies + if ($self->{model}->getName eq 'GCfilms') + { + my $info = $extracted->{video}->{value}.'|'.$extracted->{audio}->{value}->[0]->[1]; + $info =~ s/ (.*?)//g; + $name =~ s/$info//g; + } + $name =~ s/$removed//gi; + + # $name contains the title to search + $plugin->{title} = $name; + $plugin->{type} = 'load'; + $plugin->{urlField} = $self->{model}->{commonFields}->{url}; + $plugin->{searchField} = $titleField; + + #Initialize what will be pushed in the array + my $infoPlugin = {$titleField => $name}; + + $self->{options}->{parent}->setWaitCursor($self->{options}->{lang}->{StatusSearch}.' ('.$name.')'); + $plugin->load; + + my $itemNumber = $plugin->getItemsNumber; + + if ($itemNumber == 0) + { + goto endPluginGetItemInfo if (($self->{options}->{noResult} eq 'AddEmpty')); + next file if (($self->{options}->{noResult} eq 'DontAdd')); + } + else + { + $plugin->{type} = 'info'; + if (($itemNumber == 1) || ($self->{options}->{multipleResult} eq 'TakeFirst')) + { + $plugin->{wantedIdx} = 0; + } + elsif($self->{options}->{multipleResult} eq 'AddWithoutInfo' ) + { + goto endPluginGetItemInfo; + } + elsif($self->{options}->{multipleResult} eq 'DontAdd' ) + { + next file; + } + elsif($self->{options}->{multipleResult} eq 'AskEnd' && !$inWaitingQueue) + { + # re push the filename at the end of the list, to be proceded + push @filesList,'WaitingList' if !$hasFileWaiting; + push @filesList,$file; + $hasFileWaiting=1; + next file; + } + else + { + # Ask the user to choose + my $withNext = 0; + my @items = $plugin->getItems; + $resultsDialog->setWithNext(0); + $resultsDialog->setSearchPlugin($plugin); + $resultsDialog->setList($name, @items); + $resultsDialog->show; + if ($resultsDialog->{validated}) + { + $plugin->{wantedIdx} = $resultsDialog->getItemsIndexes->[0]; + } + } + $infoPlugin = $plugin->getItemInfo; + my $title = $infoPlugin->{$titleField}; + $self->{options}->{parent}->{defaultPictureSuffix} = $plugin->getDefaultPictureSuffix; + foreach my $field(@{$self->{model}->{managedImages}}) + { + $infoPlugin->{$field} = '' if $infoPlugin->{$field} eq 'empty'; + next if !$infoPlugin->{$field}; + ($infoPlugin->{$field}) = $self->{options}->{parent}->downloadPicture($infoPlugin->{$field}, $title); + } + $infoPlugin->{plugin} =$plugin->getName(); + $infoPlugin->{comment} = $self->getLang->{CommentAuto} + . "\n" + . $self->getLang->{CommentSite} + . $plugin->getName() + . "\n" + . $self->getLang->{CommentTitle} + . $name + . "\n" + . $extracted->{comment}->{displayed}; + } + endPluginGetItemInfo: + + # Add the default value + my $defaultInfo = $self->{model}->getDefaultValues; + + my $info; + # TODO : ask the user for order, or even for order on each fields + my @order=($defaultInfo,$infoFromFile,$infoFromFileName,$infoPlugin); + for my $infoSource(@order) + { + foreach my $field(keys %$infoSource) + { + $info->{$field} =$infoSource->{$field} if $infoSource->{$field}; + } + } + push @result, $info; + $self->{options}->{parent}->restoreCursor; + } + return \@result; + } + + + sub getEndInfo + { + my $self = shift; + my $message; + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportGCfilms.pm b/lib/gcstar/GCImport/GCImportGCfilms.pm new file mode 100644 index 0000000..285e17d --- /dev/null +++ b/lib/gcstar/GCImport/GCImportGCfilms.pm @@ -0,0 +1,190 @@ +package GCImport::GCImportGCfilms; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterGCfilms; + use base qw(GCImport::GCImportBaseClass); + use File::Basename; + use File::Copy; + use GCUtils; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + $self->{errors} = ''; + + #The fields as they were in GCfilms 6.1 + # If name has changed in GCstar, the comment contains the original one + $self->{fields} = [ + 'id', + 'title', + 'date', + 'time', + 'director', + 'country', # nat + 'genre', # type + 'image', + 'actors', + 'original', # orig + 'synopsis', + 'webPage', # url + 'seen', + 'format', + 'number', + 'place', + 'rating', + 'comment', + 'audio', + 'subt', + 'borrower', + 'lendDate', + 'borrowings', # history + 'age', + 'video', + 'serie', # collection + 'rank', + 'trailer', + ]; + + return $self; + } + + sub getName + { + return "GCfilms (.gcf)"; + } + + sub getFilePatterns + { + return (['GCfilms (.gcf)', '*.gcf']); + } + + #Return supported models name + sub getModels + { + return ['GCfilms']; + } + + sub getOptions + { + my $self = shift; + return [ + { + name => 'generate', + type => 'yesno', + label => 'ImportGenerateId', + default => '1' + }, + ]; + } + + # Ignored for the moment + sub wantsFieldsSelection + { + return 0; + } + sub generateId + { + my $self = shift; + return $self->{options}->{generate}; + } + sub getEndInfo + { + return ""; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my @result; + + open MOVIES, "<$file"; + my $gotFirstLine = 0; + my $i = 0; + while () + { + chomp; + my @values = split m/\|/; + + if (!$gotFirstLine) + { + $gotFirstLine = 1; + if ($values[0] eq 'GCfilms') + { + binmode( MOVIES, ':utf8' ) if $values[2] eq 'UTF8'; + next; + } + } + my $idx = 0; + for my $field (@{$self->{fields}}) + { + my $value = $values[$idx]; + if ($field eq 'image') + { + my $origPath = GCUtils::getDisplayedImage($value, '', $file); + my $origFile = basename($origPath); + $origFile = $origPath = '' if ! -f $origPath; + # We copy the image only if it was a generated one and if we use the default path + if ($origFile =~ /^gcfilms_/) + { + # We don't change the filename as gcstar has a different pattern for automatic files + my $destPath = $self->{options}->{parent}->getImagesDir; + copy($origPath, $destPath) if $origPath ne $destPath; + $result[$i]->{image} = $destPath.$origFile; + } + else + { + # We use the full path + $result[$i]->{image} = $origPath; + } + } + else + { + $value =~ s|:|;|gm if $field eq 'borrowings'; + $value =~ s|
|\n|gm; + $value =~ s|<.*?>||gm; + if (!$value) + { + $value = 0 if $field eq 'age'; + $value = 'none' if $field eq 'borrower'; + } + $result[$i]->{$field} = $value; + } + $idx++; + } + $i++; + } + return \@result; + + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportGCstar.pm b/lib/gcstar/GCImport/GCImportGCstar.pm new file mode 100644 index 0000000..1bff9c2 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportGCstar.pm @@ -0,0 +1,106 @@ +package GCImport::GCImportGCstar; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterGCstar; + use base qw(GCImport::GCImportBaseClass); + + use GCBackend::GCBackendXmlParser; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + return $self; + } + + sub getName + { + return "GCstar (.gcs)"; + } + + sub getFilePatterns + { + return (['GCstar (.gcs)', '*.gcs']); + } + + sub getModelName + { + my $self = shift; + return $self->{model}->getName; + } + + sub getOptions + { + my $self = shift; + return [ + { + name => 'copyPics', + type => 'yesno', + label => 'CopyPictures', + default => '1' + }]; + } + + # Ignored for the moment + sub wantsFieldsSelection + { + return 0; + } + sub getEndInfo + { + return ""; + } + + sub getItemsArray + { + my ($self, $file) = @_; + + my $parent = $self->{options}->{parent}; + $self->{modelsFactory} = $parent->{modelsFactory}; + $self->{modelAlreadySet} = 0; + + my $copyPics = 1; + $copyPics = $self->{options}->{copyPics} + if exists $self->{options}->{copyPics}; + + my $backend = new GCBackend::GCBeXmlParser($self); + $backend->setParameters(file => $file); + my $loaded = $backend->load(0); + my $itemsArray = $loaded->{data}; + if ($copyPics) + { + $self->copyPictures($itemsArray, $file); + } + return $itemsArray; + + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportList.pm b/lib/gcstar/GCImport/GCImportList.pm new file mode 100644 index 0000000..a2b2ec2 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportList.pm @@ -0,0 +1,202 @@ +package GCImport::GCImportList; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterList; + + use base qw(GCImport::GCImportBaseClass); + + use GCPlugins; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsFileSelection + { + return 1; + } + + sub getFilePatterns + { + return (); + } + + sub getOptions + { + my $self = shift; + + my $pluginsList = ''; + foreach (@{$self->{model}->getPluginsNames}) + { + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$_}; + $pluginsList .= $plugin->getName . ','; + } + + + return [ + { + name => 'plugin', + type => 'options', + label => 'Plugin', + valuesList => $pluginsList + }, + + { + name => 'first', + type => 'yesno', + label => 'UseFirst', + default => '1' + }, + ]; + + + } + + sub getModelName + { + my $self = shift; + return $self->{model}->getName; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my @result; + + #First we try to get the correct plugin + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$self->{options}->{plugin}}; + $plugin->{bigPics} = $self->{options}->{parent}->{options}->bigPics; + + my $titleField = $self->{model}->{commonFields}->{title}; + + open ITEMS, $file; + binmode(ITEMS, ':utf8'); + + my $i = 0; + + my $resultsDialog; + if (!$self->{options}->{first}) + { + $resultsDialog = $self->{options}->{parent}->getDialog('Results'); + $resultsDialog->setModel($self->{model}, $self->{model}->{fieldsInfo}); + $resultsDialog->setMultipleSelection(0); + } + while () + { + chomp; + next if ! $_; + # $_ contains the title to search + $plugin->{title} = $_; + $plugin->{type} = 'load'; + $plugin->{urlField} = $self->{model}->{commonFields}->{url}; + $plugin->{searchField} = $titleField; + #Initialize what will be pushed in the array + my $info = {$titleField => $_}; + + $self->{options}->{parent}->setWaitCursor($self->{options}->{lang}->{StatusSearch}.' ('.$_.')'); + $plugin->load; + + my $itemNumber = $plugin->getItemsNumber; + + if ($itemNumber != 0) + { + $plugin->{type} = 'info'; + if (($itemNumber == 1) || ($self->{options}->{first})) + { + $plugin->{wantedIdx} = 0; + } + else + { + my $withNext = 0; + my @items = $plugin->getItems; + $resultsDialog->setWithNext(0); + $resultsDialog->setSearchPlugin($plugin); + $resultsDialog->setList($_, @items); + $resultsDialog->show; + if ($resultsDialog->{validated}) + { + $plugin->{wantedIdx} = $resultsDialog->getItemsIndexes->[0]; + } + } + $info = $plugin->getItemInfo; + my $title = $info->{$titleField}; + $self->{options}->{parent}->{defaultPictureSuffix} = $plugin->getDefaultPictureSuffix; + foreach my $field(@{$self->{model}->{managedImages}}) + { + $info->{$field} = '' if $info->{$field} eq 'empty'; + next if !$info->{$field}; + ($info->{$field}) = $self->{options}->{parent}->downloadPicture($info->{$field}, $title); + } + $info->{comment} = $self->getLang->{CommentAuto} + . "\n" + . $self->getLang->{CommentSite} + . $plugin->getName() + . "\n" + . $self->getLang->{CommentTitle} + . $_ + . "\n"; + + # Add the default value + my $defaultInfo = $self->{model}->getDefaultValues; + foreach my $field(keys %$defaultInfo) + { + next if exists $info->{$field}; + $info->{$field} = $defaultInfo->{$field}; + } + } + + push @result, $info; + $self->{options}->{parent}->restoreCursor; + } + close ITEMS; + return \@result; + } + + + sub getEndInfo + { + my $self = shift; + my $message; + + return $message; + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportMyMovies.pm b/lib/gcstar/GCImport/GCImportMyMovies.pm new file mode 100644 index 0000000..a51b1c4 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportMyMovies.pm @@ -0,0 +1,211 @@ +package GCImport::GCImportMyMovies; + +################################################################################# +# +# Created by Rob Maas rob@progob.nl | http://www.robmaas.eu (2008) +# +# +# This file is strongly based op the already existing DVDProfiler +# import class. It is also my first perl script :-) +# +# Since MyMovies has some different fields then GCStar, there are some work +# arounds to get as much of the original data. +# +# If the field ExtraFeatures is filled, it will appear in the General tab in +# the synopsis. +# +# The rating system will be calculated back to the Dutch rating system. +# +# If data was imported from IMDB, the webpage button will link to the specific +# movie site on IMDB. +# +# Cause GCstar hasn´t (yet?) a real EAN field, the EAN code is placed on the +# details tab under comments. +# +# Special thanks goes to Tian who helped me with some array trouble :-P and +# for creating this software. +# +################################################################################# + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterMyMovies; + + use base qw(GCImport::GCImportBaseClass); + + use XML::Simple; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + + return $self; + } + + sub getName + { + return "MyMovies (.xml)"; + } + + sub getOptions + { + my $self = shift; + my @options; + return \@options; + } + + sub getFilePatterns + { + return (['MyMovies (.xml)', '*.xml']); + } + + #Return supported models name + sub getModels + { + return ['GCfilms']; + } + + # Ignored for the moment + sub wantsFieldsSelection + { + return 0; + } + sub getEndInfo + { + return ""; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my $xml; + my $data; + # Creates an object / Skip empty ellements + $xml = new XML::Simple(suppressempty => 1); + $data = $xml->XMLin ("$file"); + + my @result; + my $film; + + # For each "Title" in the XML file + foreach $film(@{$data->{Title}}){ + my $item; + + #General fields + $item->{title} = $film->{LocalTitle}; + $item->{original} = $film->{OriginalTitle}; + $item->{date} = $film->{ProductionYear}; + $item->{time} = $film->{RunningTime}.' min'; + $item->{synopsis} = $film->{Description}; + + #Extra's on the disc + if ($film->{ExtraFeatures}->{content}){ + $item->{synopsis} .= "\n\nEXTRA\n"; + $item->{synopsis} .= $film->{ExtraFeatures}->{content}; + } + + #Based on the Dutch ratings! + + $item->{age} = + ($film->{ParentalRating}->{Value} == 1) ? 1 + : ($film->{ParentalRating}->{Value} == 2) ? 2 + : ($film->{ParentalRating}->{Value} == 3) ? 5 + : ($film->{ParentalRating}->{Value} == 4) ? 12 + : 16; + + if ($film->{DataProvider} eq 'IMDB.com'){ + $item->{webPage} = 'http://www.imdb.com/title/'.$film->{DataProviderId}; + } + $item->{country} = $film->{Country}; + + ###### GENRE ######### + my $type; + if (ref ($film->{Genres}->{Genre}) eq "ARRAY"){ + foreach $type(@{$film->{Genres}->{Genre}}){ + $item->{genre} .= $type.','; + } + } + else{ + $item->{genre} = $film->{Genres}->{Genre}; + } + ###### END GENRE ######### + ####### DIRECTOR AND ACTORS ######### + my $actor; + if (ref ($film->{Persons}->{Person}) eq "ARRAY") { + foreach $actor(@{$film->{Persons}->{Person}}){ + if ($actor->{Type} eq 'Director'){ + $item->{director} = $actor->{Name}; + } + else{ + $item->{actors}.= $actor->{Name}.' ('.$actor->{Role}.'), '; + } + } + } + else{ + $item->{actors}.= $film->{Persons}->{Person}->{Name}.' ('.$film->{Persons}->{Person}->{Role}.')'; + } + ###### END DIRECTOR AND ACTORS ###### + + ##DETAIL + $item->{format} = $film->{Type}; + $item->{video} = $film->{VideoStandard}; + $item->{added} = $film->{Added}; + $item->{identifier} = $film->{CollectionNumber}; + + #Temporately cause a real barcode field is missing + if (length($film->{Barcode}) gt 0){ + $item->{comment} = 'EAN: '.$film->{Barcode}; + } + + ###### AUDIO ######### + my $audio; + my @audioTracks; + if (ref ($film->{AudioTracks}->{AudioTrack}) eq "ARRAY"){ + foreach $audio(@{$film->{AudioTracks}->{AudioTrack}}){ + push @audioTracks, [$audio->{Language}, $audio->{Type}.' '.$audio->{Channels}]; + } + $item->{audio} = \@audioTracks; + } + else{ + $item->{audio} = [[$audio->{Language}, $audio->{Type}]]; + } + ###### END AUDIO ######### + ###### SUBTITLES ######### + my $subt; + if (ref ($film->{Subtitles}->{Subtitle}) eq "ARRAY"){ + foreach $subt(@{$film->{Subtitles}->{Subtitle}}){ + $item->{subt} .= $subt->{Language}.','; + } + } + else{ + $item->{subt} = $subt->{Language}; + } + ###### END SUBTITLES ######### + + #$item->{borrower} = $film->{Title}; + #$item->{lendDate} = $film->{Title}; + #$item->{history} = $film->{Title}; + #$item->{seen} = $film->{Title};# non par defaut ? + #$item->{image} = $film->{Title}; + #$item->{number} = $film->{CollectionNumber}; + #$item->{rating} = $film->{Title};# note par defaut + #$item->{place} = $film->{Title}; + + $item->{director} =~ s/, $//; + $item->{actors} =~ s/, $//; + $item->{audio} =~ s/, $//; + $item->{subt} =~ s/, $//; + $item->{genre} =~ s/, $//; + push @result, $item; + } + return \@result; + + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportScanner.pm b/lib/gcstar/GCImport/GCImportScanner.pm new file mode 100644 index 0000000..92c5bba --- /dev/null +++ b/lib/gcstar/GCImport/GCImportScanner.pm @@ -0,0 +1,394 @@ +package GCImport::GCImportList; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +use GCImport::GCImportBase; + +{ + package GCScannerDialog; + use base 'GCModalDialog'; + use XML::Simple; + + sub new + { + my ($proto, $parent, $lang, $model, $serverSocket) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new($parent, + $lang->{Waiting}); + bless($self, $class); + + $self->{lang} = $lang; + $self->{model} = $model; + $self->{accepted} = 0; + if ($serverSocket) + { + $self->{network} = 1; + $self->{serverSocket} = $serverSocket; + } + my $table = new Gtk2::Table(2, 2); + $table->set_row_spacings($GCUtils::halfMargin); + $table->set_col_spacings($GCUtils::margin); + $table->set_border_width($GCUtils::margin); + $self->{previousLabel} = new GCLabel(''); + $self->{promptLabel} = new GCLabel($lang->{ScanPrompt}); + $table->attach($self->{previousLabel}, 0, 1, 0, 1, 'fill', 'fill', 0, 0); + $table->attach($self->{promptLabel}, 0, 1, 1, 2, 'fill', 'fill', 0, 0); + my $eanLabel = new GCLabel($lang->{EAN}); + $self->{ean} = new GCShortText; + $self->{ean}->signal_connect('activate' => sub {$self->response('ok')} ); + if (!$self->{network}) + { + $table->attach($eanLabel, 0, 1, 2, 3, 'fill', 'fill', 0, 0); + $table->attach($self->{ean}, 1, 2, 2, 3, ['fill', 'expand'], 'fill', 0, 0); + } + $self->vbox->pack_start($table, 1, 1, 0); + $table->show_all; + $self->setCancelLabel($lang->{Terminate}); + $self->action_area->remove(($self->action_area->get_children)[$self->{okPosition}]); + return $self; + } + + sub setPrevious + { + my ($self, $previous) = @_; + if (!$self->{first}) + { + $self->{first} = 1; + return; + } + my $label; + if ($previous) + { + ($label = $self->{lang}->{Previous}) =~ s|%s|$previous|; + } + else + { + my $previous = $self->{previousCode}; + ($label = $self->{lang}->{NothingFound}) =~ s|%s|$previous|; + } + $self->{previousLabel}->set_markup($label); + $self->{promptLabel}->set_label($self->{lang}->{ScanOtherPrompt}); + } + + sub readSocket + { + my ($self) = @_; + Glib::Source->remove($self->{socketWatch}); + my $socket = $self->{socket}; + my $line = <$socket>; + $self->response('cancel') if !$line; + my $xs = XML::Simple->new; + my $scan = $xs->XMLin($line); + my $code = $scan->{scan}->{content}; + $code = $self->eanToIsbn($code) + if $self->{model} eq 'GCbooks'; + $self->{ean}->setValue($code); + $self->{previousCode} = $code; + $self->response('ok'); + } + + sub waitForCode + { + my $self = shift; + $self->{socketWatch} = Glib::IO->add_watch($self->{socket}->fileno, + 'in', + sub { + $self->readSocket; + }); + } + + sub eanToIsbn + { + my ($self, $code) = @_; + return $code if $code !~ /978(\d{9})/; + my $sub = $1; + my $multiplier = 1; + my $checkSum = 0; + foreach (split(//, $sub)) + { + $checkSum += $_ * $multiplier++; + } + $checkSum %= 11; + $checkSum = 'X' if $checkSum == 10; + return $sub.$checkSum; + } + + sub show + { + my $self = shift; + $self->SUPER::show(); + $self->show_all; + $self->showMe; + if ($self->{network}) + { + if (!$self->{accepted}) + { + $self->{serverWatch} = Glib::IO->add_watch($self->{serverSocket}->fileno, + 'in', + sub { + $self->{socket} = $self->{serverSocket}->accept; + $self->{accepted} = 1; + $self->waitForCode; + }); + } + else + { + $self->waitForCode; + } + } + else + { + $self->{ean}->setValue(''); + $self->{ean}->grab_focus; + } + my $code = $self->run; + $self->hide; + return $self->{ean}->getValue if $code eq 'ok'; + $self->{socket}->close; + return undef; + } +} + +{ + package GCImport::GCImporterScanner; + + use base qw(GCImport::GCImportBaseClass); + + use IO::Socket; + use GCPlugins; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + + bless ($self, $class); + return $self; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub wantsFileSelection + { + return 0; + } + + sub hideFileSelection + { + return 1; + } + + sub getFilePatterns + { + return (); + } + + sub checkPortField + { + my ($self, $data) = @_; + my ($parent, $list) = @{$data}; + my $model = $list->getValue ; + $parent->{options}->{port}->set_sensitive($model eq 'Network'); + } + + sub getOptions + { + my $self = shift; + + my $pluginsList = ''; + foreach (@{$self->{model}->getPluginsNames}) + { + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$_}; + $pluginsList .= $plugin->getName . ',' + if $plugin->getEanField; + } + + + return [ + { + name => 'type', + type => 'options', + label => 'Type', + valuesList => 'Local,Network', + default => 'Local', + changedCallback => sub {shift; $self->checkPortField(@_)}, + }, + + { + name => 'port', + type => 'number', + label => 'Port', + default => 50007, + min => 1024, + max => 65536, + }, + + { + name => 'plugin', + type => 'options', + label => 'Plugin', + valuesList => $pluginsList + }, + + { + name => 'first', + type => 'yesno', + label => 'UseFirst', + default => '1' + }, + ]; + } + + sub getModelName + { + my $self = shift; + return $self->{model}->getName; + } + + sub getBarCode + { + my ($self, $previous) = @_; + #my $dialog = new + $self->{dialog}->setPrevious($previous); + return $self->{dialog}->show; + } + + sub getItemsArray + { + my ($self, $file) = @_; + my @result; + + #First we try to get the correct plugin + my $plugin = $GCPlugins::pluginsMap{$self->{model}->getName}->{$self->{options}->{plugin}}; + $plugin->{bigPics} = $self->{options}->{parent}->{options}->bigPics; + + my $titleField = $self->{model}->{commonFields}->{title}; + my $searchField = $plugin->getEanField; + + my $i = 0; + + my $resultsDialog; + if (!$self->{options}->{first}) + { + $resultsDialog = $self->{options}->{parent}->getDialog('Results'); + $resultsDialog->setModel($self->{model}, $self->{model}->{fieldsInfo}); + $resultsDialog->setMultipleSelection(0); + } + my $search; + + my $socket; + if ($self->{options}->{type} eq 'Network') + { + $socket = new IO::Socket::INET( + LocalPort => $self->{options}->{port}, + Proto => 'tcp', + Listen => 1, + Reuse => 1 + ); + } + + $self->{dialog} = new GCScannerDialog($self->{options}->{parent}, + $self->getLang, + $self->{model}->getName, + $socket); + my $previous = ''; + while ($search = $self->getBarCode($previous)) + { + chomp $search; + next if ! $search; + # $_ contains the title to search + $plugin->{title} = $search; + $plugin->{type} = 'load'; + $plugin->{urlField} = $self->{model}->{commonFields}->{url}; + $plugin->{searchField} = $searchField; + #Initialize what will be pushed in the array + my $info = {$searchField => $search}; + + $self->{options}->{parent}->setWaitCursor($self->{options}->{lang}->{StatusSearch}.' ('.$search.')'); + $plugin->load; + + my $itemNumber = $plugin->getItemsNumber; + + if ($itemNumber != 0) + { + $plugin->{type} = 'info'; + if (($itemNumber == 1) || ($self->{options}->{first})) + { + $plugin->{wantedIdx} = 0; + } + else + { + my $withNext = 0; + my @items = $plugin->getItems; + $resultsDialog->setWithNext(0); + $resultsDialog->setSearchPlugin($plugin); + $resultsDialog->setList($search); + $resultsDialog->show; + if ($resultsDialog->{validated}) + { + $plugin->{wantedIdx} = $resultsDialog->getItemsIndexes->[0]; + } + } + $info = $plugin->getItemInfo; + my $title = $info->{$titleField}; + $self->{options}->{parent}->{defaultPictureSuffix} = $plugin->getDefaultPictureSuffix; + foreach my $field(@{$self->{model}->{managedImages}}) + { + $info->{$field} = '' if $info->{$field} eq 'empty'; + next if !$info->{$field}; + ($info->{$field}) = $self->{options}->{parent}->downloadPicture($info->{$field}, $title); + } + + # Add the default value + my $defaultInfo = $self->{model}->getDefaultValues; + foreach my $field(keys %$defaultInfo) + { + next if exists $info->{$field}; + $info->{$field} = $defaultInfo->{$field}; + } + } + $previous = $info->{$titleField}; + push @result, $info; + $self->{options}->{parent}->restoreCursor; + } + $socket->close if $socket; + return \@result; + } + + + sub getEndInfo + { + my $self = shift; + my $message; + return $message; + } +} + + +1; diff --git a/lib/gcstar/GCImport/GCImportTarGz.pm b/lib/gcstar/GCImport/GCImportTarGz.pm new file mode 100644 index 0000000..b995b82 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportTarGz.pm @@ -0,0 +1,152 @@ +package GCImport::GCImportTarGz; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterTarGz; + + use base qw(GCImport::GCImportBaseClass); + + use GCBackend::GCBackendXmlParser; + + use File::Spec; + use File::Temp qw/ tempfile tempdir /; + use Cwd; + use File::Copy; + + #use GCData; + + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + + $self->checkModule('Compress::Zlib'); + $self->checkModule('Archive::Tar'); + $self->checkModule('File::Path'); + + return $self; + } + + sub getName + { + return ".tar.gz"; + } + + sub getFilePatterns + { + return (['Tar gzip (.tar.gz)', '*.tar.gz']); + } + + sub getModelName + { + my $self = shift; + + return $self->{model}->getName; + } + + sub getOptions + { + my $self = shift; + my @options; + return \@options; + } + + # Ignored for the moment + sub wantsFieldsSelection + { + return 0; + } + sub getEndInfo + { + my $self = shift; + return ($self->{parsingError}, 'error'); + } + + sub addFieldsToDefaultModel + { + my ($self, $inlineModel) = @_; + my $model = GCModelLoader->newFromInline($self, {inlineModel => $inlineModel, defaultModifier => 1}); + $self->{model}->addFields($model); + $self->{options}->{parent}->setCurrentModel($self->{model}); + $self->{modelAlreadySet} = 1; + } + + sub getItemsArray + { + my ($self, $file) = @_; + + my ($tarFh, $tarFilename) = tempfile(); + my $gz = Compress::Zlib::gzopen($file, "rb"); + my $buffer; + print $tarFh $buffer while $gz->gzread($buffer) > 0 ; + close $tarFh; + $gz->gzclose; + + my $tmpDir = tempdir(); + my $oldCwd = getcwd; + chdir $tmpDir; + my $tar = Archive::Tar->new($tarFilename); + $tar->extract; + my $listFile = './collection.gcs'; + + my $parent = $self->{options}->{parent}; + $self->{modelsFactory} = $parent->{modelsFactory}; + $self->{modelAlreadySet} = 0; + + my $backend = new GCBackend::GCBeXmlParser($self); + $backend->setParameters(file => $listFile); + my $loaded = $backend->load(0); + my $itemsArray = []; + if ($loaded->{error}) + { + $self->{parsingError} = GCUtils::formatOpenSaveError( + $parent->{lang}, + $file, + $loaded->{error} + ); + } + else + { + $itemsArray = $loaded->{data}; + + #Copying pictures + $self->copyPictures($itemsArray, $file); + } + + #Cleaning + chdir $oldCwd; + File::Path::rmtree($tmpDir); + unlink $tarFilename; + + return $itemsArray; + } +} + +1; diff --git a/lib/gcstar/GCImport/GCImportTellico.pm b/lib/gcstar/GCImport/GCImportTellico.pm new file mode 100644 index 0000000..033b474 --- /dev/null +++ b/lib/gcstar/GCImport/GCImportTellico.pm @@ -0,0 +1,496 @@ +package GCImport::GCImportTellico; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use GCImport::GCImportBase; + +{ + package GCImport::GCImporterTellico; + + use base qw(GCImport::GCImportBaseClass); + use File::Spec; + + sub new + { + my $proto = shift; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(); + bless ($self, $class); + + $self->checkModule('Archive::Zip'); + $self->checkModule('MIME::Base64'); + + # Associate a Tellico type to a GCstar model + $self->{models} = { + 2 => 'GCbooks', + 3 => 'GCfilms', + 4 => 'GCmusics', + 8 => 'GCcoins', + 11 => 'GCgames' + }; + + return $self; + } + + sub getName + { + return "Tellico (.tc)"; + } + + sub getOptions + { + my $self = shift; + my @options; + return \@options; + } + + sub getFilePatterns + { + my $self = shift; + + return (['Tellico Format (.tc)', '*.tc'], ['Tellico XML (.xml)', '*.xml']); + } + + #Return supported models name + sub getModels + { + my $self = shift; + my @models = values %{$self->{models}}; + return \@models; + } + + sub getModelName + { + my $self = shift; + + return $self->{extractedModel}; + } + + sub wantsFieldsSelection + { + return 0; + } + + sub getEndInfo + { + my $self = shift; + + return $self->{parsingError}; + } + + sub getItemsArray + { + my ($self, $file) = @_; + + my @result = (); + + my $xml; + + # File type is based on suffix + # T is for Tellico (zipped file) + # X is for XML + $self->{type} = ($file =~ m/tc$/) ? 'T' : 'X'; + #Then we test to be sure + eval + { + $self->{zip} = Archive::Zip->new($file); + }; + #First we uncompress file + if (($self->{type} eq 'T') && ($self->{zip})) + { + $xml = $self->{zip}->contents('tellico.xml'); + } + else + { + $self->{type} = 'X'; + open XML, $file; + $xml = do {local $/; }; + close XML; + } + + #Then we parse XML data + my $xs = XML::Simple->new; + my $tellico = $xs->XMLin($xml, + SuppressEmpty => '', + ForceArray => 1); + my $collection = $tellico->{collection}->[0]; + + $self->{extractedModel} = $self->{models}->{$collection->{type}}; + #We check we know this model + if (! $self->{extractedModel}) + { + $self->{parsingError} = $self->getLang->{NotSupported}; + return \@result; + } + + my %tmpMap; + # If there are no ids, we have an array in $collection + if (ref ($collection->{entry}) eq 'ARRAY') + { + my $i = 0; + #Then we prepare a map + foreach (@{$collection->{entry}}) + { + $tmpMap{$i} = $_; + $i++; + } + } + else + { + %tmpMap = %{$collection->{entry}}; + } + #Loop on entries + my $i = 0; + + my $methodName = 'get'.$self->{extractedModel}.'Item'; + + while (my ($id, $entry) = each (%tmpMap)) + { + $result[$i] = $self->$methodName($entry, $collection); + $i++; + } + return \@result; + + } + + sub getGCfilmsItem + { + my ($self, $entry, $collection) = @_; + + my %result; + + $result{title} = $entry->{title}->[0]; + $result{format} = $entry->{medium}->[0]; + $result{date} = $entry->{year}->[0]; + my $certification = $entry->{certification}->[0]; + if ($certification eq 'U (USA)') + { + $result{age} = 1; + } + elsif ($certification eq 'G (USA)') + { + $result{age} = 2; + } + elsif ($certification eq 'PG (USA)') + { + $result{age} = 5; + } + elsif ($certification eq 'PG-13 (USA)') + { + $result{age} = 13; + } + elsif ($certification eq 'R (USA)') + { + $result{age} = 17; + } + $result{genre} = []; + if ($entry->{genres}->[0]) + { + for my $genre(@{$entry->{genres}->[0]->{genre}}) + { + push @{$result{genre}}, [$genre]; + } + } + if ($entry->{nationalitys}->[0]) + { + for my $country(@{$entry->{nationalitys}->[0]->{nationality}}) + { + $result{country} .= $country.', '; + } + } + $result{country} =~ s/, $//; + + $result{video} = $entry->{format}->[0]; + if ($entry->{casts}->[0]) + { + for my $cast(@{$entry->{casts}->[0]->{cast}}) + { + $result{actors} .= $cast->{column}->[0]; + $result{actors} .= ' ('.$cast->{column}->[1].')' if $cast->{column}->[1]; + $result{actors} .= ', '; + } + } + $result{actors} =~ s/, $//; + + if ($entry->{directors}->[0]) + { + for my $director(@{$entry->{directors}->[0]->{director}}) + { + $result{director} .= $director.', '; + } + } + $result{director} =~ s/, $//; + + $result{audio} = []; + if ($entry->{languages}->[0]) + { + for my $language(@{$entry->{languages}->[0]->{language}}) + { + push @{$result{audio}}, [$language]; + } + } + $result{subt} = []; + if ($entry->{subtitles}->[0]) + { + for my $subtitle(@{$entry->{subtitles}->[0]->{subtitle}}) + { + push @{$result{subt}}, [$subtitle]; + } + } + $result{time} = $entry->{'running-time'}->[0]; + $result{synopsis} = $entry->{plot}->[0]; + $result{synopsis} =~ s{(<|<)br/>}{\n}g; + + $result{rating} = $self->convertRating($entry->{rating}->[0]); + #$result{borrower} = 'none' if (! $entry->{loaned}); + $result{borrower} = 'Unknown' if ($entry->{loaned}->[0] eq 'true'); + $result{comment} = $entry->{comments}->[0]; + + #Picture management + $result{image} = $self->getPicture($collection, $entry->{cover}->[0], $result{title}); + + return \%result; + } + + sub getGCgamesItem + { + my ($self, $entry, $collection) = @_; + + my %result; + + $result{name} = $entry->{title}->[0]; + $result{platform} = $entry->{platform}->[0]; + $result{released} = $entry->{year}->[0]; + $result{genre} = []; + if ($entry->{genres}->[0]) + { + for my $genre(@{$entry->{genres}->[0]->{genre}}) + { + push @{$result{genre}}, [$genre]; + } + } + if ($entry->{publishers}->[0]) + { + for my $editor(@{$entry->{publishers}->[0]->{publisher}}) + { + $result{editor} .= $editor.', '; + } + $result{editor} =~ s/, $//; + } + if ($entry->{developers}->[0]) + { + for my $developer(@{$entry->{developers}->[0]->{developer}}) + { + $result{developer} .= $developer.', '; + } + $result{developer} =~ s/, $//; + } + $result{description} = $entry->{description}->[0]; + $result{rating} = $self->convertRating($entry->{rating}->[0]); + $result{completion} = 100 if $entry->{completed}->[0] eq 'true'; + $result{borrower} = 'Unknown' if ($entry->{loaned}->[0] eq 'true'); + $result{boxpic} = $self->getPicture($collection, $entry->{cover}->[0], $result{name}); + return \%result; + } + + sub getGCbooksItem + { + my ($self, $entry, $collection) = @_; + + my %result; + + $result{title} = $entry->{title}->[0]; + $result{isbn} = $entry->{isbn}->[0]; + $result{authors} = []; + if ($entry->{authors}->[0]) + { + for my $author(@{$entry->{authors}->[0]->{author}}) + { + push @{$result{authors}}, [$author]; + } + } + $result{publisher} = $entry->{publisher}->[0]; + $result{publication} = $entry->{pub_year}->[0]; + if ($entry->{languages}->[0]) + { + for my $language(@{$entry->{languages}->[0]->{language}}) + { + $result{language} .= $language.', '; + } + $result{language} =~ s/, $//; + } + $result{serie} = $entry->{series}->[0]; + $result{rank} = $entry->{series_num}->[0]; + $result{edition} = $entry->{edition}->[0]; + $result{format} = $entry->{binding}->[0]; + $result{description} = $entry->{comments}->[0]; + $result{pages} = $entry->{pages}->[0]; + $result{read} = 1 if ($entry->{read}->[0] eq 'true'); + $result{acquisition} = $entry->{pur_date}->[0]; + $result{genre} = []; + if ($entry->{genres}->[0]) + { + for my $genre(@{$entry->{genres}->[0]->{genre}}) + { + push @{$result{genre}}, [$genre]; + } + } + $result{rating} = $self->convertRating($entry->{rating}->[0]); + $result{borrower} = 'Unknown' if ($entry->{loaned}->[0] eq 'true'); + $result{cover} = $self->getPicture($collection, $entry->{cover}->[0], $result{title}); + return \%result; + } + + sub getGCmusicsItem + { + my ($self, $entry, $collection) = @_; + + my %result; + + $result{title} = $entry->{title}->[0]; + $result{format} = $entry->{medium}->[0]; + if ($entry->{artists}->[0]) + { + for my $artist(@{$entry->{artists}->[0]->{artist}}) + { + $result{artist} .= $artist.', '; + } + $result{artist} =~ s/, $//; + } + if ($entry->{labels}->[0]) + { + for my $label(@{$entry->{labels}->[0]->{label}}) + { + $result{label} .= $label.', '; + } + $result{label} =~ s/, $//; + } + $result{release} = $entry->{year}->[0]; + $result{genre} = []; + if ($entry->{genres}->[0]) + { + for my $genre(@{$entry->{genres}->[0]->{genre}}) + { + push @{$result{genre}}, [$genre]; + } + } + if ($entry->{tracks}->[0]) + { + my $trackNum = 1; + for my $track(@{$entry->{tracks}->[0]->{track}}) + { + push @{$result{tracks}}, [$trackNum, + $track->{column}->[0], + $track->{column}->[2]]; + $trackNum++; + } + } + $result{comment} = $entry->{comments}->[0]; + $result{rating} = $self->convertRating($entry->{rating}->[0]); + $result{borrower} = 'Unknown' if ($entry->{loaned}->[0] eq 'true'); + $result{cover} = $self->getPicture($collection, $entry->{cover}->[0], $result{title}); + return \%result; + } + + sub getGCcoinsItem + { + my ($self, $entry, $collection) = @_; + + my $i = 0; + my %result; + + #$result{name} = $entry->{title}->[0]; + + $result{currency} = $entry->{type}->[0]; + $result{value} = $entry->{denomination}->[0]; + $result{year} = $entry->{years}->[0]->{year}->[0]; + $result{country} = $entry->{country}->[0]; + $result{type} = ($entry->{set}->[0] eq 'true') ? 'coin' : 'banknote'; + # TODO: Import grade + $result{added} = $entry->{pur_date}->[0]; + $result{estimate} = $entry->{pur_price}->[0]; + $result{location} = $entry->{location}->[0]; + + $result{comments} = $entry->{comments}->[0]; + + $result{name} = $result{currency}.' '.$result{value}.' ('.$result{year}.')'; + + $result{picture} = $self->getPicture($collection, $entry->{obverse}->[0], $result{name}); + $result{front} = $self->getPicture($collection, $entry->{obverse}->[0], $result{name}.'_front'); + $result{back} = $self->getPicture($collection, $entry->{reverse}->[0], $result{name}.'_back'); + return \%result; + } + + sub getPicture + { + my ($self, $collection, $imageId, $title) = @_; + + my $result = undef; + if ($imageId && (ref($imageId) ne 'HASH')) + { + (my $suffix = $imageId) =~ s/.*?(\.[^.]*)$/$1/; + my $fileName = $self->{options}->{parent}->getUniqueImageFileName($suffix, $title); + if ((exists $collection->{images}->[0]->{image}->{$imageId}) && + (exists $collection->{images}->[0]->{image}->{$imageId}->{content})) + { + # Picture is embedded + my $data = MIME::Base64::decode_base64($collection->{images}->[0]->{image}->{$imageId}->{content}); + open PIC, ">$fileName"; + print PIC $data; + close PIC; + } + else + { + if ($self->{type} eq 'T') + { + # Only zipped file may have external pictures + my $picName = 'images/'.$imageId; + $self->{zip}->extractMember($picName, $fileName); + } + else + { + $fileName = ''; + } + } + $result = $self->{options}->{parent}->transformPicturePath($fileName); + } + return $result; + } + + sub convertRating + { + my ($self, $rating) = @_; + return 10 if $rating =~ /^5/; + return 7 if $rating =~ /^4/; + return 3 if $rating =~ /^2/; + return 0 if $rating =~ /^1/; + return 5; #if ($rating =~ /^3/) || ($rating == undef); + } + +} + + + + +1; diff --git a/lib/gcstar/GCItemsLists/GCImageListComponents.pm b/lib/gcstar/GCItemsLists/GCImageListComponents.pm new file mode 100644 index 0000000..aa2bf2b --- /dev/null +++ b/lib/gcstar/GCItemsLists/GCImageListComponents.pm @@ -0,0 +1,848 @@ +package GCImageListComponents; + +################################################### +# +# Copyright 2005-2011 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; + +{ + package GCImageListItem; + + use GCUtils; + use GCStyle; + use base "Gtk2::EventBox"; + use File::Temp qw/ tempfile /; + + @GCImageListItem::ISA = ('Gtk2::EventBox'); + + sub new + { + my ($proto, $container, $info) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new; + bless ($self, $class); + + # Some information that we'll need later + $self->{info} = $info; + $self->{container} = $container; + $self->{style} = $container->{style}; + $self->{tooltips} = $container->{tooltips}; + $self->{file} = $container->{parent}->{options}->file; + $self->{collectionDir} = $container->{collectionDir}; + $self->{model} = $container->{parent}->{model}; + $self->{imageCache} = $container->{imageCache}; + $self->{dataManager} = $container->{parent}->{items}; + + $self->can_focus(1); + my $image = new Gtk2::Image; + $self->add($image); + $self->refreshInfo($info); + $self->set_size_request($container->{style}->{vboxWidth}, $container->{style}->{vboxHeight}); + $self->show_all; + + return $self; + } + + sub setInfo + { + my ($self, $info) = @_; + + $self->{info} = $info; + } + + sub refreshInfo + { + my ($self, $info, $cacheRefresh) = @_; + + $self->setInfo($info); + + $self->refreshPopup; + + delete $self->{zoomedPixbufCache}; + + { + my $pixbuf = $self->createPixbuf($info, $cacheRefresh); + if (! $self->{style}->{withImage}) + { + $self->modify_bg('normal', $self->{style}->{inactiveBg}); + } + $self->{previousPixbuf} = $pixbuf->copy; + $self->child->set_from_pixbuf($pixbuf); + } + if ($self->{selected}) + { + $self->{selected} = 0; + $self->highlight; + $self->{selected} = 1; + } + } + + sub refreshPopup + { + my $self = shift; + # Old versions of Gtk2 don't support set_tooltip_markup + eval { + $self->set_tooltip_markup($self->{dataManager}->getSummary($self->{info}->{idx})); + }; + if ($@) + { + print "$@\n"; + # So we do it the old way for them + $self->{tooltips}->set_tip($self, $self->{info}->{title}, ''); + } + } + + sub savePicture + { + my $self = shift; + $self->{previousPixbuf} = $self->child->get_pixbuf->copy + if $self->child; + } + + sub restorePicture + { + my $self = shift; + $self->child->set_from_pixbuf($self->{previousPixbuf}) + if $self->{previousPixbuf} && $self->child; + } + + sub startZoomAnimation + { + my $self = shift; + $self->{currentZoom} = 1.01; + my $pixbuf = $self->createPixbuf($self->{info}, 0, 1.01); + $self->child->set_from_pixbuf($pixbuf); + $self->{zoomTimeout} = Glib::Timeout->add(20 , sub { + my $widget = shift; + $widget->{currentZoom} += 0.02; + if ($widget->{currentZoom} > 1.06) + { + $widget->{zoomTimeout} = undef; + return 0; + } + my $pixbuf = $widget->createPixbuf($self->{info}, 0, $widget->{currentZoom}); + $widget->child->set_from_pixbuf($pixbuf) + if $widget->child; + return 1; + }, $self); + } + + sub stopZoomAnimation + { + my $self = shift; + Glib::Source->remove($self->{zoomTimeout}) + if $self->{zoomTimeout}; + } + + # This method sets all the event callbacks + sub prepareHandlers + { + my ($self, $idx, $info) = @_; + $self->{idx} = $idx; + $self->{info} = $info; + + $self->signal_handler_disconnect($self->{mouseHandler}) + if $self->{mouseHandler}; + $self->{mouseHandler} = $self->signal_connect('button_press_event' => sub { + my ($widget, $event) = @_; + + if (($event->type ne '2button-press') && !(($event->button eq 3) && ($widget->{selected}))) + { + my $state = $event->get_state; + my $keepPrevious = 0; + if ($state =~ /control-mask/) + { + $widget->{container}->select($widget->{idx}, 0, 1); + } + elsif ($state =~ /shift-mask/) + { + $widget->{container}->restorePrevious; + $widget->{container}->selectMany($widget->{idx}); + } + else + { + $widget->{container}->select($widget->{idx}); + } + $widget->{container}->setPreviousSelectedDisplayed($widget->{idx}); + + #$self->{parent}->display($widget->{idx}) unless $event->type eq '2button-press'; + $widget->{container}->displayDetails(0, keys %{$widget->{container}->{selectedIndexes}}); + } + + $widget->{container}->displayDetails(1, $widget->{idx}) if $event->type eq '2button-press'; + $widget->{container}->showPopupMenu($event->button, $event->time) if ($event->button eq 3); + $widget->grab_focus; + }); + + if ($self->{style}->{withAnimation}) + { + $self->signal_handler_disconnect($self->{enterHandler}) + if $self->{enterHandler}; + $self->{enterHandler} = $self->signal_connect('enter_notify_event' => sub { + my ($widget, $event) = @_; + if (!$widget->{selected}) + { + $widget->startZoomAnimation; + } + }); + + $self->signal_handler_disconnect($self->{leaveHandler}) + if $self->{leaveHandler}; + $self->{leaveHandler} = $self->signal_connect('leave_notify_event' => sub { + my ($widget, $event) = @_; + if (!$widget->{selected}) + { + $widget->stopZoomAnimation; + $widget->restorePicture; + } + }); + } + + + $self->signal_handler_disconnect($self->{keyHandler}) + if $self->{keyHandler}; + + $self->{keyHandler} = $self->signal_connect('key-press-event' => sub { + my ($widget, $event) = @_; + my $displayed = $self->{container}->convertIdxToDisplayed($widget->{idx}); + my $key = Gtk2::Gdk->keyval_name($event->keyval); + if ($key eq 'Delete') + { + $widget->{container}->{parent}->deleteCurrentItem; + return 1; + } + if (($key eq 'Return') || ($key eq 'space')) + { + $widget->{container}->displayDetails(1, $widget->{idx}); + return 1; + } + my $unicode = Gtk2::Gdk->keyval_to_unicode($event->keyval); + if ($unicode) + { + $self->{container}->showSearch(pack('U',$unicode)); + } + else + { + my $columns = $widget->{container}->getColumnsNumber; + + ($key eq 'Right') ? $displayed++ : + ($key eq 'Left') ? $displayed-- : + ($key eq 'Down') ? $displayed += $columns : + ($key eq 'Up') ? $displayed -= $columns : + ($key eq 'Page_Down') ? $displayed += ($widget->{style}->{pageCount} * $columns): + ($key eq 'Page_Up') ? $displayed -= ($widget->{style}->{pageCount} * $columns): + ($key eq 'Home') ? $displayed = 0 : + ($key eq 'End') ? $displayed = $widget->{container}->getNbItems - 1 : + return 1; + + return 1 if ($displayed < 0) || ($displayed >= $widget->{container}->getNbItems); + my $column = $displayed % $columns; + my $valueIdx = $widget->{container}->convertDisplayedToIdx($displayed); +# my $keepPrevious = 0; + my $state = $event->get_state; + if ($state =~ /control-mask/) + { + $widget->{container}->select($valueIdx, 0, 1); + $widget->{container}->unsetPreviousSelectedDisplayed; + } + elsif ($state =~ /shift-mask/) + { + $widget->{container}->setPreviousSelectedDisplayed($widget->{idx}); + $widget->{container}->restorePrevious; + $widget->{container}->selectMany($valueIdx); + } + else + { + $widget->{container}->select($valueIdx); + $widget->{container}->unsetPreviousSelectedDisplayed; + } + $widget->{container}->displayDetails(0, $valueIdx); + $widget->{container}->grab_focus; + $widget->{container}->showCurrent unless (($key eq 'Left') && ($column != ($columns - 1))) + || (($key eq 'Right') && ($column != 0)); + } + return 1; + + }); + + } + + sub highlight + { + my ($self, $keepPrevious) = @_; + return if $self->{selected}; + $self->{selected} = 1; + if (! $self->{style}->{withImage}) + { + $self->modify_bg('normal', $self->{style}->{activeBg}); + } +# $self->savePicture +# unless $keepPrevious; + + my $pixbuf = $self->createPixbuf($self->{info}, 0, 1.1); + + $pixbuf->saturate_and_pixelate($pixbuf, 1.5, 0); + $pixbuf = $pixbuf->composite_color_simple ($pixbuf->get_width, $pixbuf->get_height, 'nearest',220, 128, $self->{style}->{activeBgValue}, $self->{style}->{activeBgValue}); + $self->child->set_from_pixbuf($pixbuf); + } + + sub unhighlight + { + my ($self) = @_; + + $self->modify_bg('normal', $self->{style}->{inactiveBg}) + if (! $self->{style}->{withImage}); + $self->restorePicture; + $self->{selected} = 0; + } + + sub createPixbuf + { + my ($self, $info, $cacheRefresh, $zoom) = @_; + + my $displayedImage = $info->{picture}; + my $pixbuf = undef; + + my $borrower = $info->{borrower}; + my $favourite = $info->{favourite}; + + # Item has a picture assigned + if ($cacheRefresh) + { + $self->{imageCache}->forceCacheUpdateForNextUse; + } + + if ($zoom) + { + if (! exists $self->{zoomedPixbufCache}->{$zoom}) + { + $self->{zoomedPixbufCache}->{$zoom} = $self->{imageCache}->getPixbuf($info, $zoom); + } + $pixbuf = $self->{zoomedPixbufCache}->{$zoom}; + } + else + { + $pixbuf = $self->{imageCache}->getPixbuf($info, $zoom); + } + + my $width; + my $height; + my $boxWidth = $self->{style}->{imgWidth}; + my $boxHeight = $self->{style}->{imgHeight}; + + my $overlay; + my $imgWidth; + my $imgHeight; + my $targetOverlayHeight; + my $targetOverlayWidth; + my $pixbufTempHeight; + my $pixbufTempWidth; + my $alpha = 1; + if ($self->{style}->{useOverlays}) + { + # Need to call this to get the overlay padding + ($imgWidth, $imgHeight, $overlay) = $self->{imageCache}->getDestinationImgSize($pixbuf->get_width, + $pixbuf->get_height); + } + $width = $pixbuf->get_width; + $height = $pixbuf->get_height; + + # Do the composition + + if ($self->{style}->{useOverlays}) + { + if ($self->{style}->{withImage}) + { + # Using background, so center accordingly + my $offsetX = (($self->{style}->{offsetX} / 2) * $self->{style}->{factor}) + (($boxWidth - ($width + $overlay->{paddingLeft} + $overlay->{paddingRight})) / 2); + my $offsetY = 15 * $self->{style}->{factor} + ($boxHeight - ($height + $overlay->{paddingTop} + $overlay->{paddingBottom})); + + # Make an empty pixbuf to work within + my $tempPixbuf =Gtk2::Gdk::Pixbuf->new('rgb', 1, 8, + $self->{style}->{backgroundPixbuf}->get_width, + $self->{style}->{backgroundPixbuf}->get_height); + $tempPixbuf->fill(0x00000000); + + # Place cover in pixbuf + $pixbuf->composite($tempPixbuf, + $offsetX + $overlay->{paddingLeft}, $offsetY + $overlay->{paddingTop}, + $width , $height, + $offsetX + $overlay->{paddingLeft}, $offsetY + $overlay->{paddingTop}, + 1, 1, + 'nearest', 255); + $pixbuf = $tempPixbuf; + + # Composite overlay picture + $self->{style}->{overlayPixbuf}->composite($pixbuf, + $offsetX, $offsetY, + $width + $overlay->{paddingLeft} + $overlay->{paddingRight}, + $height + $overlay->{paddingTop} + $overlay->{paddingBottom}, + $offsetX, $offsetY, + ($width + $overlay->{paddingLeft} + $overlay->{paddingRight}) / $self->{style}->{overlayPixbuf}->get_width, + ($height + $overlay->{paddingTop} + $overlay->{paddingBottom}) / $self->{style}->{overlayPixbuf}->get_height, + 'nearest', 255); + + # Overlay borrower image if required + if ($borrower && ($borrower ne 'none')) + { + # De-saturate borrowed items + $pixbuf->saturate_and_pixelate($pixbuf, .1, 0); + $self->{style}->{lendPixbuf}->composite($pixbuf, + $pixbuf->get_width - $self->{style}->{lendPixbuf}->get_width - $offsetX, + $offsetY + $height + $overlay->{paddingTop} + $overlay->{paddingBottom} - $self->{style}->{lendPixbuf}->get_height, + $self->{style}->{lendPixbuf}->get_width, $self->{style}->{lendPixbuf}->get_height, + $pixbuf->get_width - $self->{style}->{lendPixbuf}->get_width - $offsetX, + $offsetY + $height + $overlay->{paddingTop} + $overlay->{paddingBottom} - $self->{style}->{lendPixbuf}->get_height, + 1, 1, + 'nearest', 255); + } + + # Overlay favourite image if required + if ($favourite) + { + $self->{style}->{favPixbuf}->composite($pixbuf, + $pixbuf->get_width - $self->{style}->{favPixbuf}->get_width - $offsetX, + $offsetY, + $self->{style}->{favPixbuf}->get_width, $self->{style}->{favPixbuf}->get_height, + $pixbuf->get_width - $self->{style}->{favPixbuf}->get_width - $offsetX, + $offsetY, + 1, 1, + 'nearest', 255); + } + + # Create and apply reflection if required + if ($self->{style}->{withReflect}) + { + my $reflect; + $reflect = $pixbuf->flip(0); + $reflect->composite( + $pixbuf, + 0, 2 * ($offsetY + $height + $overlay->{paddingTop} + $overlay->{paddingBottom}) - $pixbuf->get_height, + $pixbuf->get_width, + 2 * ($pixbuf->get_height - $height - $offsetY - $overlay->{paddingTop} - $overlay->{paddingBottom}) - (10 * $self->{style}->{factor}), + 0, 2 * ($offsetY + $height + $overlay->{paddingTop} + $overlay->{paddingBottom}) - $pixbuf->get_height, + 1, 1, + 'nearest', 100 + ); + + # Apply foreground fading + $self->{style}->{foregroundPixbuf}->composite( + $pixbuf, + 0, 0, + $pixbuf->get_width, $pixbuf->get_height, + 0, 0, + 1, 1, + 'nearest', 255 + ); + } + + # Heft created pixbuf onto background + my $bgPixbuf = $self->{style}->{backgroundPixbuf}->copy; + $pixbuf->composite($bgPixbuf, + 0,0, + $pixbuf->get_width , $pixbuf->get_height, + 0,0, + 1, 1, + 'nearest', 255); + $pixbuf = $bgPixbuf; + + } + else + { + # Not using background, so we need to make an empty pixbuf which is right size for overlay first + my $tempPixbuf =Gtk2::Gdk::Pixbuf->new('rgb', 1, 8, + $width + $overlay->{paddingLeft} + $overlay->{paddingRight}, + $height + $overlay->{paddingTop} + $overlay->{paddingBottom}); + $tempPixbuf->fill(0x00000000); + + # Now, place list image inside empty pixbuf + $pixbuf->composite($tempPixbuf, + $overlay->{paddingLeft}, $overlay->{paddingTop}, + $width , $height, + $overlay->{paddingLeft}, $overlay->{paddingTop}, + 1, 1, + 'nearest', 255 * $alpha); + $pixbuf = $tempPixbuf; + + # Place overlay on top of pixbuf + $self->{style}->{overlayPixbuf}->composite($pixbuf, + 0, 0, + $width + $overlay->{paddingLeft} + $overlay->{paddingRight}, + $height + $overlay->{paddingTop} + $overlay->{paddingBottom}, + 0, 0, + ($width + $overlay->{paddingLeft} + $overlay->{paddingRight}) / $self->{style}->{overlayPixbuf}->get_width, + ($height + $overlay->{paddingTop} + $overlay->{paddingBottom}) / $self->{style}->{overlayPixbuf}->get_height, + 'nearest', 255 * $alpha); + + # Overlay borrower image if required + if ($borrower && ($borrower ne 'none')) + { + # De-saturate borrowed items + $pixbuf->saturate_and_pixelate($pixbuf, .1, 0); + + $self->{style}->{lendPixbuf}->composite($pixbuf, + $pixbuf->get_width - $self->{style}->{lendPixbuf}->get_width, + $pixbuf->get_height - $self->{style}->{lendPixbuf}->get_height, + $self->{style}->{lendPixbuf}->get_width, $self->{style}->{lendPixbuf}->get_height, + $pixbuf->get_width - $self->{style}->{lendPixbuf}->get_width, + $pixbuf->get_height - $self->{style}->{lendPixbuf}->get_height, + 1, 1, + 'nearest', 255); + } + + # Overlay favourite image if required + if ($favourite) + { + $self->{style}->{favPixbuf}->composite($pixbuf, + $pixbuf->get_width - $self->{style}->{favPixbuf}->get_width, + 0, + $self->{style}->{favPixbuf}->get_width, $self->{style}->{favPixbuf}->get_height, + $pixbuf->get_width - $self->{style}->{favPixbuf}->get_width, + 0, + 1, 1, + 'nearest', 255); + } + + } + } + else + { + # No overlays, nice and simple + + # Overlay borrower image if required + if ($borrower && ($borrower ne 'none')) + { + # De-saturate borrowed items + $pixbuf->saturate_and_pixelate($pixbuf, .1, 0); + $self->{style}->{lendPixbuf}->composite($pixbuf, + $width - $self->{style}->{lendPixbuf}->get_width - $self->{style}->{factor}, + $height - $self->{style}->{lendPixbuf}->get_height - $self->{style}->{factor}, + $self->{style}->{lendPixbuf}->get_width, $self->{style}->{lendPixbuf}->get_height, + $width - $self->{style}->{lendPixbuf}->get_width - $self->{style}->{factor}, + $height - $self->{style}->{lendPixbuf}->get_height - $self->{style}->{factor}, + 1, 1, + 'nearest', 255); + } + + # Overlay favourite image if required + if ($favourite) + { + $self->{style}->{favPixbuf}->composite($pixbuf, + $width - $self->{style}->{favPixbuf}->get_width - $self->{style}->{factor}, + $self->{style}->{factor}, + $self->{style}->{favPixbuf}->get_width, $self->{style}->{favPixbuf}->get_height, + $width - $self->{style}->{favPixbuf}->get_width - $self->{style}->{factor}, + $self->{style}->{factor}, + 1, 1, + 'nearest', 255); + } + + my $reflect; + $reflect = $pixbuf->flip(0) + if $self->{style}->{withReflect}; + + my $offsetX = (($self->{style}->{offsetX} / 2) * $self->{style}->{factor}) + (($boxWidth - $width) / 2); + my $offsetY = 15 * $self->{style}->{factor} + ($boxHeight - $height); + if ($self->{style}->{withImage}) + { + my $bgPixbuf = $self->{style}->{backgroundPixbuf}->copy; + $pixbuf->composite($bgPixbuf, + $offsetX, $offsetY, + $width, $height, + $offsetX, $offsetY, + 1, 1, + 'nearest', 255); + $pixbuf = $bgPixbuf; + } + + if ($self->{style}->{withReflect}) + { + $reflect->composite( + $pixbuf, + $offsetX, $height + $offsetY, + $width, $pixbuf->get_height - $height - $offsetY - (10 * $self->{style}->{factor}), + $offsetX, $height + $offsetY, + 1, 1, + 'nearest', 100 + ); + + # Apply foreground fading + $self->{style}->{foregroundPixbuf}->composite( + $pixbuf, + 0, 0, + $pixbuf->get_width, $pixbuf->get_height, + 0, 0, + 1, 1, + 'nearest', 255 + ); + } + } + return $pixbuf; + } + + + +} + +{ + package GCImageCache; + + use File::Path; + use File::Copy; + use List::Util qw/min/; + + sub new + { + my ($proto, $imagesDir, $imageSize, $style, $defaultImage) = @_; + my $class = ref($proto) || $proto; + my $self = { + imagesDir => $imagesDir, + imageSize => $imageSize, + style => $style, + cacheDir => $imagesDir.'/.cache/', + oldCacheDir => $imagesDir, + defaultImage => $defaultImage, + forceUpdate => 0, + }; + # Make sure destination directory exists + if ( ! -d $self->{cacheDir}) + { + mkpath $self->{cacheDir}; + } + bless ($self, $class); + + $self->clearOldCache; + + return $self; + } + + # This method removes images cached by previous versions + sub clearOldCache + { + my $self = shift; + my $trashDir = $self->{imagesDir}.'.trash'; + mkpath $trashDir; + foreach (glob $self->{oldCacheDir}.'/*') + { + if (/\.cache\.[0-4](\.|$)/) + { + move $_, $trashDir; + } + } + } + + sub forceCacheUpdateForNextUse + { + my ($self) = @_; + $self->{forceUpdate} = 1; + } + + sub getPixbuf + { + my ($self, $info, $zoom) = @_; + my $fileName; + my $pixbuf = undef; + if (!$zoom) + { + $fileName = $self->getCachedFileName($info); + if ($self->{forceUpdate} || (! -e $fileName)) + { + $self->createImageCache($info); + } + $self->{forceUpdate} = 0; + eval { + $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($fileName); + }; + } + else + { + # When a zoom is requested, we have to generate the picture + $fileName = $self->getCachedFileName($info); + # Get picture size from cached file to avoid re-computing everything + my ($picFormat, $picWidth, $picHeight) = Gtk2::Gdk::Pixbuf->get_file_info($fileName); + # Then open the original file + my $origFileName = $info->{picture}; + if (! -f $origFileName) + { + $origFileName = $self->{defaultImage}; + } + if (!$self->{style}->{useOverlays}) + { + $zoom -= 0.01; + } + + eval { + $pixbuf = Gtk2::Gdk::Pixbuf->new_from_file($origFileName); + my $newWidth = int($picWidth * $zoom); + my $newHeight = int($picHeight * $zoom); + $pixbuf = GCUtils::scaleMaxPixbuf($pixbuf, $newWidth, $newHeight, 1, 0); + }; + } + + return $pixbuf; + } + + sub getCachedFileName + { + my ($self, $info, $size) = @_; + + my $gcsautoid = $info->{autoid}; + my $title = $info->{title}; + + $title =~ s/[^a-zA-Z0-9]*//g; + my $cacheFilename = $self->{cacheDir}; + if ($info->{picture}) + { + $cacheFilename .= $gcsautoid + ."." + .$title; + } + else + { + $cacheFilename .= 'GCSDefaultImage'; + } + $cacheFilename .= (defined $size ? $size : $self->{imageSize}); + $cacheFilename .= ".overlay" + if $self->{style}->{useOverlays}; + + return $cacheFilename; + } + + # Resizes artwork to required sizes and saves copies of the images, for fast loading + sub createImageCache + { + my ($self, $info) = @_; + + my $srcImage = $info->{picture}; + if (! -f $srcImage) + { + $srcImage = $self->{defaultImage}; + $info->{picture} = ""; + } + + # Load in the original source image + my $origPixbuf = Gtk2::Gdk::Pixbuf->new_from_file($srcImage); + + my $gcsautoid = $info->{autoid}; + my $title = $info->{title}; + $title =~ s/[^a-zA-Z0-9]*//g; + # Get original picture format + my ($picFormat, $picWidth, $picHeight) = Gtk2::Gdk::Pixbuf->get_file_info($srcImage); + + # Loop through possible sizes + for (my $size = 0; $size < 5; $size++) { + my $imgWidth; + my $imgHeight; + my $overlay; + + my $cacheFilename = $self->getCachedFileName($info, $size); + + # Get size for cached image + ($imgWidth, $imgHeight, $overlay) = $self->getDestinationImgSize($picWidth, + $picHeight, + $size); + + # Scale pixbuf and save + my $scaledPixbuf = GCUtils::scaleMaxPixbuf($origPixbuf, $imgWidth, $imgHeight, 0, 0); + if ($picFormat->{name} eq 'jpeg') + { + $scaledPixbuf->save ($cacheFilename, 'jpeg', quality => '99'); + } + else + { + $scaledPixbuf->save ($cacheFilename, 'png'); + } + } + } + + # Calculates height and width of list image + sub getDestinationImgSize + { + my ($self, $origWidth, $origHeight, $size) = @_; + + $size = $self->{imageSize} + if (!defined $size); + + my $imgWidth; + my $imgHeight; + my $overlay; + + # No overlays + $imgWidth = $self->{style}->{imgWidth} / $self->{style}->{factor}; + $imgHeight = $self->{style}->{imgHeight} / $self->{style}->{factor}; + + if ($self->{style}->{useOverlays}) + { + # Overlays + + # Calculate size of list image with proportional size of overlay padding added + my $pixbufTempHeight = (($self->{style}->{overlayPaddingTop} + $self->{style}->{overlayPaddingBottom})/$self->{style}->{overlayPixbuf}->get_height + 1) * $origHeight; + my $pixbufTempWidth = (($self->{style}->{overlayPaddingLeft} + $self->{style}->{overlayPaddingRight})/$self->{style}->{overlayPixbuf}->get_width + 1) * $origWidth; + + # Find out target size of overlay, keeping the same ratio as the size calculated above (ie, list image + relative padding) + my $ratio = $pixbufTempHeight / $pixbufTempWidth; + my $targetOverlayHeight; + my $targetOverlayWidth; + if (($pixbufTempWidth > $imgWidth) || ($pixbufTempHeight > $imgHeight)) + { + if (($pixbufTempWidth * $imgHeight/$pixbufTempHeight) < $imgHeight ) + { + $targetOverlayHeight = $imgHeight; + $targetOverlayWidth = int($imgHeight / $ratio); + } + else + { + $targetOverlayHeight = int( $imgWidth * $ratio); + $targetOverlayWidth = $imgWidth; + } + } + else + { + # Special case when image is small enough and doesn't need to be resized + $targetOverlayHeight = $pixbufTempHeight; + $targetOverlayWidth = $pixbufTempWidth; + } + + # Calculate final offset amounts for target size of overlay + $overlay->{paddingLeft} = int($self->{style}->{overlayPaddingLeft} * $targetOverlayWidth / $self->{style}->{overlayPixbuf}->get_width); + $overlay->{paddingRight} = int($self->{style}->{overlayPaddingRight} * $targetOverlayWidth / $self->{style}->{overlayPixbuf}->get_width); + $overlay->{paddingTop} = int($self->{style}->{overlayPaddingTop} * $targetOverlayHeight / $self->{style}->{overlayPixbuf}->get_height); + $overlay->{paddingBottom} = int($self->{style}->{overlayPaddingBottom} * $targetOverlayHeight / $self->{style}->{overlayPixbuf}->get_height); + + $imgWidth = $imgWidth - $overlay->{paddingLeft} - $overlay->{paddingRight}; + $imgHeight = $imgHeight - $overlay->{paddingTop} - $overlay->{paddingBottom}; + } + + my $factor = ($size == 0) ? 0.5 + : ($size == 1) ? 0.8 + : ($size == 3) ? 1.5 + : ($size == 4) ? 2 + : 1; + $imgWidth *= $factor; + $imgHeight *= $factor; + + return ($imgWidth, $imgHeight, $overlay); + } + +} + +1; diff --git a/lib/gcstar/GCItemsLists/GCImageLists.pm b/lib/gcstar/GCItemsLists/GCImageLists.pm new file mode 100644 index 0000000..2250bcb --- /dev/null +++ b/lib/gcstar/GCItemsLists/GCImageLists.pm @@ -0,0 +1,2028 @@ +package GCImageLists; + +################################################### +# +# Copyright 2005-2010 Christian Jodar +# +# This file is part of GCstar. +# +# GCstar is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GCstar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCstar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################### + +use strict; +use locale; + +# Number of ms to wait before enhancing the next picture +my $timeOutBetweenEnhancements = 50; + +{ + package GCBaseImageList; + + use File::Basename; + use GCItemsLists::GCImageListComponents; + use GCUtils; + use GCStyle; + use base "Gtk2::VBox"; + use File::Temp qw/ tempfile /; + + sub new + { + my ($proto, $container, $columns) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(0,0); + bless ($self, $class); + + my $parent = $container->{parent}; + + $self->{preferences} = $parent->{model}->{preferences}; + $self->{imagesDir} = $parent->getImagesDir(); + $self->{coverField} = $parent->{model}->{commonFields}->{cover}; + $self->{titleField} = $parent->{model}->{commonFields}->{title}; + $self->{idField} = $parent->{model}->{commonFields}->{id}; + $self->{borrowerField} = $parent->{model}->{commonFields}->{borrower}->{name}; + # Sort field + $self->{sortField} = $self->{preferences}->secondarySort + || $self->{titleField}; + $self->{fileIdx} = ""; + $self->{selectedIndexes} = {}; + $self->{previousSelectedDisplayed} = 0; + $self->{displayedToItemsArray} = {}; + $self->{container} = $container; + $self->{scroll} = $container->{scroll}; + $self->{searchEntry} = $container->{searchEntry}; + + + $self->{preferences}->sortOrder(1) + if ! $self->{preferences}->exists('sortOrder'); + + $self->{parent} = $container->{parent}; + + $self->{tooltips} = Gtk2::Tooltips->new(); + + $self->{columns} = $columns; + $self->{dynamicSize} = ($columns == 0); + $self->clearCache; + + + $self->set_border_width(0); + + $self->signal_connect('button_press_event' => sub { + my ($widget, $event) = @_; + if ($event->button eq 3) + { + $self->{parent}->{context}->popup(undef, undef, undef, undef, $event->button, $event->time) + } + }); + + $self->can_focus(1); + + $self->{imageCache} = new GCImageCache($self->{imagesDir}, + $self->{preferences}->listImgSize, + $container->{style}, + $self->{parent}->{defaultImage}); + + return $self; + } + + sub couldExpandAll + { + my $self = shift; + + return 0; + } + + sub getCurrentIdx + { + my $self = shift; + return $self->{displayedToIdx}->{$self->{current}}; + } + + sub getCurrentItems + { + my $self = shift; + my @indexes = keys %{$self->{selectedIndexes}}; + return \@indexes; + } + + sub isSelected + { + my ($self, $idx) = @_; + foreach (keys %{$self->{selectedIndexes}}) + { + return 1 if $_ == $idx; + } + return 0; + } + + sub DESTROY + { + my $self = shift; + + #unlink $self->{style}->{tmpBgPixmap}; + $self->SUPER::DESTROY; + } + + sub isUsingDate + { + my ($self) = @_; + return 0; + } + + sub setSortOrder + { + my ($self, $order) = @_; + $order = 0 if !defined $order; + $self->{currentOrder} = ($order == -1) ? (1 - $self->{currentOrder}) + : $self->{preferences}->sortOrder; + + if ($self->{itemsArray}) + { + if ($order == -1) + { + @{$self->{itemsArray}} = reverse @{$self->{itemsArray}}; + } + else + { + sub compare + { + return ( + GCUtils::gccmpe($a->{sortValue}, $b->{sortValue}) + ); + } + if ($self->{currentOrder} == 1) + { + @{$self->{itemsArray}} = sort compare @{$self->{itemsArray}}; + } + else + { + @{$self->{itemsArray}} = reverse sort compare @{$self->{itemsArray}}; + } + } + } + $self->refresh if ! $self->{initializing}; + $self->{initializing} = 0; + } + + sub setFilter + { + my ($self, $filter, $items, $refresh, $splash) = @_; + $self->{displayedNumber} = 0; + $self->{filter} = $filter; + $self->{displayedToItemsArray} = {}; + my $current = $self->{current}; + $self->restorePrevious; + my $i = 0; + foreach (@{$self->{itemsArray}}) + { + $_->{displayed} = $filter->test($items->[$_->{idx}]); + if ($_->{displayed}) + { + $self->{displayedToItemsArray}->{$self->{displayedNumber}} = $i; + $self->{displayedNumber}++; + } + $self->{container}->setDisplayed($_->{idx}, $_->{displayed}); + $i++; + } + my $newIdx = $self->getFirstVisibleIdx($current); + my $conversionNeeded = 0; + $conversionNeeded = 1 if ! exists $self->{boxes}->[$current]; + + if ($refresh) + { + $self->refresh(1, $splash); + $self->show_all; + } + + $self->{initializing} = 0; + return $self->displayedToItemsArrayIdx($newIdx) + if $conversionNeeded; + return $newIdx; + } + + sub getFirstVisibleIdx + { + my ($self, $displayed) = @_; + return $displayed if ! exists $self->{boxes}->[$displayed]; + my $currentIdx = $self->{boxes}->[$displayed]->{info}->{idx}; + my $info = $self->{boxes}->[$displayed]->{info}; + + return $currentIdx if (! exists $self->{boxes}->[$displayed]) + || ($self->{boxes}->[$displayed]->{info}->{displayed}); + my $previous = -1; + my $after = 0; + foreach my $item(@{$self->{itemsArray}}) + { + $after = 1 if $item->{idx} == $currentIdx; + if ($after) + { + return $item->{idx} if $item->{displayed}; + } + else + { + $previous = $item->{idx} if $item->{displayed}; + } + } + return $previous; + } + + sub refresh + { + my ($self, $forceClear, $splash) = @_; + return if $self->{columns} == 0; + + # Store current item index + my $currentIdx = $self->{displayedToIdx}->{$self->{current}}; + $self->{boxes} = []; + $self->{displayedToIdx} = {}; + $self->{idxToDisplayed} = {}; + + $self->clearView if (! $self->{initializing}) || $forceClear; + $self->{number} = 0; + my $idx = 0; + $self->{collectionDir} = dirname($self->{parent}->{options}->file); + foreach (@{$self->{itemsArray}}) + { + $splash->setProgressForItemsSort($idx++) if $splash; + next if ! $_->{displayed}; + $self->addDisplayedItem($_); + } + delete $self->{collectionDir}; + # Determine new current displayed + $self->{current} = $self->{idxToDisplayed}->{$currentIdx}; + if ($self->{toBeSelectedLater}) + { + $self->{parent}->display($self->select(-1)); + $self->{toBeSelectedLater} = 0; + } + #$self->show_all; + } + + sub getNbItems + { + my $self = shift; + return $self->{displayedNumber}; + } + + sub clearCache + { + my $self = shift; + + if ($self->{cache}) + { + foreach (@{$self->{cache}}) + { + $_->{imageBox}->destroy + if $_->{imageBox}; + } + } + $self->{cache} = []; + } + + sub reset + { + my $self = shift; + #Restore current picture if modified + $self->restorePrevious; + + $self->{itemsArray} = []; + $self->{boxes} = []; + $self->{number} = 0; + $self->{count} = 0; + $self->{displayedNumber} = 0; + $self->{current} = 0; + $self->{previous} = 0; + $self->clearView; + } + + sub clearView + { + my $self = shift; + + # TODO : This will be different with many lists + #my $parent = $self->parent; + #$self->parent->remove($self) + # if $parent; + + my @children = $self->get_children; + foreach (@children) + { + my @children2 = $_->get_children; + foreach my $child(@children2) + { + $_->remove($child); + } + $self->remove($_); + $_->destroy; + } + $self->{rowContainers} = []; + $self->{enhanceInformation} = []; + + # TODO : This will be different with many lists + #$self->{scroll}->add_with_viewport($self) + # if $parent; + + $self->{initializing} = 1; + } + + sub done + { + my ($self, $splash, $refresh) = @_; + if ($refresh) + { + $self->refresh(0, $splash); + } + $self->{initializing} = 0; + } + + sub setColumnsNumber + { + my ($self, $columns, $refresh) = @_; + $self->{columns} = $columns; + my $init = $self->{initializing}; + $self->{initializing} = 1; + $self->refresh($refresh) if $refresh; + $self->show_all; + $self->{initializing} = $init; + } + + sub getColumnsNumber + { + my $self = shift; + return $self->{columns}; + } + + sub createImageBox + { + my ($self, $info) = @_; + + my $imageBox = new GCImageListItem($self, $info); + return $imageBox; + } + + sub getFromCache + { + my ($self, $info) = @_; + if (! $self->{cache}->[$info->{idx}]) + { + my $item = {}; + $item->{imageBox} = $self->createImageBox($info); + $self->{cache}->[$info->{idx}] = $item; + } + return $self->{cache}->[$info->{idx}]; + } + + sub findPlace + { + my ($self, $item, $sortvalue) = @_; + my $refSortValue = $sortvalue || $item->{sortValue}; + $refSortValue = uc($refSortValue); + + # First search where it should be inserted + my $place = 0; + my $itemsIdx = 0; + if ($self->{currentOrder} == 1) + { + foreach my $followingItem(@{$self->{itemsArray}}) + { + my $testSortValue = uc($followingItem->{sortValue}); + my $cmp = GCUtils::gccmpe($testSortValue, $refSortValue); + $itemsIdx++ if ! ($cmp > 0); + + next if !$followingItem->{displayed}; + last if ($cmp > 0); + $place++; + } + } + else + { + foreach my $followingItem(@{$self->{itemsArray}}) + { + my $testSortValue = uc($followingItem->{sortValue}); + my $cmp = GCUtils::gccmpe($refSortValue, $testSortValue); + $itemsIdx++ if ! ($cmp > 0); + next if !$followingItem->{displayed}; + last if ($cmp > 0); + $place++; + } + } + return ($place, $itemsIdx) if wantarray; + return $place; + } + + sub createItemInfo + { + my ($self, $idx, $info) = @_; + my $displayedImage = GCUtils::getDisplayedImage($info->{$self->{coverField}}, + undef, + $self->{parent}->{options}->file, + $self->{collectionDir}); + my $item = { + idx => $idx, + title => $self->{parent}->transformTitle($info->{$self->{titleField}}), + picture => $displayedImage, + borrower => $info->{$self->{borrowerField}}, + sortValue => $self->{sortField} eq $self->{titleField} + ? $self->{parent}->transformTitle($info->{$self->{titleField}}) + : $info->{$self->{sortField}}, + favourite => $info->{favourite}, + displayed => 1, + autoid => $info->{$self->{idField}} + }; + return $item; + } + + sub addItem + { + my ($self, $info, $immediate, $idx, $keepConversionTables) = @_; + + my $item = $self->createItemInfo($idx, $info); + + if ($immediate) + { + # When the flag is set, that means we modified an item and that it had + # to be added to that group. In this case, we don't want to de-select + # the current one. + if (!$keepConversionTables) + { + $self->restorePrevious; + # To force the selection + $self->{current} = -1; + } + # First search where it should be inserted + my ($place, $itemsArrayIdx) = $self->findPlace($item); + # Prepare the conversions displayed <-> index + if (!$keepConversionTables) + { + $self->{displayedToIdx}->{$place} = $idx; + $self->{idxToDisplayed}->{$idx} = $place; + } + # Then we insert it at correct position + $self->addDisplayedItem($item, $place); + splice @{$self->{itemsArray}}, $itemsArrayIdx, 0, $item; + } + else + { + # Here we know it will be sorted after + push @{$self->{itemsArray}}, $item; + } + + $self->{count}++; + $self->{displayedNumber}++; + $self->{header}->show_all if $self->{header} && $self->{displayedNumber} > 0; + } + + # Params: + # $info: Information already formated for this class + # $place: Optional value to indicate where it should be inserted + sub addDisplayedItem + { + # info is an iternal info generated + my ($self, $info, $place) = @_; + return if ! $self->{columns}; + my $item = $self->getFromCache($info); + my $imageBox = $item->{imageBox}; + my $i = $info->{idx}; + if (!defined $place) + { + $self->{displayedToIdx}->{$self->{number}} = $i; + $self->{idxToDisplayed}->{$i} = $self->{number}; + } + $imageBox->prepareHandlers($i, $info); + + if (($self->{number} % $self->{columns}) == 0) + { + #New row begin + $self->{currentRow} = new Gtk2::HBox(0,0); + push @{$self->{rowContainers}}, $self->{currentRow}; + $self->pack_start($self->{currentRow},0,0,0); + $self->{currentRow}->show_all if ! $self->{initializing}; + } + + if (defined($place)) + { + # Get the row and col where it should be inserted + my $itemLine = int $place / $self->{columns}; + my $itemCol = $place % $self->{columns}; + # Insert it at correct place + $self->{rowContainers}->[$itemLine]->pack_start($imageBox,0,0,0); + $self->{rowContainers}->[$itemLine]->reorder_child($imageBox, $itemCol); + $imageBox->show_all; + $self->shiftItems($place, 1, 0, scalar @{$self->{boxes}}); + splice @{$self->{boxes}}, $place, 0, $imageBox; + $self->initConversionTables; + } + else + { + $self->{currentRow}->pack_start($imageBox,0,0,0); + $self->{idxToDisplayed}->{$i} = $self->{number}; + push @{$self->{boxes}}, $imageBox; + } + + $self->{number}++; + } + + sub grab_focus + { + my $self = shift; + $self->SUPER::grab_focus; + $self->{boxes}->[$self->{current}]->grab_focus; + } + + sub displayedToItemsArrayIdx + { + my ($self, $displayed) = @_; + return 0 if ! exists $self->{boxes}->[$displayed]; + # If we have nothing, that means we have no filter. So displayed and idx are the same + return $displayed if ! exists $self->{displayedToItemsArray}->{$displayed}; + return $self->{displayedToItemsArray}->{$displayed}; + } + + sub shiftItems + { + my ($self, $place, $direction, $justFromView, $maxPlace) = @_; + my $idx = $self->{displayedToIdx}->{$place}; + my $itemLine = int $place / $self->{columns}; + my $itemCol = $place % $self->{columns}; + # Did we already remove or add the item ? + my $alreadyChanged = ($direction < 0) || (defined $maxPlace); + # Useful to always have the same comparison a few lines below + # Should be >= for $direction == 1 + # This difference is because we didn't added it yet while it has + # already been removed in the other direction + #$itemCol-- if ! (defined $maxPlace); + $itemCol++ if ($direction < 0); + # Same here + $idx-- if $alreadyChanged; + my $newDisplayed = 0; + my $currentLine = 0; + my $currentCol; + my $shifting = 0; + # Limit indicates which value for column should make use take action + # For backward, it's the 1st one. For forward, the last one + my $limit = 0; + $limit = ($self->{columns} - 1) if $direction > 0; + foreach my $item(@{$self->{itemsArray}}) + { + if (!$item->{displayed}) + { + $item->{idx} += $direction if ((!defined $maxPlace) && ($item->{idx} > $idx)); + next; + } + $currentLine = int $newDisplayed / $self->{columns}; + $currentCol = $newDisplayed % $self->{columns}; + $shifting = $direction if (!$shifting) + && ( + ($currentLine > $itemLine) + || (($currentLine == $itemLine) + && ($currentCol >= $itemCol)) + ); + $shifting = 0 if (defined $maxPlace) && ($newDisplayed > $maxPlace); + # When using maxPlace, we are only moving in view + if ((!defined $maxPlace) && ($item->{idx} > $idx)) + { + $item->{idx} += $direction; + $self->{cache}->[$item->{idx}]->{imageBox}->{idx} = $item->{idx} + if ($item->{idx} > 0) && $self->{cache}->[$item->{idx}]; + } + if ($shifting) + { + # Is this the first/last one in the line? + if ($currentCol == $limit) + { + $self->{rowContainers}->[$currentLine]->remove( + $self->{cache}->[$item->{idx}]->{imageBox} + ); + $self->{rowContainers}->[$currentLine + $direction]->pack_start( + $self->{cache}->[$item->{idx}]->{imageBox}, + 0,0,0 + ); + # We can't directly insert on the beginning. + # So we need a little adjustement here + if ($direction > 0) + { + $self->{rowContainers}->[$currentLine + $direction]->reorder_child( + $self->{cache}->[$item->{idx}]->{imageBox}, + 0 + ); + } + } + } + $newDisplayed++; + } + } + + sub shiftIndexes + { + my ($self, $indexes) = @_; + my $nbIndexes = scalar @$indexes; + my $nbLower; + my $currentIdx; + my @cache; + foreach my $box(@{$self->{boxes}}) + { + # Find how many are lowers in our indexes + # We suppose they are sorted + $nbLower = 0; + $currentIdx = $box->{info}->{idx}; + foreach (@$indexes) + { + last if $_ > $currentIdx; + $nbLower++; + } + $box->{info}->{idx} -= $nbLower; + $cache[$box->{info}->{idx}] = $self->{cache}->[$box->{info}->{idx} + $nbLower]; + } + $self->{cache} = \@cache; + } + + sub initConversionTables + { + my $self = shift; + my $displayed = 0; + $self->{displayedToIdx} = {}; + $self->{idxToDisplayed} = {}; + foreach (@{$self->{boxes}}) + { + $self->{displayedToIdx}->{$displayed} = $_->{info}->{idx}; + $self->{idxToDisplayed}->{$_->{info}->{idx}} = $displayed; + $_->{idx} = $_->{info}->{idx}; + $displayed++; + } + } + + sub convertIdxToDisplayed + { + my ($self, $idx) = @_; + return $self->{idxToDisplayed}->{$idx}; + } + + sub convertDisplayedToIdx + { + my ($self, $displayed) = @_; + return $self->{displayedToIdx}->{$displayed}; + } + + sub removeItem + { + my ($self, $idx, $justFromView) = @_; + $self->{count}--; + $self->{displayedNumber}--; + # Fix to remove header only when items are grouped + $self->{header}->hide if $self->{container}->{groupItems} && $self->{displayedNumber} <= 0; + my $displayed = $self->{idxToDisplayed}->{$idx}; + my $itemLine = int $displayed / $self->{columns}; + #my $itemCol = $displayed % $self->{columns}; + $self->{rowContainers}->[$itemLine]->remove( + $self->{cache}->[$idx]->{imageBox} + ); + + # Remove event box from cache + my $itemsArrayIdx = $self->displayedToItemsArrayIdx($displayed); + + $self->{cache}->[$idx]->{imageBox}->destroy; + $self->{cache}->[$idx]->{imageBox} = 0; + + splice @{$self->{cache}}, $idx, 1 if !$justFromView; + splice @{$self->{boxes}}, $self->{idxToDisplayed}->{$idx}, 1; + + if ($justFromView) + { + $self->shiftItems($displayed, -1, 0, scalar @{$self->{boxes}}); + } + else + { + $self->shiftItems($displayed, -1); + } + $self->initConversionTables; + + splice @{$self->{itemsArray}}, $itemsArrayIdx, 1; + my $next = $self->{displayedToIdx}->{$displayed}; + if ($displayed >= (scalar(@{$self->{boxes}}))) + { + $next = $self->{displayedToIdx}->{--$displayed} + } + $self->{current} = $displayed; + + my $last = scalar @{$self->{itemsArray}}; + delete $self->{displayedToIdx}->{$last}; + # To be sure we still have consistent data, we re-initialize the other hash by swapping keys and values. + $self->{idxToDisplayed} = {}; + my ($k,$v); + $self->{idxToDisplayed}->{$v} = $k while (($k,$v) = each %{$self->{displayedToIdx}}); + + # Fix to remove items from "displayed" list on delete + my $numDisplayed = scalar(keys %{$self->{container}->{displayed}}); + delete $self->{container}->{displayed}->{$numDisplayed-1}; + + $self->{number}--; + return $next; + } + + sub removeCurrentItems + { + my ($self) = @_; + my @indexes = sort {$a <=> $b} keys %{$self->{selectedIndexes}}; + my $nbRemoved = 0; + $self->restorePrevious; + my $next; + foreach my $idx(@indexes) + { + $next = $self->removeItem($idx - $nbRemoved); + $nbRemoved++; + } + $self->{selectedIndexes} = {}; + $self->select($next, 1); + + return $next; + } + + sub restoreItem + { + my ($self, $idx) = @_; + + my $previous = $self->{idxToDisplayed}->{$idx}; + next if ($previous == -1) || (!defined $previous) || (!$self->{boxes}->[$previous]); + + $self->{boxes}->[$previous]->unhighlight; + delete $self->{selectedIndexes}->{$idx}; + } + + sub restorePrevious + { + my ($self, $fromContainer) = @_; + foreach my $idx(keys %{$self->{selectedIndexes}}) + { + $self->restoreItem($idx); + } + $self->{container}->clearSelected($self) if !$fromContainer; + } + + sub selectAll + { + my $self = shift; + + $self->restorePrevious; + $self->select($self->{displayedToIdx}->{0}, 1, 0); + foreach my $displayed(1..scalar(@{$self->{boxes}}) - 1) + { + $self->select($self->{displayedToIdx}->{$displayed}, 0, 1); + } + $self->{parent}->display(keys %{$self->{selectedIndexes}}); + } + + sub selectMany + { + my ($self, $lastSelected) = @_; + + my ($min, $max); + if ($self->{previousSelectedDisplayed} > $self->{idxToDisplayed}->{$lastSelected}) + { + $min = $self->{idxToDisplayed}->{$lastSelected}; + $max = $self->{previousSelectedDisplayed}; + } + else + { + $min = $self->{previousSelectedDisplayed}; + $max = $self->{idxToDisplayed}->{$lastSelected}; + } + foreach my $displayed($min..$max) + { + $self->select($self->{displayedToIdx}->{$displayed}, 0, 1); + } + + } + + sub select + { + my ($self, $idx, $init, $keepPrevious) = @_; + $self->{container}->setCurrentList($self); + $idx = $self->{displayedToIdx}->{0} if $idx == -1; + my $displayed = $self->{idxToDisplayed}->{$idx}; + if (! $self->{columns}) + { + $self->{toBeSelectedLater} = 1; + return $idx; + } + my @boxes = @{$self->{boxes}}; + + return $idx if ! scalar(@boxes); + my $alreadySelected = 0; + $alreadySelected = $boxes[$displayed]->{selected} + if exists $boxes[$displayed]; + my $nbSelected = scalar keys %{$self->{selectedIndexes}}; + + return $idx if $alreadySelected && ($nbSelected < 2) && (!$init); + if ($keepPrevious) + { + if (($alreadySelected) && ($nbSelected > 1)) + { + + $self->restoreItem($idx); + # Special case where user has deselect items, so now only one item is left selected + # and menus need to be updated to reflect that + $self->updateMenus(1) + if $nbSelected <= 2; + + return $idx; + } + $self->{selectedIndexes}->{$idx} = 1; + } + else + { + $self->restorePrevious; + $self->{selectedIndexes} = {$idx => 1}; + } + + $self->{current} = $displayed; + + $boxes[$displayed]->highlight + if exists $boxes[$displayed]; + + $self->grab_focus; + $self->{container}->setCurrentList($self) + if $self->{container}; + + # Update menu items to reflect number of items selected + $self->updateMenus(scalar keys %{$self->{selectedIndexes}}); + return $idx; + } + + sub displayDetails + { + my ($self, $createWindow, @idx) = @_; + if ($createWindow) + { + $self->{parent}->displayInWindow($idx[0]); + } + else + { + $self->{parent}->display(@idx); + } + } + + sub showPopupMenu + { + my ($self, $button, $time) = @_; + + $self->{parent}->{context}->popup(undef, undef, undef, undef, $button, $time); + } + + sub setPreviousSelectedDisplayed + { + my ($self, $idx) = @_; + $self->{previousSelectedDisplayed} = $self->{idxToDisplayed}->{$idx} + if !exists $self->{previousSelectedDisplayed}; + } + + sub unsetPreviousSelectedDisplayed + { + my ($self, $idx) = @_; + delete $self->{previousSelectedDisplayed}; + } + + sub updateMenus + { + # Update menu items to reflect number of items selected + my ($self, $nbSelected) = @_; + foreach ( + # Menu labels + [$self->{parent}->{menubar}, 'duplicateItem', 'MenuDuplicate'], + [$self->{parent}->{menubar}, 'deleteCurrentItem', 'MenuEditDeleteCurrent'], + # Context menu labels + [$self->{parent}, 'contextNewWindow', 'MenuNewWindow'], + [$self->{parent}, 'contextDuplicateItem', 'MenuDuplicate'], + [$self->{parent}, 'contextItemDelete', 'MenuEditDeleteCurrent'], + ) + { + $self->{parent}->{menubar}->updateItem( + $_->[0]->{$_->[1]}, + $_->[2].(($nbSelected > 1) ? 'Plural' : '')); + } + } + + sub setHeader + { + my ($self, $header) = @_; + $self->{header} = $header; + } + + sub showCurrent + { + my $self = shift; + return if ! $self->{columns}; + if ($self->{initializing}) + { + Glib::Timeout->add(100 ,\&showCurrent, $self); + return; + } + + my $adj = $self->{scroll}->get_vadjustment; + my $totalRows = int $self->{number} / $self->{columns}; + my $row = (int $self->{current} / $self->{columns}); + + my $ypos = 0; + if ($self->{header}) + { + $ypos = $self->{header}->allocation->y; + # We scroll also the size of the header. + # But we don't do that for the 1st row to have it displayed then. + $ypos += $self->{header}->allocation->height + if $row; + } + # Add the items before + $ypos += (($row - 1) * $self->{style}->{vboxHeight}); + + $adj->set_value($ypos); + return 0; + } + + sub changeItem + { + my ($self, $idx, $previous, $new, $withSelect) = @_; + return $self->changeCurrent($previous, $new, $idx, 0); + } + + sub changeCurrent + { + my ($self, $previous, $new, $idx, $wantSelect) = @_; + my $forceSelect = 0; + #To ease comparison, do some modifications. + #empty borrower is equivalent to 'none'. + $previous->{$self->{borrowerField}} = 'none' if $previous->{$self->{borrowerField}} eq ''; + $new->{$self->{borrowerField}} = 'none' if $new->{$self->{borrowerField}} eq ''; + my $previousDisplayed = $self->{idxToDisplayed}->{$idx}; + my $newDisplayed = $previousDisplayed; + if ($new->{$self->{sortField}} ne $previous->{$self->{sortField}}) + { + # Adjust title + my $newTitle = $self->{parent}->transformTitle($new->{$self->{titleField}}); + my $newSort = $self->{sortField} eq $self->{titleField} ? $newTitle : $new->{$self->{sortField}}; + + $self->{boxes}->[$previousDisplayed]->{info}->{title} = $newTitle; + $self->{tooltips}->set_tip($self->{boxes}->[$previousDisplayed], $newTitle, ''); + my $newItemsArrayIdx; + ($newDisplayed, $newItemsArrayIdx) = $self->findPlace(undef, $newSort); + # We adjust the index as we'll remove an item + $newDisplayed-- if $newDisplayed > $previousDisplayed; + if ($previousDisplayed != $newDisplayed) + { + #$self->restorePrevious; + my $itemPreviousLine = int $previousDisplayed / $self->{columns}; + my $itemNewLine = int $newDisplayed / $self->{columns}; + my $itemNewCol = $newDisplayed % $self->{columns}; + my ($direction, $origin, $limit); + if ($previousDisplayed > $newDisplayed) + { + $direction = 1; + $origin = $newDisplayed; + $limit = $previousDisplayed - 1; + } + else + { + $direction = -1; + $origin = $previousDisplayed; + $limit = $newDisplayed; + $itemNewCol++ if ($itemNewLine > $itemPreviousLine) && ($itemNewCol != 0) + } + my $box = $self->{cache}->[$idx]->{imageBox}; + my $previousItemsArrayIdx = $self->displayedToItemsArrayIdx($previousDisplayed); + $self->{rowContainers}->[$itemPreviousLine]->remove($box); + splice @{$self->{boxes}}, $previousDisplayed, 1; + $self->{rowContainers}->[$itemNewLine]->pack_start($box,0,0,0); + $self->{rowContainers}->[$itemNewLine]->reorder_child($box, $itemNewCol); + + $self->shiftItems($origin, $direction, 0, $limit); + my $item = splice @{$self->{itemsArray}}, $previousItemsArrayIdx, 1; + $newItemsArrayIdx-- if $previousItemsArrayIdx < $newItemsArrayIdx; + splice @{$self->{itemsArray}}, $newItemsArrayIdx, 0, $item; + splice @{$self->{boxes}}, $newDisplayed, 0, $box; + $self->initConversionTables; + } + } + + my @boxes = @{$self->{boxes}}; + my $item = $self->createItemInfo($idx, $new); + if (($previous->{$self->{coverField}} ne $new->{$self->{coverField}}) + || ($previous->{$self->{borrowerField}} ne $new->{$self->{borrowerField}}) + || ($previous->{favourite} ne $new->{favourite})) + { + $boxes[$newDisplayed]->refreshInfo($item, 1); + $forceSelect = 1; + $wantSelect = 1 if $wantSelect ne ''; + } + else + { + # Popup is refreshed by previous call. + # So we just need to explicitely do it here + if ($boxes[$newDisplayed]) + { + $boxes[$newDisplayed]->setInfo($item); + $boxes[$newDisplayed]->refreshPopup; + } + } + if ($self->{filter}) + { + # Test visibility + my $previouslyVisible = $self->{filter}->test($previous); + my $visible = $self->{filter}->test($new); + if ($previouslyVisible && ! $visible) + { + $self->{displayedNumber}--; + $self->restorePrevious if $wantSelect; + my $itemLine = int $newDisplayed / $self->{columns}; + + $self->{rowContainers}->[$itemLine]->remove( + $self->{cache}->[$idx]->{imageBox} + ); + my $info = $self->{boxes}->[$newDisplayed]->{info}; + splice @{$self->{boxes}}, $newDisplayed, 1; + $self->shiftItems($newDisplayed, -1, 0, scalar @{$self->{boxes}}); + $self->initConversionTables; + $info->{displayed} = $visible; + $idx = $self->getFirstVisibleIdx($newDisplayed); + $wantSelect = 0 if ! scalar @{$self->{boxes}} + } + } + $self->select($idx, $forceSelect) if $wantSelect; + return $idx; + } + + sub showSearch + { + my ($self, $char) = @_; + $self->{searchEntry}->set_text($char); + $self->{searchEntry}->show_all; + $self->activateSearch; + $self->{container}->{searchTimeOut} = Glib::Timeout->add(4000, sub { + $self->hideSearch; + $self->{searchTimeOut} = 0; + return 0; + }); + } + + sub activateSearch + { + my ($self) = @_; + $self->{searchEntry}->grab_focus; + $self->{searchEntry}->select_region(length($self->{searchEntry}->get_text), -1); + } + + sub hideSearch + { + my $self = shift; + $self->{searchEntry}->set_text(''); + $self->{searchEntry}->hide; + $self->grab_focus; + $self->{previousSearch} = ''; + } + + sub internalSearch + { + my $self = shift; + + my $query = $self->{searchEntry}->get_text; + return if !$query; + my $newDisplayed = -1; + + my $current = 0; + my $length = length($query); + if ($self->{currentOrder}) + { + if (($length > 1) && ($length > length($self->{previousSearch}))) + { + $current = $self->{idxToDisplayed}->{$self->{itemsArray}->[$self->{current}]->{idx}}; + } + foreach(@{$self->{itemsArray}}[$current..$self->{count} - 1]) + { + next if !$_->{displayed}; + if ($_->{title} ge $query) + { + $newDisplayed = $self->{idxToDisplayed}->{$_->{idx}}; + last; + } + } + } + else + { + foreach(@{$self->{itemsArray}}[$current..$self->{count} - 1]) + { + next if !$_->{displayed}; + if (($_->{title} =~ m/^\Q$query\E/i) || ($_->{title} lt $query)) + { + $newDisplayed = $self->{idxToDisplayed}->{$_->{idx}}; + last; + } + } + } + + if ($newDisplayed != -1) + { + my $valueIdx = $self->{displayedToIdx}->{$newDisplayed}; + $self->select($valueIdx); + $self->{parent}->display($valueIdx); + $self->{boxes}->[$newDisplayed]->grab_focus; + $self->showCurrent; + $self->activateSearch; + } + $self->{previousSearch} = $query; + } + +} + +{ + package GCImageList; + + use base "Gtk2::VBox"; + use File::Temp qw/ tempfile /; + + my $defaultGroup = 'GCMAINDEFAULTGROUP'; + + sub new + { + my ($proto, $parent, $columns) = @_; + my $class = ref($proto) || $proto; + my $self = $class->SUPER::new(0,0); + bless ($self, $class); + + $self->{preferences} = $parent->{model}->{preferences}; + $self->{parent} = $parent; + $self->{columns} = $columns; + + $self->{borrowerField} = $parent->{model}->{commonFields}->{borrower}->{name}; + + $self->{scroll} = new Gtk2::ScrolledWindow; + $self->{scroll}->set_policy ('automatic', 'automatic'); + $self->{scroll}->set_shadow_type('none'); + + $self->{searchEntry} = new Gtk2::Entry; + #$self->{list} = new GCBaseImageList($self, $columns); + + $self->{orderSet} = 0; + $self->{sortButton} = Gtk2::Button->new; + $self->setSortButton($self->{preferences}->sortOrder); + $self->{searchEntry}->signal_connect('changed' => sub { + return if ! $self->{searchEntry}->get_text; + $self->internalSearch; + }); + $self->{searchEntry}->signal_connect('key-press-event' => sub { + my ($widget, $event) = @_; + Glib::Source->remove($self->{searchTimeOut}) + if $self->{searchTimeOut}; + return if ! $self->{searchEntry}->get_text; + my $key = Gtk2::Gdk->keyval_name($event->keyval); + if ($key eq 'Escape') + { + $self->hideSearch; + return 1; + } + $self->{searchTimeOut} = Glib::Timeout->add(4000, sub { + $self->hideSearch; + $self->{searchTimeOut} = 0; + return 0; + }); + + return 0; + }); + + #$self->{scroll}->add_with_viewport($self->{list}); + $self->{mainList} = new Gtk2::VBox(0,0); + $self->{scroll}->add_with_viewport($self->{mainList}); + #$self->{list}->initPixmaps; + + $self->pack_start($self->{sortButton},0,0,0); + $self->pack_start($self->{scroll},1,1,0); + $self->pack_start($self->{searchEntry},0,0,0); + + $self->{sortButton}->signal_connect('clicked' => sub { + $self->setSortOrder(-1); + $self->setSortButton; + }); + + $self->initStyle; + $self->setGroupingInformation; + $self->{empty} = 1; + $self->{orderedLists} = []; + $self->{displayed} = {}; + return $self; + } + + sub setSortButton + { + my ($self, $order) = @_; + $order = $self->{currentOrder} + if !defined $order; + my $image = Gtk2::Image->new_from_stock($order + ? 'gtk-sort-descending' + : 'gtk-sort-ascending', + 'button'); + my $stockItem = Gtk2::Stock->lookup($order + ? 'gtk-sort-ascending' + : 'gtk-sort-descending'); + $stockItem->{label} =~ s/_//g; + $self->{sortButton}->set_label($stockItem->{label}); + $self->{sortButton}->set_image($image); + + } + + sub show_all + { + my $self = shift; + $self->SUPER::show_all; + $self->{mainList}->show_all; + $self->{searchEntry}->hide; + } + + sub done + { + my $self = shift; + foreach (values %{$self->{lists}}) + { + $_->done; +# $self->{style}->{vboxWidth} = $_->{style}->{vboxWidth} +# if !exists $self->{style}->{vboxWidth}; + } + # We set a number of ms to wait before enhancing the pictures + my $offset = 0; + foreach (@{$self->{orderedLists}}) + { + $self->{lists}->{$_}->{offset} = $offset; + $offset += $timeOutBetweenEnhancements * ($self->{lists}->{$_}->{displayedNumber} + 1); + } + if ($self->{columns} == 0) + { + $self->signal_connect('size-allocate' => sub { + $self->computeAllocation; + }); + $self->computeAllocation; + } + else + { + foreach (values %{$self->{lists}}) + { + $_->setColumnsNumber($self->{columns}, 0); + } + } + } + + sub computeAllocation + { + my $self = shift; + return if !$self->{style}->{vboxWidth}; + my $width = $self->{scroll}->child->allocation->width - 15; + return if $width < 0; + if (($self->{scroll}->get_hscrollbar->visible) + || ($width > (($self->{columns} + 1) * $self->{style}->{vboxWidth}))) + { + my $columns = int ($width / $self->{style}->{vboxWidth}); + if ($columns) + { + return if $columns == $self->{columns}; + $self->{columns} = $columns; + foreach (values %{$self->{lists}}) + { + $_->setColumnsNumber($columns, 1); + } + # TODO : We should maybe select an item here + #$self->{parent}->display($self->select(-1, 1)) + # if !$self->{current}; + } + else + { + $self->{columns} = 1; + } + } + + } + + sub initStyle + { + my $self = shift; + my $parent = $self->{parent}; + + my $size = $self->{preferences}->listImgSize; + $self->{style}->{withAnimation} = $self->{preferences}->animateImgList; + $self->{style}->{withImage} = $self->{preferences}->listBgPicture; + $self->{style}->{useOverlays} = ($self->{preferences}->useOverlays) && ($parent->{model}->{collection}->{options}->{overlay}->{image}); + $self->{preferences}->listImgSkin($GCStyle::defaultList) if ! $self->{preferences}->exists('listImgSkin'); + $self->{style}->{skin} = $self->{preferences}->listImgSkin; + # Reflect setting can be enabled using "withReflect=1" in the listbg style file + $self->{style}->{withReflect} = 0; + $self->{preferences}->listImgSize(2) if ! $self->{preferences}->exists('listImgSize'); + + my $bgdir; + # Load in extra settings from the style file + if ($self->{style}->{withImage}) + { + $bgdir = $ENV{GCS_SHARE_DIR}.'/list_bg/'.$self->{style}->{skin}; + if (open STYLE, $bgdir.'/style') + { + while ( + + +

$$PAGETITLE$$

+[JAVASCRIPT] +
+
+ + + + +
+
+ + +
+
+[/JAVASCRIPT] + +
+[/HEADER] +[ITEM] +
+[JAVASCRIPT] + +[/JAVASCRIPT] + $$TITLE_FIELD$$ | ($$TOP$$) + $$borrower_YESNO$$$$borrower_OREMPTY$$ +
+
+ +
+[/ITEM] +[FOOTER] +
+
$$GENERATOR_NOTE$$ - Modèle Piwi - Adapté de Tian
+ + +[/FOOTER] +[POST] + + my %letters = (); + my $idx = 0; foreach (@items) + { + my $firstLetter = uc(substr($_->{name}, 0, 1)); + $firstLetter =~ s/[^A-Z]/_/; + if (!$letters{$firstLetter}) + { + $body =~ s/$_<\/a> |/; + } + $header =~ s/\| ([^<|]) /| $1<\/span> /g; + #$header =~ s/\| ([^<]) \|/| $1<\/span> |/g; +[/POST] \ No newline at end of file diff --git a/share/gcstar/html_models/GCboardgames/piwi.png b/share/gcstar/html_models/GCboardgames/piwi.png new file mode 100644 index 0000000..bbd6ce8 Binary files /dev/null and b/share/gcstar/html_models/GCboardgames/piwi.png differ diff --git a/share/gcstar/html_models/GCbooks/FloFred b/share/gcstar/html_models/GCbooks/FloFred new file mode 100644 index 0000000..59c625a --- /dev/null +++ b/share/gcstar/html_models/GCbooks/FloFred @@ -0,0 +1,73 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + + + +

$$PAGETITLE$$

+ + +[/HEADER] +[ITEM] + + +[/ITEM] +[FOOTER] +


+ + + + + + + + +
$$title$$
$$title$$
$$authors_LABEL$$$$SEPARATOR$$$$authors$$ 
$$publisher_LABEL$$$$SEPARATOR$$$$publisher$$
$$publication_LABEL$$$$SEPARATOR$$$$publication$$
$$format_LABEL$$$$SEPARATOR$$$$format$$
$$description$$
+


+

$$GENERATOR_NOTE$$ - Design Florent

+ + +[/FOOTER] +[POST] +[/POST] + + + diff --git a/share/gcstar/html_models/GCbooks/FloFred.png b/share/gcstar/html_models/GCbooks/FloFred.png new file mode 100644 index 0000000..050fd0a Binary files /dev/null and b/share/gcstar/html_models/GCbooks/FloFred.png differ diff --git a/share/gcstar/html_models/GCbooks/NellistosDark b/share/gcstar/html_models/GCbooks/NellistosDark new file mode 100644 index 0000000..79b983d --- /dev/null +++ b/share/gcstar/html_models/GCbooks/NellistosDark @@ -0,0 +1,67 @@ +[HEADER] + + + + + + $$PAGETITLE$$ + + + +

$$PAGETITLE$$

+
+
    +[/HEADER] +[ITEM] +
  • +
    + +
    +
    Book cover
    +
    +
      +
    • $$authors$$
    • +
    • $$publisher$$
    • +
    • $$publication$$
    • +
    • $$format$$
    • +
    • $$tags$$
    • +
    +
    +
    +
    $$description$$
    +
    +
    +
  • +[/ITEM] +[FOOTER] +
+
+
Design Nellistos +
+ based on microFormats Spec
+ + +[/FOOTER] +[POST] +[/POST] + + + diff --git a/share/gcstar/html_models/GCbooks/NellistosDark.png b/share/gcstar/html_models/GCbooks/NellistosDark.png new file mode 100644 index 0000000..ca505cd Binary files /dev/null and b/share/gcstar/html_models/GCbooks/NellistosDark.png differ diff --git a/share/gcstar/html_models/GCbooks/NellistosLight b/share/gcstar/html_models/GCbooks/NellistosLight new file mode 100644 index 0000000..2cf897d --- /dev/null +++ b/share/gcstar/html_models/GCbooks/NellistosLight @@ -0,0 +1,66 @@ +[HEADER] + + + + + + $$PAGETITLE$$ + + + +

$$PAGETITLE$$

+
+
    +[/HEADER] +[ITEM] +
  • +
    + +
    +
    Book cover
    +
    +
      +
    • $$authors$$
    • +
    • $$publisher$$
    • +
    • $$publication$$
    • +
    • $$format$$
    • +
    • $$tags$$
    • +
    +
    +
    +
    $$description$$
    +
    +
    +
  • +[/ITEM] +[FOOTER] +
+
+
Design Nellistos +
+ based on microFormats Spec
+ + +[/FOOTER] +[POST] +[/POST] + + + diff --git a/share/gcstar/html_models/GCbooks/NellistosLight.png b/share/gcstar/html_models/GCbooks/NellistosLight.png new file mode 100644 index 0000000..3186e1b Binary files /dev/null and b/share/gcstar/html_models/GCbooks/NellistosLight.png differ diff --git a/share/gcstar/html_models/GCbooks/Shelf b/share/gcstar/html_models/GCbooks/Shelf new file mode 100644 index 0000000..72def06 --- /dev/null +++ b/share/gcstar/html_models/GCbooks/Shelf @@ -0,0 +1,12 @@ + + Shelf + + authors + publisher + publication + description + format + rating + acquisition + + \ No newline at end of file diff --git a/share/gcstar/html_models/GCbooks/Shelf.png b/share/gcstar/html_models/GCbooks/Shelf.png new file mode 100644 index 0000000..a74305b Binary files /dev/null and b/share/gcstar/html_models/GCbooks/Shelf.png differ diff --git a/share/gcstar/html_models/GCbooks/Simple b/share/gcstar/html_models/GCbooks/Simple new file mode 100644 index 0000000..b737209 --- /dev/null +++ b/share/gcstar/html_models/GCbooks/Simple @@ -0,0 +1,10 @@ + + Simple + + title + authors + publisher + publication + format + + \ No newline at end of file diff --git a/share/gcstar/html_models/GCbooks/Simple.png b/share/gcstar/html_models/GCbooks/Simple.png new file mode 100644 index 0000000..341a1f7 Binary files /dev/null and b/share/gcstar/html_models/GCbooks/Simple.png differ diff --git a/share/gcstar/html_models/GCcoins/Simple b/share/gcstar/html_models/GCcoins/Simple new file mode 100644 index 0000000..2111fe2 --- /dev/null +++ b/share/gcstar/html_models/GCcoins/Simple @@ -0,0 +1,11 @@ + + Simple + + type + currency + value + year + country + condition + + \ No newline at end of file diff --git a/share/gcstar/html_models/GCcoins/Simple.png b/share/gcstar/html_models/GCcoins/Simple.png new file mode 100644 index 0000000..8052733 Binary files /dev/null and b/share/gcstar/html_models/GCcoins/Simple.png differ diff --git a/share/gcstar/html_models/GCfilms/Flat b/share/gcstar/html_models/GCfilms/Flat new file mode 100644 index 0000000..a33d302 --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Flat @@ -0,0 +1,109 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + + + +
$$TOTALNUMBER$$ $$ITEMS$$
+

$$PAGETITLE$$

+ +[/HEADER] +[ITEM] + + + + +[/ITEM] +[FOOTER] +
+ $$title$$ + +
$$genre$$ - $$country$$ - $$time$$
+ $$date$$
+ +
$$title$$
+ $$director$$
+ +
$$actors_LABEL$$$$SEPARATOR$$$$actors$$
+ $$synopsis$$ +
+ +
+ + + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Flat.png b/share/gcstar/html_models/GCfilms/Flat.png new file mode 100644 index 0000000..5373102 Binary files /dev/null and b/share/gcstar/html_models/GCfilms/Flat.png differ diff --git a/share/gcstar/html_models/GCfilms/Shelf b/share/gcstar/html_models/GCfilms/Shelf new file mode 100644 index 0000000..1a7d7ae --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Shelf @@ -0,0 +1,284 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + +

$$PAGETITLE$$

+
+[/HEADER] +[ITEM] +
+
+ $$title$$ +
+
+

$$title$$

+
+

$$info_LABEL$$

+ + + + + + + + + + + +
+ $$title$$ + $$date_LABEL$$$$date$$
$$director_LABEL$$$$director$$
$$time_LABEL$$$$time$$
$$genre_LABEL$$$$genre$$
$$actors_LABEL$$$$actors$$
+

+ $$synopsis$$ +

+
+

$$details_LABEL$$

+ + + + +
$$rating_LABEL$$$$rating$$/10
$$format_LABEL$$$$format$$ ($$number$$)
$$borrower_LABEL$$$$borrower$$
+
+
+
+[/ITEM] +[FOOTER] +
 
+
+

$$GENERATOR_NOTE$$

+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Shelf.png b/share/gcstar/html_models/GCfilms/Shelf.png new file mode 100644 index 0000000..f4423d0 Binary files /dev/null and b/share/gcstar/html_models/GCfilms/Shelf.png differ diff --git a/share/gcstar/html_models/GCfilms/Simple b/share/gcstar/html_models/GCfilms/Simple new file mode 100644 index 0000000..ee5a5f8 --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Simple @@ -0,0 +1,100 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + + + +

$$PAGETITLE$$

+ + + + + + + + +[/HEADER] +[ITEM] + + + + + + + +[/ITEM] +[FOOTER] +
$$title_LABEL$$$$director_LABEL$$$$genre_LABEL$$$$date_LABEL$$$$time_LABEL$$
$$title$$$$director$$$$genre$$$$date$$$$time$$
+

$$BORROWED_ITEMS$$

+

$$GENERATOR_NOTE$$

+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Simple.png b/share/gcstar/html_models/GCfilms/Simple.png new file mode 100644 index 0000000..3e23235 Binary files /dev/null and b/share/gcstar/html_models/GCfilms/Simple.png differ diff --git a/share/gcstar/html_models/GCfilms/Tabs b/share/gcstar/html_models/GCfilms/Tabs new file mode 100644 index 0000000..bec3b18 --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Tabs @@ -0,0 +1,245 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + +

$$PAGETITLE$$

+[/HEADER] +[ITEM] +
+

$$title$$

+
+ $$title$$ +
+
+ $$info_LABEL$$ +
+
$$date_LABEL$$
+
$$date$$
+
$$director_LABEL$$
+
$$director$$
+
$$time_LABEL$$
+
$$time$$
+
$$genre_LABEL$$
+
$$genre$$
+
$$actors_LABEL$$
+
$$actors$$
+
+
+
+ $$details_LABEL$$ +
+
$$rating_LABEL$$
+
$$rating$$/10
+
$$format_LABEL$$
+
$$format$$ ($$number$$)
+
$$audio_LABEL$$
+
$$audio$$
+
$$borrower_LABEL$$
+
$$borrower$$
+
+
+
+ $$synopsis_LABEL$$ +

$$synopsis$$

+
+
+[/ITEM] +[FOOTER] +
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Tabs.png b/share/gcstar/html_models/GCfilms/Tabs.png new file mode 100644 index 0000000..fc217aa Binary files /dev/null and b/share/gcstar/html_models/GCfilms/Tabs.png differ diff --git a/share/gcstar/html_models/GCfilms/Tian b/share/gcstar/html_models/GCfilms/Tian new file mode 100644 index 0000000..c87d34f --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Tian @@ -0,0 +1,276 @@ +[HEADER] + + + + + $$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + + +

$$PAGETITLE$$

+[JAVASCRIPT] +
+
+ + + + +
+
+ + +
+
+[/JAVASCRIPT] + +
+[/HEADER] +[ITEM] +
+[JAVASCRIPT] + +[/JAVASCRIPT] + $$title$$ | ($$TOP$$) + $$borrower_YESNO$$$$borrower_OREMPTY$$ +
+
+ +
+[/ITEM] +[FOOTER] +
+
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] + + my %letters = (); + my $idx = 0; + + foreach (@items) + { + my $title = $self->{options}->{originalList}->transformValue($_->{title}, 'title'); + my $firstLetter = uc(substr($title, 0, 1)); + $firstLetter =~ s/[^A-Z]/_/; + if (!$letters{$firstLetter}) + { + $body =~ s/$_<\/a> |/; + } + $header =~ s/\| ([^<|]) /| $1<\/span> /g; + #$header =~ s/\| ([^<]) \|/| $1<\/span> |/g; +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Tian-Mario b/share/gcstar/html_models/GCfilms/Tian-Mario new file mode 100644 index 0000000..15ff986 --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Tian-Mario @@ -0,0 +1,276 @@ +[HEADER] + + + + + $$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + + +

$$PAGETITLE$$

+[JAVASCRIPT] +
+
+ + + + +
+
+ + +
+
+[/JAVASCRIPT] + +
+[/HEADER] +[ITEM] +
+[JAVASCRIPT] + +[/JAVASCRIPT] + $$title$$ | ($$TOP$$) + $$borrower_YESNO$$$$borrower_OREMPTY$$ +
+
+ +
+[/ITEM] +[FOOTER] +
+
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] + + my %letters = (); + my $idx = 0; + + foreach (@items) + { + my $title = $self->{options}->{originalList}->transformValue($_->{title}, 'title'); + my $firstLetter = uc(substr($title, 0, 1)); + $firstLetter =~ s/[^A-Z]/_/; + if (!$letters{$firstLetter}) + { + $body =~ s/$_<\/a> |/; + } +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Tian-Mario-Kim b/share/gcstar/html_models/GCfilms/Tian-Mario-Kim new file mode 100644 index 0000000..2772020 --- /dev/null +++ b/share/gcstar/html_models/GCfilms/Tian-Mario-Kim @@ -0,0 +1,281 @@ +[HEADER] + + + + + $$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + + +

$$PAGETITLE$$

+[JAVASCRIPT] +
+
+ + + + +
+
+ + +
+
+[/JAVASCRIPT] + +
+[/HEADER] +[ITEM] +
+[JAVASCRIPT] + +[/JAVASCRIPT] + $$title$$ | ($$TOP$$) + $$borrower_YESNO$$$$borrower_OREMPTY$$ +
+
+ +
+[/ITEM] +[FOOTER] +
+
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] + + my %letters = (); + my $idx = 0; + + foreach (@items) + { + my $title = $self->{options}->{originalList}->transformValue($_->{title}, 'title'); + my $firstLetter = uc(substr($title, 0, 1)); + $firstLetter =~ s/[^A-Z]/_/; + if (!$letters{$firstLetter}) + { + $body =~ s/$_<\/a> |/; + } +[/POST] diff --git a/share/gcstar/html_models/GCfilms/Tian-Mario.png b/share/gcstar/html_models/GCfilms/Tian-Mario.png new file mode 100644 index 0000000..70dc0b3 Binary files /dev/null and b/share/gcstar/html_models/GCfilms/Tian-Mario.png differ diff --git a/share/gcstar/html_models/GCfilms/Tian.png b/share/gcstar/html_models/GCfilms/Tian.png new file mode 100644 index 0000000..942f51c Binary files /dev/null and b/share/gcstar/html_models/GCfilms/Tian.png differ diff --git a/share/gcstar/html_models/GCfilms/float b/share/gcstar/html_models/GCfilms/float new file mode 100644 index 0000000..47a9924 --- /dev/null +++ b/share/gcstar/html_models/GCfilms/float @@ -0,0 +1,120 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + + + + +
$$PAGETITLE$$ - $$TOTALNUMBER$$ $$ITEMS$$


+ +[/HEADER] +[ITEM] + + + + +[/ITEM] +[FOOTER] +
+ $$title$$ + +
$$time_LABEL$$: $$time$$
+ $$genre_LABEL$$: $$genre$$
+
$$title$$
+
$$director_LABEL$$:
$$director$$ +

$$actors_LABEL$$:
$$actors$$ +

$$synopsis_LABEL$$:
$$synopsis$$ +

+ + + + + + + +
$$TOP$$$$gtk-media-play$$$$BOTTOM$$
+
+
+ + + + +[/FOOTER] +[POST] +[/POST] \ No newline at end of file diff --git a/share/gcstar/html_models/GCfilms/float.png b/share/gcstar/html_models/GCfilms/float.png new file mode 100644 index 0000000..e047a32 Binary files /dev/null and b/share/gcstar/html_models/GCfilms/float.png differ diff --git a/share/gcstar/html_models/GCfilms/rootII_design b/share/gcstar/html_models/GCfilms/rootII_design new file mode 100644 index 0000000..74873aa --- /dev/null +++ b/share/gcstar/html_models/GCfilms/rootII_design @@ -0,0 +1,118 @@ +[HEADER] + + + + + + $$PAGETITLE$$ +[JAVASCRIPT] + +[/JAVASCRIPT] + + + + +
+

$$PAGETITLE$$

+
+ + + + +
+[/HEADER] +[ITEM] + + + + + + + + + + + + +
$$title$$
$$title$$$$original_LABEL$$$$SEPARATOR$$$$original$$
$$director_LABEL$$$$SEPARATOR$$$$director$$
$$date_LABEL$$$$SEPARATOR$$$$date$$
$$time_LABEL$$$$SEPARATOR$$$$time$$
$$country_LABEL$$$$SEPARATOR$$$$country$$
$$genre_LABEL$$$$SEPARATOR$$$$genre$$
$$actors_LABEL$$$$SEPARATOR$$$$actors$$
$$synopsis$$

+[/ITEM] +[FOOTER] +
$$TOTALNUMBER$$ films
+




design by + rootII design department
+
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCfilms/rootII_design.png b/share/gcstar/html_models/GCfilms/rootII_design.png new file mode 100644 index 0000000..ffeae14 Binary files /dev/null and b/share/gcstar/html_models/GCfilms/rootII_design.png differ diff --git a/share/gcstar/html_models/GCgames/Flat b/share/gcstar/html_models/GCgames/Flat new file mode 100644 index 0000000..8587298 --- /dev/null +++ b/share/gcstar/html_models/GCgames/Flat @@ -0,0 +1,111 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + + + +
$$TOTALNUMBER$$ $$ITEMS$$
+

$$PAGETITLE$$

+ +[/HEADER] +[ITEM] + + + + +[/ITEM] +[FOOTER] +
+ + $$name$$ + + +
$$platform$$ - $$editor$$
+ $$genre$$
+ +
$$name$$
+ +
+ $$description$$ +
+ +
+ + + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCgames/Flat.png b/share/gcstar/html_models/GCgames/Flat.png new file mode 100644 index 0000000..68dc96a Binary files /dev/null and b/share/gcstar/html_models/GCgames/Flat.png differ diff --git a/share/gcstar/html_models/GCgames/Simple b/share/gcstar/html_models/GCgames/Simple new file mode 100644 index 0000000..dc61b0d --- /dev/null +++ b/share/gcstar/html_models/GCgames/Simple @@ -0,0 +1,10 @@ + + Simple + + name + editor + platform + genre + players + + diff --git a/share/gcstar/html_models/GCgames/Simple.png b/share/gcstar/html_models/GCgames/Simple.png new file mode 100644 index 0000000..d1c34d3 Binary files /dev/null and b/share/gcstar/html_models/GCgames/Simple.png differ diff --git a/share/gcstar/html_models/GCgames/Tabs b/share/gcstar/html_models/GCgames/Tabs new file mode 100644 index 0000000..e6b5ccb --- /dev/null +++ b/share/gcstar/html_models/GCgames/Tabs @@ -0,0 +1,284 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + +

$$PAGETITLE$$

+[/HEADER] +[ITEM] +
+

$$name$$

+
+ $$name$$ +
+
+ $$info_LABEL$$ +
+
$$platform_LABEL$$
+
$$platform$$
+
$$genre_LABEL$$
+
$$genre$$
+
$$players_LABEL$$
+
$$players$$
+
$$editor_LABEL$$
+
$$editor$$
+
$$released_LABEL$$
+
$$released$$
+
$$rating_LABEL$$
+
$$rating$$/10
+
$$completion_LABEL$$
+
$$completion$$
+
$$borrower_LABEL$$
+
$$borrower$$
+
+
+
+ $$description_LABEL$$ + + + + +
$$screenshot1_LABEL$$$$screenshot2_LABEL$$

$$description$$

+
+
+ $$tips_LABEL$$ +
+ + + $$code_TABLE$$ +
$$code_LABEL$$$$Effect_LABEL$$
+ + + $$unlockable_TABLE$$ +
$$unlockable_LABEL$$$$Howto_LABEL$$
+

$$secrets$$

+
+
+
+[/ITEM] +[FOOTER] +
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCgames/Tabs.png b/share/gcstar/html_models/GCgames/Tabs.png new file mode 100644 index 0000000..01adbc7 Binary files /dev/null and b/share/gcstar/html_models/GCgames/Tabs.png differ diff --git a/share/gcstar/html_models/GCminicars/Tian-Jim b/share/gcstar/html_models/GCminicars/Tian-Jim new file mode 100644 index 0000000..1d942bf --- /dev/null +++ b/share/gcstar/html_models/GCminicars/Tian-Jim @@ -0,0 +1,279 @@ +[HEADER] + + + + + $$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + + +

$$PAGETITLE$$

+[JAVASCRIPT] +
+
+ + + + +
+
+ + +
+
+[/JAVASCRIPT] + +
+[/HEADER] +[ITEM] +
+[JAVASCRIPT] + +[/JAVASCRIPT] + $$name$$ | ($$TOP$$) + $$borrower_YESNO$$$$borrower_OREMPTY$$ +
+
+ +
+[/ITEM] +[FOOTER] +
+
$$GENERATOR_NOTE$$
+ + +[/FOOTER] +[POST] + + my %letters = (); + my $idx = 0; + + foreach (@items) + { + my $name = $self->{options}->{originalList}->transformValue($_->{name}, 'name'); + my $firstLetter = uc(substr($name, 0, 1)); + $firstLetter =~ s/[^A-Z]/_/; + if (!$letters{$firstLetter}) + { + $body =~ s/$_<\/a> |/; + } + $header =~ s/\| ([^<|]) /| $1<\/span> /g; + #$header =~ s/\| ([^<]) \|/| $1<\/span> |/g; +[/POST] diff --git a/share/gcstar/html_models/GCminicars/Tian-Jim.png b/share/gcstar/html_models/GCminicars/Tian-Jim.png new file mode 100644 index 0000000..3815533 Binary files /dev/null and b/share/gcstar/html_models/GCminicars/Tian-Jim.png differ diff --git a/share/gcstar/html_models/GCmusics/Shelf b/share/gcstar/html_models/GCmusics/Shelf new file mode 100644 index 0000000..429f179 --- /dev/null +++ b/share/gcstar/html_models/GCmusics/Shelf @@ -0,0 +1,298 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + +

$$PAGETITLE$$

+
+[/HEADER] +[ITEM] +
+
+ $$TITLE_FIELD$$ +
+
+

$$TITLE_FIELD$$

+
+

$$main_LABEL$$

+ + + + + + +
$$artist_LABEL$$$$artist$$
$$label_LABEL$$$$label$$
$$release_LABEL$$$$release$$
$$running_LABEL$$$$running$$
$$genre_LABEL$$$$genre$$
+ +

$$details_LABEL$$

+ + + + +
$$producer_LABEL$$$$producer$$
$$composer_LABEL$$$$composer$$
$$comments_LABEL$$$$comments$$
+ +

$$tracks_LABEL$$

+ + $$tracks_TABLE$$ +
+
+
+
+[/ITEM] +[FOOTER] +
 
+
+

$$GENERATOR_NOTE$$

+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCmusics/Shelf.png b/share/gcstar/html_models/GCmusics/Shelf.png new file mode 100644 index 0000000..83dbfd8 Binary files /dev/null and b/share/gcstar/html_models/GCmusics/Shelf.png differ diff --git a/share/gcstar/html_models/GCmusics/Simple b/share/gcstar/html_models/GCmusics/Simple new file mode 100644 index 0000000..7cef04c --- /dev/null +++ b/share/gcstar/html_models/GCmusics/Simple @@ -0,0 +1,10 @@ + + Simple + + title + artist + running + label + genre + + diff --git a/share/gcstar/html_models/GCmusics/Simple.png b/share/gcstar/html_models/GCmusics/Simple.png new file mode 100644 index 0000000..4bc59cf Binary files /dev/null and b/share/gcstar/html_models/GCmusics/Simple.png differ diff --git a/share/gcstar/html_models/GCstar/Shelf b/share/gcstar/html_models/GCstar/Shelf new file mode 100644 index 0000000..e699fdc --- /dev/null +++ b/share/gcstar/html_models/GCstar/Shelf @@ -0,0 +1,282 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + +[JAVASCRIPT] + +[/JAVASCRIPT] + + +

$$PAGETITLE$$

+
+[/HEADER] +[ITEM] +
+
+ $$TITLE_FIELD$$ +
+
+

$$TITLE_FIELD$$

+
+ [LOOP0 values=GCSgroups idx=GROUP] +

$$GROUP_LABEL$$

+ + [LOOP1 values=GROUP idx=VALUE] + + [/LOOP1] +
$$VALUE_LABEL$$$$VALUE$$
+ [/LOOP0] +
+
+
+[/ITEM] +[FOOTER] +
 
+
+

$$GENERATOR_NOTE$$

+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCstar/Shelf.png b/share/gcstar/html_models/GCstar/Shelf.png new file mode 100644 index 0000000..f4423d0 Binary files /dev/null and b/share/gcstar/html_models/GCstar/Shelf.png differ diff --git a/share/gcstar/html_models/GCstar/Simple b/share/gcstar/html_models/GCstar/Simple new file mode 100644 index 0000000..1db1548 --- /dev/null +++ b/share/gcstar/html_models/GCstar/Simple @@ -0,0 +1,96 @@ +[HEADER] + + + + + +$$PAGETITLE$$ + + + +

$$PAGETITLE$$

+ + + [LOOP1 values=GCSfields idx=title] + + [/LOOP1] + +[/HEADER] +[ITEM] + + [LOOP2 values=GCSfields idx=field] + + [/LOOP2] + +[/ITEM] +[FOOTER] +
$$title_LABEL$$
$$field$$
+[LENDING]

$$BORROWED_ITEMS$$

[/LENDING] +

$$GENERATOR_NOTE$$

+ + +[/FOOTER] +[POST] +[/POST] diff --git a/share/gcstar/html_models/GCstar/Simple.png b/share/gcstar/html_models/GCstar/Simple.png new file mode 100644 index 0000000..3e23235 Binary files /dev/null and b/share/gcstar/html_models/GCstar/Simple.png differ diff --git a/share/gcstar/icons/GCstar.ico b/share/gcstar/icons/GCstar.ico new file mode 100644 index 0000000..ddc75ec Binary files /dev/null and b/share/gcstar/icons/GCstar.ico differ diff --git a/share/gcstar/icons/gcstar_128x128.png b/share/gcstar/icons/gcstar_128x128.png new file mode 100644 index 0000000..6121ebf Binary files /dev/null and b/share/gcstar/icons/gcstar_128x128.png differ diff --git a/share/gcstar/icons/gcstar_16x16.png b/share/gcstar/icons/gcstar_16x16.png new file mode 100644 index 0000000..249b4c0 Binary files /dev/null and b/share/gcstar/icons/gcstar_16x16.png differ diff --git a/share/gcstar/icons/gcstar_192x192.png b/share/gcstar/icons/gcstar_192x192.png new file mode 100644 index 0000000..a1d65b8 Binary files /dev/null and b/share/gcstar/icons/gcstar_192x192.png differ diff --git a/share/gcstar/icons/gcstar_22x22.png b/share/gcstar/icons/gcstar_22x22.png new file mode 100644 index 0000000..ba81e06 Binary files /dev/null and b/share/gcstar/icons/gcstar_22x22.png differ diff --git a/share/gcstar/icons/gcstar_24x24.png b/share/gcstar/icons/gcstar_24x24.png new file mode 100644 index 0000000..c361bee Binary files /dev/null and b/share/gcstar/icons/gcstar_24x24.png differ diff --git a/share/gcstar/icons/gcstar_256x256.png b/share/gcstar/icons/gcstar_256x256.png new file mode 100644 index 0000000..0db7cb5 Binary files /dev/null and b/share/gcstar/icons/gcstar_256x256.png differ diff --git a/share/gcstar/icons/gcstar_32x32.png b/share/gcstar/icons/gcstar_32x32.png new file mode 100644 index 0000000..6a4aa63 Binary files /dev/null and b/share/gcstar/icons/gcstar_32x32.png differ diff --git a/share/gcstar/icons/gcstar_36x36.png b/share/gcstar/icons/gcstar_36x36.png new file mode 100644 index 0000000..09d0815 Binary files /dev/null and b/share/gcstar/icons/gcstar_36x36.png differ diff --git a/share/gcstar/icons/gcstar_48x48.png b/share/gcstar/icons/gcstar_48x48.png new file mode 100644 index 0000000..75b22e4 Binary files /dev/null and b/share/gcstar/icons/gcstar_48x48.png differ diff --git a/share/gcstar/icons/gcstar_64x64.png b/share/gcstar/icons/gcstar_64x64.png new file mode 100644 index 0000000..9ab0f26 Binary files /dev/null and b/share/gcstar/icons/gcstar_64x64.png differ diff --git a/share/gcstar/icons/gcstar_72x72.png b/share/gcstar/icons/gcstar_72x72.png new file mode 100644 index 0000000..6346b0c Binary files /dev/null and b/share/gcstar/icons/gcstar_72x72.png differ diff --git a/share/gcstar/icons/gcstar_96x96.png b/share/gcstar/icons/gcstar_96x96.png new file mode 100644 index 0000000..fc4bb66 Binary files /dev/null and b/share/gcstar/icons/gcstar_96x96.png differ diff --git a/share/gcstar/icons/gcstar_scalable.svg b/share/gcstar/icons/gcstar_scalable.svg new file mode 100644 index 0000000..a8f4345 --- /dev/null +++ b/share/gcstar/icons/gcstar_scalable.svg @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/gcstar/icons/icon_install.ico b/share/gcstar/icons/icon_install.ico new file mode 100644 index 0000000..6fe942e Binary files /dev/null and b/share/gcstar/icons/icon_install.ico differ diff --git a/share/gcstar/icons/star.png b/share/gcstar/icons/star.png new file mode 100644 index 0000000..063d0df Binary files /dev/null and b/share/gcstar/icons/star.png differ diff --git a/share/gcstar/icons/star_hover.png b/share/gcstar/icons/star_hover.png new file mode 100644 index 0000000..fb7f0d1 Binary files /dev/null and b/share/gcstar/icons/star_hover.png differ diff --git a/share/gcstar/icons/stardark.png b/share/gcstar/icons/stardark.png new file mode 100644 index 0000000..4c57a41 Binary files /dev/null and b/share/gcstar/icons/stardark.png differ diff --git a/share/gcstar/icons/stardark_hover.png b/share/gcstar/icons/stardark_hover.png new file mode 100644 index 0000000..0874b55 Binary files /dev/null and b/share/gcstar/icons/stardark_hover.png differ diff --git a/share/gcstar/icons/web.ico b/share/gcstar/icons/web.ico new file mode 100644 index 0000000..9cdd1d9 Binary files /dev/null and b/share/gcstar/icons/web.ico differ diff --git a/share/gcstar/list_bg/Box/group.png b/share/gcstar/list_bg/Box/group.png new file mode 100644 index 0000000..ff26ccf Binary files /dev/null and b/share/gcstar/list_bg/Box/group.png differ diff --git a/share/gcstar/list_bg/Box/list_bg.png b/share/gcstar/list_bg/Box/list_bg.png new file mode 100644 index 0000000..302ec62 Binary files /dev/null and b/share/gcstar/list_bg/Box/list_bg.png differ diff --git a/share/gcstar/list_bg/Box/style b/share/gcstar/list_bg/Box/style new file mode 100644 index 0000000..25b9cd4 --- /dev/null +++ b/share/gcstar/list_bg/Box/style @@ -0,0 +1,2 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" diff --git a/share/gcstar/list_bg/Brick_and_Glass/group.png b/share/gcstar/list_bg/Brick_and_Glass/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Brick_and_Glass/group.png differ diff --git a/share/gcstar/list_bg/Brick_and_Glass/list_bg.png b/share/gcstar/list_bg/Brick_and_Glass/list_bg.png new file mode 100644 index 0000000..42cc613 Binary files /dev/null and b/share/gcstar/list_bg/Brick_and_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Brick_and_Glass/list_fg.png b/share/gcstar/list_bg/Brick_and_Glass/list_fg.png new file mode 100644 index 0000000..361a825 Binary files /dev/null and b/share/gcstar/list_bg/Brick_and_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Brick_and_Glass/style b/share/gcstar/list_bg/Brick_and_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Brick_and_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Dark_Glass/group.png b/share/gcstar/list_bg/Dark_Glass/group.png new file mode 100644 index 0000000..9e634fc Binary files /dev/null and b/share/gcstar/list_bg/Dark_Glass/group.png differ diff --git a/share/gcstar/list_bg/Dark_Glass/list_bg.png b/share/gcstar/list_bg/Dark_Glass/list_bg.png new file mode 100644 index 0000000..7b6c933 Binary files /dev/null and b/share/gcstar/list_bg/Dark_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Dark_Glass/list_fg.png b/share/gcstar/list_bg/Dark_Glass/list_fg.png new file mode 100644 index 0000000..3f4b0ec Binary files /dev/null and b/share/gcstar/list_bg/Dark_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Dark_Glass/style b/share/gcstar/list_bg/Dark_Glass/style new file mode 100644 index 0000000..a15c4b2 --- /dev/null +++ b/share/gcstar/list_bg/Dark_Glass/style @@ -0,0 +1,4 @@ +groupStyle="weight='bold' color='#ffffff' size='large'" +groupAlign="center" +withReflect=1 + diff --git a/share/gcstar/list_bg/Glass/group.png b/share/gcstar/list_bg/Glass/group.png new file mode 100644 index 0000000..bbf095c Binary files /dev/null and b/share/gcstar/list_bg/Glass/group.png differ diff --git a/share/gcstar/list_bg/Glass/list_bg.png b/share/gcstar/list_bg/Glass/list_bg.png new file mode 100644 index 0000000..028b75c Binary files /dev/null and b/share/gcstar/list_bg/Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Glass/list_fg.png b/share/gcstar/list_bg/Glass/list_fg.png new file mode 100644 index 0000000..9886f4e Binary files /dev/null and b/share/gcstar/list_bg/Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Glass/style b/share/gcstar/list_bg/Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Green_Glass/group.png b/share/gcstar/list_bg/Green_Glass/group.png new file mode 100644 index 0000000..ca4c99a Binary files /dev/null and b/share/gcstar/list_bg/Green_Glass/group.png differ diff --git a/share/gcstar/list_bg/Green_Glass/list_bg.png b/share/gcstar/list_bg/Green_Glass/list_bg.png new file mode 100644 index 0000000..12d2b1a Binary files /dev/null and b/share/gcstar/list_bg/Green_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Green_Glass/list_fg.png b/share/gcstar/list_bg/Green_Glass/list_fg.png new file mode 100644 index 0000000..3da18aa Binary files /dev/null and b/share/gcstar/list_bg/Green_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Green_Glass/style b/share/gcstar/list_bg/Green_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Green_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Green_Glass/group.png b/share/gcstar/list_bg/Luxury_Green_Glass/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Green_Glass/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Green_Glass/list_bg.png b/share/gcstar/list_bg/Luxury_Green_Glass/list_bg.png new file mode 100644 index 0000000..9af5e50 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Green_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Green_Glass/list_fg.png b/share/gcstar/list_bg/Luxury_Green_Glass/list_fg.png new file mode 100644 index 0000000..f555372 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Green_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Green_Glass/style b/share/gcstar/list_bg/Luxury_Green_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Green_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Green_Wood/group.png b/share/gcstar/list_bg/Luxury_Green_Wood/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Green_Wood/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Green_Wood/list_bg.png b/share/gcstar/list_bg/Luxury_Green_Wood/list_bg.png new file mode 100644 index 0000000..bedb34a Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Green_Wood/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Green_Wood/list_fg.png b/share/gcstar/list_bg/Luxury_Green_Wood/list_fg.png new file mode 100644 index 0000000..968a418 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Green_Wood/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Green_Wood/style b/share/gcstar/list_bg/Luxury_Green_Wood/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Green_Wood/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Grey_Glass/group.png b/share/gcstar/list_bg/Luxury_Grey_Glass/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Grey_Glass/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Grey_Glass/list_bg.png b/share/gcstar/list_bg/Luxury_Grey_Glass/list_bg.png new file mode 100644 index 0000000..bafb27c Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Grey_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Grey_Glass/list_fg.png b/share/gcstar/list_bg/Luxury_Grey_Glass/list_fg.png new file mode 100644 index 0000000..77234f7 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Grey_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Grey_Glass/style b/share/gcstar/list_bg/Luxury_Grey_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Grey_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Grey_Wood/group.png b/share/gcstar/list_bg/Luxury_Grey_Wood/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Grey_Wood/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Grey_Wood/list_bg.png b/share/gcstar/list_bg/Luxury_Grey_Wood/list_bg.png new file mode 100644 index 0000000..14f22c1 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Grey_Wood/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Grey_Wood/list_fg.png b/share/gcstar/list_bg/Luxury_Grey_Wood/list_fg.png new file mode 100644 index 0000000..968a418 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Grey_Wood/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Grey_Wood/style b/share/gcstar/list_bg/Luxury_Grey_Wood/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Grey_Wood/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Purple_Glass/group.png b/share/gcstar/list_bg/Luxury_Purple_Glass/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Purple_Glass/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Purple_Glass/list_bg.png b/share/gcstar/list_bg/Luxury_Purple_Glass/list_bg.png new file mode 100644 index 0000000..7d8dd4b Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Purple_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Purple_Glass/list_fg.png b/share/gcstar/list_bg/Luxury_Purple_Glass/list_fg.png new file mode 100644 index 0000000..85dde3e Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Purple_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Purple_Glass/style b/share/gcstar/list_bg/Luxury_Purple_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Purple_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Purple_Wood/group.png b/share/gcstar/list_bg/Luxury_Purple_Wood/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Purple_Wood/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Purple_Wood/list_bg.png b/share/gcstar/list_bg/Luxury_Purple_Wood/list_bg.png new file mode 100644 index 0000000..8930822 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Purple_Wood/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Purple_Wood/list_fg.png b/share/gcstar/list_bg/Luxury_Purple_Wood/list_fg.png new file mode 100644 index 0000000..968a418 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Purple_Wood/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Purple_Wood/style b/share/gcstar/list_bg/Luxury_Purple_Wood/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Purple_Wood/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Red_Glass/group.png b/share/gcstar/list_bg/Luxury_Red_Glass/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Red_Glass/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Red_Glass/list_bg.png b/share/gcstar/list_bg/Luxury_Red_Glass/list_bg.png new file mode 100644 index 0000000..8205509 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Red_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Red_Glass/list_fg.png b/share/gcstar/list_bg/Luxury_Red_Glass/list_fg.png new file mode 100644 index 0000000..1b3305f Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Red_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Red_Glass/style b/share/gcstar/list_bg/Luxury_Red_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Red_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Luxury_Red_Wood/group.png b/share/gcstar/list_bg/Luxury_Red_Wood/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Red_Wood/group.png differ diff --git a/share/gcstar/list_bg/Luxury_Red_Wood/list_bg.png b/share/gcstar/list_bg/Luxury_Red_Wood/list_bg.png new file mode 100644 index 0000000..876682b Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Red_Wood/list_bg.png differ diff --git a/share/gcstar/list_bg/Luxury_Red_Wood/list_fg.png b/share/gcstar/list_bg/Luxury_Red_Wood/list_fg.png new file mode 100644 index 0000000..968a418 Binary files /dev/null and b/share/gcstar/list_bg/Luxury_Red_Wood/list_fg.png differ diff --git a/share/gcstar/list_bg/Luxury_Red_Wood/style b/share/gcstar/list_bg/Luxury_Red_Wood/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Luxury_Red_Wood/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Marble/group.png b/share/gcstar/list_bg/Marble/group.png new file mode 100644 index 0000000..371a322 Binary files /dev/null and b/share/gcstar/list_bg/Marble/group.png differ diff --git a/share/gcstar/list_bg/Marble/list_bg.png b/share/gcstar/list_bg/Marble/list_bg.png new file mode 100644 index 0000000..adbb4bc Binary files /dev/null and b/share/gcstar/list_bg/Marble/list_bg.png differ diff --git a/share/gcstar/list_bg/Marble/style b/share/gcstar/list_bg/Marble/style new file mode 100644 index 0000000..613426b --- /dev/null +++ b/share/gcstar/list_bg/Marble/style @@ -0,0 +1,2 @@ +groupStyle="weight='bold' color='#ffffff'" +groupAlign="center" diff --git a/share/gcstar/list_bg/Wood/group.png b/share/gcstar/list_bg/Wood/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Wood/group.png differ diff --git a/share/gcstar/list_bg/Wood/list_bg.png b/share/gcstar/list_bg/Wood/list_bg.png new file mode 100644 index 0000000..b30737e Binary files /dev/null and b/share/gcstar/list_bg/Wood/list_bg.png differ diff --git a/share/gcstar/list_bg/Wood/list_fg.png b/share/gcstar/list_bg/Wood/list_fg.png new file mode 100644 index 0000000..fd1b76f Binary files /dev/null and b/share/gcstar/list_bg/Wood/list_fg.png differ diff --git a/share/gcstar/list_bg/Wood/style b/share/gcstar/list_bg/Wood/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Wood/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Wood2/group.png b/share/gcstar/list_bg/Wood2/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Wood2/group.png differ diff --git a/share/gcstar/list_bg/Wood2/list_bg.png b/share/gcstar/list_bg/Wood2/list_bg.png new file mode 100644 index 0000000..e89f956 Binary files /dev/null and b/share/gcstar/list_bg/Wood2/list_bg.png differ diff --git a/share/gcstar/list_bg/Wood2/list_fg.png b/share/gcstar/list_bg/Wood2/list_fg.png new file mode 100644 index 0000000..968a418 Binary files /dev/null and b/share/gcstar/list_bg/Wood2/list_fg.png differ diff --git a/share/gcstar/list_bg/Wood2/style b/share/gcstar/list_bg/Wood2/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Wood2/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/list_bg/Wood_and_Glass/group.png b/share/gcstar/list_bg/Wood_and_Glass/group.png new file mode 100644 index 0000000..6b23fab Binary files /dev/null and b/share/gcstar/list_bg/Wood_and_Glass/group.png differ diff --git a/share/gcstar/list_bg/Wood_and_Glass/list_bg.png b/share/gcstar/list_bg/Wood_and_Glass/list_bg.png new file mode 100644 index 0000000..d3e4135 Binary files /dev/null and b/share/gcstar/list_bg/Wood_and_Glass/list_bg.png differ diff --git a/share/gcstar/list_bg/Wood_and_Glass/list_fg.png b/share/gcstar/list_bg/Wood_and_Glass/list_fg.png new file mode 100644 index 0000000..62c1c0e Binary files /dev/null and b/share/gcstar/list_bg/Wood_and_Glass/list_fg.png differ diff --git a/share/gcstar/list_bg/Wood_and_Glass/style b/share/gcstar/list_bg/Wood_and_Glass/style new file mode 100644 index 0000000..58ff634 --- /dev/null +++ b/share/gcstar/list_bg/Wood_and_Glass/style @@ -0,0 +1,3 @@ +groupStyle="weight='bold' color='#ffffff' size='medium'" +groupAlign="center" +withReflect=1 diff --git a/share/gcstar/logos/Peri.png b/share/gcstar/logos/Peri.png new file mode 100644 index 0000000..08f39ff Binary files /dev/null and b/share/gcstar/logos/Peri.png differ diff --git a/share/gcstar/logos/Peri_main_logo.png b/share/gcstar/logos/Peri_main_logo.png new file mode 100644 index 0000000..cb4cd37 Binary files /dev/null and b/share/gcstar/logos/Peri_main_logo.png differ diff --git a/share/gcstar/logos/Peri_main_logo.svg b/share/gcstar/logos/Peri_main_logo.svg new file mode 100644 index 0000000..98bff66 --- /dev/null +++ b/share/gcstar/logos/Peri_main_logo.svg @@ -0,0 +1,3436 @@ + + + + + + + + + + + + + +]> + + + + + + image/svg+xml + + + Adobe Illustrator CS2 + 2005-11-07T17:36:23+01:00 + 2005-11-07T17:36:23+01:00 + 2005-11-07T17:36:23+01:00 + + + + 256 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 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 wG3COqcfc1p7Vrm37I12efoI4x3np8XSdvdm6bH64ngl/NA2PuG1efTyY9+XXkC58wX8d5eRMmiw Nyldqr6xU/3aHwqPiI6fPM7tTtIYI8MT+8P2ef6nW9jdjy1MhKY/dDn5+Q/T+t79nEPozsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVSnV/Kfl3WLu C81KxS5uLcUidy1KA8qMoIVxXswOZWDW5cUTGEqBcHU9m4M8xPJHilH8b948imkUUUMSRRIscUah Y41AVVVRQAAbAAZjEkmzzcyMREAAUAuwMnYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYqhX1SzQkc+RHgCcmMZYHIFP8AS9t2Vz9A/rh8Mo8UO/SsR6I34Y+GV8QL hqSH9g/fg4F8RcNQQ/sHHgTxrxexnsfwwcKeJcLmI+IwcKeJUV1boa4KS3irsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVU3t4JDV41Y+JArhEigxCmdPsz/uofQSP1HD xlHAGv0dadkP3nDxlHAG/wBH247H78HGV4A2LKAdj9+PEU8AXC1hH7P4nHiKeELhDEOi4LWl4AHQ UwJdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdir/9k= + + + + + + uuid:0D242DA2AC4FDA11B312AE4ADC8A7260 + uuid:0E242DA2AC4FDA11B312AE4ADC8A7260 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 +exu5jAnf3Nq45urqBsOWChn4d5r9PPwoaJb8yL4RvNg7c6O7OKdLbMJx6a9//TEqfr6xqa3C/moP +ZEv8xgnmvbh9dsAw5t2l+aBK+1Q4V8+vHpzsDP9q9/LZ4YX+vCMbf3OAz+syOGl0NF8DTTw8Pjoh +fv799putfwZwvz8/fnHz5WXgAmhHFY8V95h497sbMG32lT6M2CIhE/OUSuoT1BImdQD8y6dP3le/ +kD9SNm799fDpp129PShbijv//KN6+4fq77/4t4+yF//t6/8P//lR/8nf/d7+otx0Zdf87+bl//7l +e/+2v23/ucxeiKd98u1huL//6PLm4+tf//7Z5t8+/8f1+hc3+e8+/Pv81932py9e7eBltsMn//q7 +/H+s25v6J8O7/1g9lH/3jz//+18/ez97/+efF9Uv//GDD8u//+Zxs/2u//gPn/z0q/95uf0u++1H +5rfFz//nV80PP/nvd9/835/84offf/KTnz+/+NlPfvHti3/5yS+K/dc/eeeTB/Vu1E9+e1F+1v/k +5x/9z7uf0cv88Fh+dPfuL+/goP/hJR305Vcb+u7f/uHv8d1cvP9Q/1/47uu74CHwDh+6/c03xTv9 +b/8OXgZOVY5v5At7bPf/+vB9By/dvfr5Rx//5L9VD8XD57xv93H5zx/+9Dv458fX8Nzfbvmtf/Zw +f//hw/++/92HX36RvV99/Q4eLL6qehn9wh/n/1r95ofPfhF91d99v/kk+apN8fv3f5J61Wf3/+f9 +n/+zehn3helVf7V596f/dHf9WexVH376f5pN6lU/+egf25tfe6+KL4MvXL37m59/+PyLz2Ovev/q +3z5453/87Jc/+bfYq2b77O9/mXjV5qd/1334Xa8v6MjbrX77r9n+m80/Rt/r/7u/++BnX1x9/lX0 +VT/+6e1n3qvyfYMv/N//4bPq89RJ/qf/n7337IpbidKFv89a/AfAgdiNSlk4k22wjQM2OIAxxtnG +Jtw78+X97e9+nl1Sq9WSWh3OPXfdmQk+dEmquGvncP7uxH2EUed6dnhz+rV3I7ickc/8s55zXb4O +ELAD787MFI7Wfxlt/uSocqs+rneP+v78/auPzytGXT4K9j59WeqMKsPkBj5svXxSOWq09PnFTPmo +t6fmzi+WFy7KR92NDjGMBeOe5V7M3D0wFaMGX+dmVk43y0f1594v3L71K7dWDJM/2uPg2t/w6nHZ +qM7G9rOVilHDazeCOLhXMerBEQB64+zly9LlTm9ev3Nz5/TrXumom8/iV1U7vHWzNdX+qqOuv/ux +AUjLb/L03MXM/UVu8nzPqFtHv4K/838cGTX6Uxx15+GT93bUg9ZsYa0yTBS02286A3ct982Ks/Pr +WVQ+6sPpq3jn4CQuHfXpz9Otzqg4m+6Bt9t//yxWjPp2wXmx8/eqfNRt73BnY+PeVNmoOJuXD789 +rFzui8fhyw9Vo645r5zDpHzUnda1l5+O5m9xVBmmuNxXh3cuKkd9NXt69Kdq1MfO69a9lbJRZRgZ +eH3h9f34fLV0k98svjysHPX79MudlYpR34XO++MPCxyVkFZY7pO9bz+WbzxZLB318HD/qHLUs9N7 +s1/KRpVhMPBj58P6zlr5Jm/smen9y2cPy0Y9P3/SumZH/ejNFS7PQjK/7nFUGcYcT19udmOo5fMr +b9XBqIs9oz69M/v3cH3/vox657ww6sLy2cGiHfVHMt8ZFRgaA9/4/HZaybu7cmAeduOK5876k7eb +GLXVS2WftK7/9m7tyqirl8UdXl//3uaoPJvZ9cUCXpz5tGAxlHd9eXW7Gy+eLtyJn7zFqEu9o4Y3 +pu8cbG3JqI+mOqNiGLJZ7srTr7rce8vP2oVN/n52Z/2Hjnpvb2ene4flcL9/PSOVFc5qdzX/1L38 +NeUu//6Yshy9L1zdnPI+/fpT/tSfk8tzNzitenouEPH4W+dpAXX6b+44O/c9ly/0YvQ395yd5/f8 +qqcPnJ1P22HZUwXoN6vO46nXcdXn687Tk4fPq54eOS9Wf1xWPH276Lx4dW0q3bSSF9rOy/uPZ6ue +Bs7e1N92+dPbU8751VRkn5bQG//tlvNqb+qOvlC8UP7bR86rL617VU93nNfXkwdlT3XT3j5xXofr +q1Wf7zpvrkXvq55+d97vv5mvePouct5//7yYbVrvC4lzuHfLq3p63zkKj5KKp0fnpj2/HXSeFjft +eNYkH9zHFZ9/vGZWbh5uVD19YbafTT+q3rSTM/Pkh/ut4vNPc+bg+/bN8qfB4e+Xt25fvil/6l7s +Ts3MPXqSbpo7c3d2q/uFB1Ptrft39WkRz7mXP6du+4++556utuaf50Ww2U8vF1bOLp5kyEZlNH/x +OXDQigDg6UqZAGpFzHvmxv2ly5nVjZf3ooO1NxsHL9ferN9uS5uzsbrSPlldXVnaXg== + + + yEtwH278tKvxFK1moy/cvebNUB4kToOk87aD55Yex79nnKU7+1e4I28FFX6+nUmr15a+3fk4J3dp +ev0ifra8W+Q6z6fdmTu7LSUckHRyaD0/avAVks5Z+aj+wX7ZqCBrHHh641M3Ws+PSkmnYlTha0XS +Oa4a9QNHzQF013KnN1tJbtRPN29e64xK7j8b1SvsMFj/W+momz85Ks9GN3l6Nr9c/8X1zqgiFJp2 +5ajk/itGDa+B9X+X4zrtwNly31aOKjv8y60clax/YVQVCu3A4P4/VY16Wj1q/OTZfvWo4CPyXGdx +k8FKHFWN+qwHoK4tL9rx+ZcF9+UyECh/9VajLv13O5XvyTBdr87t6quKOtydsFtJ1HWDN65E4p55 +u9pR15B3UzyTbq6XIRv58r5Z3DOL2T9v81K77Dn29U/+Vn1Y+I0+nmeTeCYi280zzG61oy7rjA+A +fhTc0H9wqq/zLLAd4Wm2kDUIgBt8paCLupts7u3Kz5s37D/H+zlm2YLAs84llvffrpn19xebOWSX +m/Td9Rv2n8XHZ7pByrSnyLmzBoHDVd3DPAh0dn9l6evp2g38I5B5z3nYmVbZnLJXKua0uK5KSPlv +uqumTEXIjb+q3PjOrvMfu0JK1+kKOxhaF7k7lQeU8hXyn+d9zlBY5MedM6TaruwYj67m0+mrVFO2 +wrPspPucoQzT9xhvLQy4X71dcTW2t8UGvTWC+c2DP4WtzzRQA8OX89lMH/S7QenWq+a2Zvf7QWuz +G6RE+pYzwn51Y6H2WQ8WOliE+qFrhGwLBjyRdSxkM9dBzg4ge6gATUQ0d5PgWb59B4uX9dMhnuc/ +dvuoTS2/nuvvXk2V4fHq66miTcni7pntne7r2b2+ZotrXWuw187pbvsmLQ2pMqVnpzd3f/Vf101d +VznMH647pxc/XqdajrIja3hef7oJhlcO7pvbNy2kPS/FM+vv1pYG2puKjTk6z3Ba797cd04v95Ys +BGUArRJMaW8fLyq7Agj07a37Apo/PRfwo/u3ftUpL9D/Am5AqfSw8gICf80IE/h4MUNPsx3Y6FKp +yvvHG+77q5XtfqeKf+z0VUHdCyCywg4+tBi6q7fFlVk7rfzcFirg9njD+bzafluxzKfPyJUBdXZz +Z3XH4v/tOZYf8Xk92uWpp1xnGSLqvC/r2r+oYhIasJJP82Rts4hli5Sq7pC7NvLTpjm+PrPVzdyW +8FWNmKofyVS//brzcZZz6rIUlk3LXXmTPCqnntl0+s+J7OCP5FrltDp0pNkZ/rhedoZWIiDSvVZ/ +jLkz7Ob1GpxhnhAU98s5PT7aGw9EEKd9P301nt46BKbQFTD04L19nvm7P75N68PwDbhpn5dn34xp +0wrYbeBNs2qwVGdzefdvQbp1V/YPK/ERuM4BOOcvW82lxS6ancdpW7DBbDXkvku5B1nSj+lxXc8t +2Ei2BxGn56oh7cuWd/3W9s7gG5SfTootOsztEBt01G4qSNStph96aDQTJydG9ZVqKmfSBx+kM6nj +OjEZt7F8VT0Tuf2py0gD+VHGWv5bTkcv717UkD8BFQ5TkB9pwOz2UEo/anFOHT24TOf7Q8EHx+v1 +EKnKlGyELg1IbvveX/xuoP7ooJG5wh6mDjCcVoEd6Sc1V8/p8lo/vUBTVPBwIFRgOZviIjsrbI4K +6lYow7jvLxdmRtn4HDBUcQ8delME3gpO/97s2qWZX/m+MdAKKXiUHuPX5jezTnQXiWjz3d+CUDjC +ftWS+o6Wo9l+1V/2SohIMXT3ZW/3XvZfj7ove4UYV6GL6lKmeNeXp6dHVDr8etStGvNyTn2Dyhz3 +zPbNJgqDTDFJZFNxp2RxZmbkxXn3Xr18nLcUVutCKkmSdz356zRYF1n1Gl3IIxnm1dnISyJz201o +B1cS3TOPrrqxe3FjOmSt394Egykru3U7eULwqFa9U4YUulQS3Wz2veXdyw6bnaq7vXt7NxroH/uz +2X+2y3RshXvTf/vu7ZlK1Vg1XSxXQm4XSWM5WOS55MrFkS6Oem/u7d2ZawDuVr6pgvg/20WCOAS4 +Lz+bovI+TwuHW9INv5l2sBYVbJMCjoIKlBeQvSkQv9K96c/mYoO6KV81QDdic2dk6GszOaOu/LUq +bbOlWvK8UNjwAu6/qLdulIFFh7PpHPzm7q8yCjiMyg1dQUXb4N400NJKb93c6XAYGpfHjKLGt2eD +w5ubHdUewF7my3pJMfQgHS0MPp1u1960o8WxrKvV6aUgezajhd29tZuxHM3sHHNzvZzo/sum5gnI +N/30ZNLbqOQnJ3tibptHpR4f1ZbCyt38dNNdanY2eaN5HXYT0SaYK2A3aVvOsXc9/mkDYbe9pthN +702VLVAYzvFgN24aehuTDUq6KsNugzJQUGh748ACsOuX4qWBsEDBm6IW0vp21Go6HUrSNR01wST9 +p7NUdEvoMt18PVu4k8JSOX/QfWgLlXK2KrpyFsilUt7F+pHZKwOJaL7oMyZt1Yh9QI+uVXnwsAE+ +KnJsRSvu/qtRbMLFrjKM28dfoAHPj966HCrKulJlSq33webBHwrdo7DZVsRd7PaZGqIj9lLJ9XcY +qIYdNfSqKPTSpbPRjmol76bTKfg0lRmL6/j1Ym8N9V4d/7Ra+ng8vblQpI/H0zsNZJ5reZt0JX18 +PTj33wuqVEKOjft/97eSOBYgrQl9fPe3nvsvg5JUL5A/WjPdRD/TB/0fTz8eD1mTjhpTo1qyJh2N +7JzEXpxm3nb9OzL1xDFzhm1yH810q87nooY4ZiJukT4uzy720Mfl2SaHUUYcexkomczby0ZeXh3X +jsr1L8+aokEyP7cubqNzM6tMeCJI1F/y3Nk0uOfS23BMcKn64ei80T1vADTLs2Hdpg0kgMppvurD +f3a7wVWg6f1anXcT57+8gpjTqlZmNGBCC2TKXdk/aJFMdQ9T9McYWow7KJCpopeq9Y6rdY172jWx +r5Xq69xuFtzgKkFmtd1HeBrAtRe9NSI1VX783V2FY1OmuCsH0zcbnGaeF6jydcSBNvL0VdiolqQ/ +XhR9sarAotmcerTJeYBucM/+5K+H/OUsbRyVQrU9BE0N8xQR+Zs/Xx0fMR3MRnL9/uHGy7u764OH +89XH8k3YcOyRw/nS6ZfH8mWbNmo4X30s34QNxx45nK981DSWLycUjhbOVx/LN2FDF0cO56uP5evI +NyOG8y3UxvJNZKGLI4bzVe4wY/kmqkMXBwvnq39vApHF4wjnq4/ly8ueI4Xz1Ttg5+2eI4XzFRyg +CyS8qLN5u9bIiSbH8FVHIv0tkrXm0yrOqY9zlnBAme9gn7itboXUcK6yapB8u9bNG/ffqioBeHem +1H+9y0zUdKu6FVLVW9XZp3IL+1oP8e/xWrrWJZTVxQPCC6jbRjA8VJ3VA2jqOzhkEF/TFRY0UJhW +QxjtO6e8KcjaCIbd+H6Krvp70yR+rx9L3QW01a69h+tDKhw7A8rcn14WQn6buZiUnMN6AwNfx9uu +j7r5cH1w9VZncSkWkPW9uzaKG4UNu6vwLyk4XPYPuxvOv2SiECG5NrLBZr2fRNB8b2r8S8qllDqA +bhAoViPzdDl48Xp+dM8KnEV/JXMzRPHRveqrTGkaqiRzOurjp5mP8aiPe92oNYz3UbT1KoihuXSb +aVQqFG3dikYwwYvWZaSj+9/E3qyPzGNojFx2yTKAzutGm8ejIabwsh+LOEhM4eFVPeEcKKawH082 +n9dxVc/ptBiE06UXaBDqmJtTjef90+eFgNGJfjGF/TzvB4oprDZMD3RRe7We5Vxn8976RPgUulLV +UHVvS+NZJhVdd171SWMwyDJLbR9Db1qfKIMBN80bZZkdfbHlOjWlZZcf1ZetfuE7jdjcLaqKC7rO +ymtfFZnWJ24v7aDGtrbVH/fwnpu+uPrLFgNjxiMPXt6rvuR5U8RspeIVfdxsqhKoRDaXdy+cPqlc +suOu2pb6kD3emwYnXTCs1EhEVU4WCLarNls229K7F/VCXrfsWbkjfcN15+ttYCmywZL8pkvqF0Iw +u3ZeYC8J0BdfLgeQkCtjjdarA/R7Abo+BGowOTvjLHusuA/HowvinDoXv4xPG+Tuy1Y1ubd5+01N +xN9AuqASJtwytw8H1QUNGKNX8ITsO610TgNpb/yziSzjWMm0hoOq3jn1ZIAZfqsaxOLWTKtbe3Pn +sqi9QVhUA+1NHtKqtI6/Ho2svZFhvOvLs9frxdmG7PujSu1NUcvRQHvz69E4nJNkcf7syPFwBe1N +0cI+QDzcINqbifIgWcTDDeGPXDyqTHtTo7ZrtDcNo4NyPlBVAUKyQWUBQgNFB+UVxEu9jPSf7X6h +sY0Y6W1igVG9A+8tP+vjF5bqBRpINd69vahBMGup+qeHSG83cNTtv7i5wuK6fTmaAer24GnPJsoy +XCLEr9oZonmIXwfjTpRktWoe4tfYY7JGjNpuGMBSEczatTv0h67WnPaN8yv4AgrCnCkaZKVtrq8n +ZDN7W7/IvI6NoI977miReQVrVBqcN+7IvOEhbaDIvDpn2DFG5o3DGbZBZF4WfNG3o5Ei8yZyoYtN +ommGjMyrQJ3jjsxL/QUGjhMZLDJvoj5nyrgi87Kz6Q7Oqz7k4SLz0mEKwXlD24U2X52NI/Af9K7P +qst4iwquE701wiQNXC+lK3csLvHzD140M3rV8rAIhBwxtarKN+xo5PQb7KVbxK6MYe/f0TApAIrO +SeyooUGwL3brZDfuEqNGcbEWLv3ufDESaP3d6p++bgnN7uNBk2S3aiPoE1DVJ8dLA14/s3hIb9VG +1QFdoWd6BMphWPXVwV0lSll1xC+OnuqYvdjLOJKIy47K7+NgWUbY0WgpOdiLQtp4GH/O6WNpYsge +ZNMoJw5W2OOwhLYqsXsin9uuSZDs8fTzBoETfXHa6/EFyb4ea5Ds6zEFyb77O54gWTPdGkOQrPQy +niBZdDSWIFl0NHqQLMLouiXIglDYP8y8cFGqUsEW/JcqQ0mK9/HovPc+Hp03VYP1c+0dU1Beh6yl +cXn/SFBe4WwGC75tHpSXY24rBfwxBOXlNy0cj+xZFpRXK3tWILEhgvImsqqL5dMaU1CeFaNycXm5 +YfoG5TXkGD+i3loNmhos+z2ir7p9LgqGlcFD/H7Uy01Fi4+y6qVGH/T2t95+1NTvcFX9BS7r09U1 +5WwOignXq6xRDTL0MpauQYa4rhzIvdYo4PalGhvQYLRC5oR85t3uPEO5u3a4+RXmICkZsOPUxyKf +s8+vR6il/mJK2KidqVbcOpqa375lphbXnr+eWnx1+ALV1F9Ozb98EOKvXby3OtXaPgqcpf0fkSVO +d85+5Ge8df17qoHqDrubrQm7211y8tvcFXZ3MXP9LF8FthDsF3yd/Xj9+Y+KALi5d3Vhd4etylGd +jZXwabfDZXdQWFeptWLY3WFd2N1UUDbqRBrst7l5fpAttxgUVhMAd3vqfU0c2rP7zw== + + + uxmoQtjdrZ97TypGDb7O3389/6cqDu2gLthPNvlL/miLYXfvt6qD/W78WHr1sWrU4/pgv01nuXLU +84svj65Vjjr17lq4V12ncGqxrmLgw5uFo8W9bXF8/pUGBV59KntPMXT3q0+vnTbpcvrpnakG751f +Hf24keI0padYeA+XmiqZ5fPF2QKBrZO58v69E1XZ4ArM7e7NX0Xn/KISts4PZKJPTa/uMJthaqCl +YpRM6/7fptOqnVN1iZpyj66RyuuVMcYTJSkmRiyv13V8trZenlUfbqtmpgf06KqpFNe32kqX72B9 +LbzRK+tlXRXL6vWy6k2BoX+hldwKrfdD5bT6uqQ3m9NEkxorfTY+m1O1K/qg98b5vOO/azSnvJ70 +T4EdHEdgX7UpYqyBfWWcuNV1jjOwr0w/VqogHi2wryyqb6I6keawgX1lXiVVnpAjBPZ1LclG9U3U +BJQNGdg3hLp7mMC+OoAeY2BfWVSfqh/GGthXdggdZDO2wL4y6TpFNt1HO1JgX+/EHtdacYcM7CuL +6usXsTJEYF8Py5MvJjy+wL6yE+7VC4wc2Jffr5TD7vACYwvs64BKlx563IF9ZWeY80wZV2BfWVRf +GQM1YmBfWVeZr/r4AvvqjMVjDOwri+obbdP6RgcNtGmDBfb127QxBfaVRfUpWRtrYF/Z/ZrIFRAc +U2BfjRvcOAP7yqL6ynygRgzsKwtBKxNxRwzsK4vqK7VGjRbYV3ZKPd7dowf2lUX11cmelTvSOP6n +0pA/hsC+sqi+lN7UCmBVtgfMqZmM2ONz2+25uXzREz40u9aX7+gN6CsVo74/7FfGs2n0VYo3mrIc +A1TxK2ODGrEcg1XxK/PIyLEc/av4NdyqkhrAPT63Dbfqa1+K3gUHeYmgCAeVNXsHnlOPRNAIPEvn +VFurd6I7gLnftAYK152rRjaYVrV7xmBb1ZN9tBlF6RaZvOvJj6VukelRMRFKid2zmdpsyOJ/xU0r +r/83BL/eXfyvIBQOqmluWvyvj5bD1v8bRXHD4n9j8IduUvyviT80YvpGLP5HZNO3/l/DvakOlcpi +PJp5twxb/K9DPevq/2WhXfXF/5rmthNKcW1UiNgeZ4zHn+2BgptUCVkVZr38bORoom0S02rvh+Yx +fQ3cG/t6d+PgR47K1bDSZs7GdTcYNfu6qedQ4XGs2NcHddc5qXVJBNigaAQntUJI02xHnM4BNOIC +K4jfQJQPWv1ifNlQLvEyVb+PSnkAVytKBGNztWJ6glG90V9UelkNGog5SgnOjl6AHY0a6MteSmhg +AXU27ajuKg5QQ3JMdTi1K+FiG6DOptG+309ne6J9v5/2t0Y11ROitz6xM128QL8AtO+n840QW063 +VCx+kt/QF78bxU7nhc1al+1XZ9Yg2e21/epsLMkLVjvOKKMFxuyNtbjj3liLO+6NnsGABQRLmPsy +JWS/QMyFwUMoStxH0dHIdmLtZQxpDLSjhhy+lW+qO6rxRhrALXbC1pBsGkXRLITi4E/hMqZ82pju +Y1Xdv4nB4j2HrftXqh3Ml/4b8j7muzKs9T3ifWxU968vqz6eun8TtoDg6Pextu7fxKAFBIeTuLuZ +25LSf4NHp+S6ypL6ZP7Qg/Q2eN2/cneehrEb7/42dZ2qxWnC9fWJl2/K/ExoFF4j6bJJtK+ZLq0v +n9o9G0f7Hk+fNoj7aBKIOYZo39cF5dewHl3sqLkAXq1VZ0ejR/u+7pfbbsDo+55c3AVPyCEKnPV6 +7SBea686Pjn1HWx2H4/OhwykKidr+5WxVEMEUs3c+ThVOJuhs+eht6/V4fMTufTtTQR86e1Hg+tZ +revs3rRaKX8A2fPonHJ+c9mzArsuz7YbBFJR8GgSSyXTqhHT+3GMxGlFpvFjj+2WbSNwjBn1tGF3 +n66qznfQYpir7RxS6DWsDBp3+/GigcUn87brF3crvY2QVavgZCG9jasY5mo7qtu0weJuV/aPmpTk +ABaoi7s9GDzuti5zEqbVsHRDHa3oXOO7wePfnQG7PVPsOexcnJ+7l9dtwOCj5ABxhi/xz/2phZP2 +I4QYrjLOsO3tJNez47tZmJ396+h8WrFAFsFlbpznL2p3abppdzmpKP43fbMyMu/86kO7VRDYu+v/ +3Xa/VJfhqyk56B+8KYxqQSCLG/tuKkd1Nj4/eVE56k3z6OikatRPMkxdabqVF7lRu2PkLq59Pa2K +zIu37v69/itbK9Xd3UGXpSGB6SbfqSvDlzhVUYghNu3681/uYVVIYE344/TGhV896ubih1edUYkF +ugaeOZ2NvlYFXbbrRt2ZqRxVhjm/2Ls7VbncqfvvWy+7jvY0ScfnX/Yw5lbffPpV+Z7itPTVw6tf +v/t2GV77e7i+/7Tve8FXC31CNS0WQJTOuwcFjjTV3sz+6iGmG1fn1Zgpo3wd8jfRU8Shm5HNWZRS +dPp2bXDvzHLP+zXIYxsVCqlSZUpNVT6ww93yzVB6p7UGDp8T/3GjgblMTm6jNIdQUyY4z9msjey4 +1dmqgtdWrcNl/VaVOG5VmhUn+oTHzVcKYAOHx/VxBh0Aqip9wAb1GsIK+zrC966wVPBAsF2dG9gg +c8qH/I628Q18wBrfm1vV7u92Or1+uD3JTEryex6uj0U3vV5SMWYIze3B4mUDXdhEoypYMtVX9S42 +fWTkPLJZH4OZ6KBV6qgzoD5tfUg1WFGfhpDEke3fiEfsVrqUoc5GIYmNEq9mG1NOPdfHl39yPc3O +M0Jv3a4opmgcQgDg3wam7ybI5nhjjJL0R296FB1yl40AisSxJeqS/eqxEQyaL6mgfvkR/yl4JBR0 +khPlJV6bx9lV83ONMll0xyX2c8Tv5yDbsUn/iPukYhggNuvjVIGpy6POEptKdZxddSqGZqx617S+ +VgYJDFZtUL1Uu9D/CLGllVV/Oia85rGl54VQoQYQMVEVXI7e+okxzSeG4Is+pcoH6K0S8IfatL7R +RANt2pD5USo2rW9N6Oa9Vda7ywhBV2+lnGNZSGLTeMRMCTlcSGLTeESuZviQxKbxiNZGMGxI4pB6 +6EFDEpvGI3ZxnYOHJFZLCZWuVsOEJPacUkU8Yt4BZoiQxKZb2qGeQ4UkNo1HrJSkm4Uk9hdZa0x4 +9SGJT/fK11VXrDCFtH+4WGEFQNcHjw1eVq5c8Bh7scJ6tV3DeOb+xQothm6yVaMUK8yzg/9gscL+ +aruxFCusj1gZW7HCiZL07f9AscIcA9V0q04rr/F9nU6DrFZDFzxsktVqDAUP66sdTgyW1aq64OEQ +Wa2GKXjYu7h8tcNhfaB6Ch7Wq5CqeOiBCx5WBQB6Y8hq9aipN9ZEv3jP8YRhTNgIyZELHmZflFY7 +5KaNo+DhWOqt9S94WK+cmCgElA1d8LC4uG4tQJeNYJBIwmLBw2F0nUMUPOyF0ny1w4n/qC1J0bzg +4fDOsAMVPKyvdtjP265xwcP6iJ0OQI9Y8LBv0NJ4Ch4OEB43pmD8kmqHlTaCBil6ugoejuB2PUjB +w/pqhxNjqlM43xcExlPwsHmdwpEKHma9lFY77DV6DVnwsN6PbqJYK2LYgof1slyNd/dgBQ9ro2Ne +lkkE/YlZ5W5WVTssMUgOV/AwDSIsr3ZYrrOpdfEuL3g4THjcEAUPqyLSTEOus2HBw3FggQYFD+tZ +g4lOncLRQy6qqx3mQWCkgocDhpIMW/Cwvtphj6Vw2IKHpeavzFJacBkZvuBh1dL1MubI2mgFDxuF +x41e8DALIyu9QfU4bYCCh8Ow6kMUPCwBt1y1ww4DNWxHjaqP9hFxmxc8rO9FydoYCh7Wq8vygTEj +FTwcLgnQwAUP68XuiWKyxmELHtZXOywTcYcqeFjP/Oj1HEPBw/7ppsZS8LC+2mFHpdqsoyGVXxP/ +0VOncFRsXFbtcCiPrpKCh31C9RXSxlDwsN7tJzNFjFrwsFYNtqrG4jEUPMzC2Eql1ZSsjVzwsF66 +17MZQ8HDeuk+lW/GFXhVUe1wKNmzrOBhtexZqbwfpuBhfbXDOvXDEEG6VdUO++nTGhc8rA/StU4W +oxc8rA/SreDTBi94WB2ki2qHjZSQjYJ0a6sdDsjZVBc8rLdMTJSlNBqm4GHPRe2qdtjPr7NxwcN6 +sJjQ5MANM7uc9pgr2VZNKyyZyOpGVWuEP+8tLxU1wtJW4zRb4u+fAnRpHGUXVP0pqLzkgHZXOwig +Sz+28Du/CQet2YlcADNce3+v2l1Ne7NfHp2unp/fff7t/tLl7UcPTPz6hTtzd3qNryCqa2vh5e7x ++dSNdws3p6A1mpo53Po61b7z/cHCrbt/GVB2+9bzg4WX336cOevr35ec9e+tZWdj+9mas3H2bcfZ +fJa0nJ2HT46cnV+fvzhPf55+dV489hLn5cNvr5y9i6+fnVfO5U/n1eGdK+d169Ws82Zxb855/3H6 +qXN4uP/ZOXruXYIQfPD2Z5wPj2efnZ+fry+dX7w7u3V+ZX6/Or86imcvZqKbYBKeXyLUdKb17dPW +060nyef7r96/+TI1f/PG/u71+NbPlRu7LzYf3fz648b0dLL0ZPbaz5MbD/3k5tOP3/fXbs9P2DqF +51cfpq4W//hPPvBYNADvwcbLlzec66efpG33rBSd2LNhvOvFBQJcd6ZaKwd+rkCmDfZbuPVzeaVi +v5Z92ZGrv86H24dz5+dPWgs1a/Xn9hduezP3nY2V7RVn4/PJI2fz6ZNfF9cOw48TjPdMpmxJx7t/ +1xfuxE/eOksbR1OIkHzmrL+Kjlj/0Fl6erpYuFXd16irlOGH2V8dFS2VKdn6OgQpvxmXN6fmvv28 +O9X6/ezd1MJJ69nUwpvZ+1PX1+KniAp+hH9uTbUeBHNTraWZJ1OtePFIy43Kz1d4Cpv01Pzfa7Lw +47+xhXMtNuouAJ1eN/Or5tbqt+MlgxUe3b+1M3XOo9pYdx/H8teLP2bxy5e78terv1QEOEs/ptv8 +1rue/Lp0nNn2ksXQ14ErZ+1f30/n5aOtOR3w88zfBfxcsD+XZ1v42bI/d/wl/Uwww+X6yY+/ibMU +PHQe/DrbuXiw/fr1e8EMl7g3dqp3o7nOs/wa7t5ZyD04vr56J32w2uo8cFfe7N1LHzxc6jwQ1uzL +g9wwT0327FBO8MZPZ2nzznynLT/05upi7kFu6M2HbdnwxXmhcu8WpJdrc+77i+9XBIHN54Yt3r29 +qStnafd+p/MjMpLStrGIV+aFGT9dIYoBhowFxe7EgqOeyuHuPl2CbnqRVFl+7rHTeR4Qh9l96y09 +fvHDk89fyhFcu30NLyzICO1fztLBTmdvPqTDfJDTD9+01paOb99c/vJ7Zm/j9rr/PYdJFc9u7j/r +iLhdyv6UNO8kKSYdsMuy/lTXuZOk+JZdzj2KnyXx5sb9mdOXaw8/2TKesq59k4LxS3dhbfHG1cbs +1sOH7tzPo2sWyA6O/WzpxwQyHho37ePTFnZpUbY2+i0/95bsBfi475iPOw/n5K+3Rg== + + + eeOlj4fu3eShK+f18djjX90kcTVPAwWQuryGctdzUFTwLkMF93DtH00t/v3zVF/JUAGwALCBRQXF +usNLN7HMWSt4COq2S3p6Os9LKbT4xTx3AtHGP5G0HaVQA4cdePderx9vHM29pZOFIIA2LvasFiU9 +vfgB7+qteXvPzfSSu9Z6uyC3+85iejbAAsJPSNtqGwzMvsre8s+c5aaDjaUOt9WBNOonBE2kIAPV +SKofkcvrz3JdzqfZ2eX0Qi9zIbNm/XD5dtp2n/cLLMfzv/Jzo02IkGEUV7y/+Phj5ef07w2z+GfV +WXt0+8zpxQwiq+tBCnowwi0AYT5Z4BnmuTiyMGBzs2OmUHjnT4fv4JUJijq54h1JGRLlNu68WP/k +fLrxe+38/M5Jd/1ccCq3t19P2NBFvu+uvvzod1g0tqEq8uO0jw83in1cfdz9K3QxvtK61zffuI9y +JJxtU3cOfqmrFfmJi72uhA0AY/9gN99HcLbRIYggnBq5T3woEPHEhX4fsLG1gL/aWduS2gi0WaBw +8YWc2+Xsud5pIUhvchQ4XfXX+Ru3pz7kMihYJkWLX78THOVvygPT7ixdIY0x98o2HOZYg9trFy9S +roDZGpJrJWWg0+K8sq8X0xuflhY6xa+zPAypE7maM7LK2VlShrfdmS/mc0t6e+59TJe03JUA44P/ +tzDtCVsduycHx+Xdv3rwVx/uewuditWyX2e5qnRyD9qdDrJEFUgZkUzbpZ88fUwQeHo63ZPEYuFx +u2LmHNUextzsvbfvyw5DlnT/qrOkbj6t8WEgJ2bax7t8B7d+Pc862O90oFxnoQ/mwRwBIlRVnp9E +52wq5tHbx9H5QAthB91adZubaZSFfL0q60Dlm1/dJarfVWxaARh3j4pw21lX13unFx1ko9lh7Ksi +Ftze7Xr1bKqyywbL5PXcPZ/OxvqVG2vr8cFJlxD5LoOvg3f5Odkq7X8scby8twA8H3YQVYqhndPL ++zFvYYfAdHgX4St/vSO1FXJ1ECh9Xtk/IglrWcIZvLvAz6WUom/NZH/RlwNUHKR5vps0k1Iv2p+r +bdLxdvbl0sL6q/0tTOedd+/Vn5WUevqzOS49I7U8G6G2OWa8m+TmmPGZezt30weW+FrKe3V4P32w +kxMGOuMrD708R5qRJ/Sb9xdyzHhu6M2NDq97yFhYadtZsizP5lMHnM2CijZLm3suGWlVqVreULjp +1QWy25Yx3H3YSinl1bwlZrs7beXIZ+7syyXbfe4Afxr55xN+7mu/3r2Xv1WWcueut+fIQJHDPHjI +A13IyxcHT9scxluJvfjW6eHc59Wv4cbTlZ/xtTjHKfBwwWtqV0WDcM6KK10+XxpPl2l/e06nPxkm +3LuzvPpg7+atD6tfo+0/D14++P1a2feVt/OHCsZzJ/vfUrHzpZcB2Yc8kH3cWcxx6Sq0fHzeJk8K +9cP6u7U5ZdWPp18p095hWkXY87bAdR0IQN1+qPMFvekw42Q+rDz8IJ6xHIgC/u5Z5pkinPNCeuMO +L1QQ/ja9ezMThC9S0bWtmmYrwQX+HF2HZJ738XN5IWUR5bplt0q+eOUoFpBrB205nL7bZGnN5mH7 +jd7CcuXqlkNFamYBvZk+2J7lhTaLzu8P6TZva14ONq/e+Jg1L9q2PYO7v93OoZ3FL3eWUtZ3x3GC +j9swTm2b3CubK39DgZcvT8H6ClY5OGsrA3VtTlMadTQlZ/+f8Faxib3J2CTJ5NLzq5+n50/Pv335 +9ntSDvTWxH8sPXhozN7vT2cb56enL0//83Lt7OTq1+nvy8nlyaUHL1YfPoyDtdOTs0+nk4sp/9Sj +C+3S2hEmwzfhrze3Wx82zqLXnvOpV6d3/Hvz56vvH1c+PH724MHib/P+1j3zKl679ukKoVFrq1vv +3vPWl6hmeVluyZW+XFn7nGz+2Lr2/M7x2mfn4G7uKpHznfnz8i/0XFtI5HYDctrbqUV348XU7NYF +VoOWAyUeKhp0WzOglOmoSwvWQjLzd8MbL25sJNeff1p9/+ja1kIS/Nl9sOV9fXb/88s/t9Zfrzx+ +JMPc/7z38smPZ9dezzzYCk+eLiRh8mbtzcbpPpe7+v7hh29U+3Tfk1mz8CNC7q6DwxRwHv/uKC06 +F1iI5s8UAhZTynRwbjHc5Sxg7OjSIl65MelfZhbaklmLXIOItxjpjh/+odjIn3JZ3p5TYuzcTpES +M5HSycPuzNdMsbU9l3+QXB5nDxbyD3YWTrIHrc4DKtUPo9Ps2VL+oz+rX7IHhYsxr3ci17Y3m927 +J4v5B1985JzLnrWJfQVf3HcUQW21NsAuPzF6WbfuPcfPZ/nOP35ZwOY+s/f55Hq0bBG5iNmq2z5p +3TF6b54tUUQxJ/ceYuee2X5Pnu2xXy+nLdo6Or9hqVXLh/7uZSuvVbl3/1Z2BZ6v/Gx/mX+we/IZ ++c7Xth9OvejAKM83w1c9+VxTPLHizm1t3C7rsqy/1LO3X5fPdu50LmpwuXv9dPP9m/jLgxdX17+t +v/m062BdpgPGeS7k4BCK20yn5OdWv3YvykDmtao13bVnqxD4Xi+l0P9aOv/4Hl65r01KX15T53OG +vzz9q8fNpkypnKKC7HoOigrWro5vIbac2GBl7uf57v2ly3BrZd19uVaBCjK004xkduglrET/GMns +0EsZ5p8jmR16mbue4yeZHXpZ8Lqu0yN24LvHt7Lnzj3JEaLr64dBXmXzrEdl41608iqbk3i7R+1z +OXuRdbDbq/OZ32z7Ww+oGbLapbP5Xg1V63FOPTWbHHhd6qnw2tSNletrBRVXegVEZlL9pZyEcoO3 +Wt7NW2+N0Iy1RcCBGlbZBpBpa9v7iyOPwlJ6oY8uu+yG12Yq1R2ZvA6NR5W6Y2ZEdcfC76YS7sHi +73oVk+qXWDEgJ+HbJb2b6M5xuvB4oZmKKTpvItJruLzMTobRPjoqpvuv54/lvBY2mDmWqCPblp/F +8p69Mz/M655EaOdJYJjew1ga8TDm8mo3aBJzZs9GykQNXRl4EgX/fkStjKDR1ICVkg70bBr3Mawm +r+PT92HOqeijK7nt1ofFhsC4mNsYGaa2y2z6ckei53NP0lyzX5zu90z1FLFM3Jt+cLPodsZScNex +NrdXu95bms/N6bd3azed0y+34/ww++ml4PH7F8rDZXliFVfd3XytBo8cgckpPp6e/rakdv9Hyz3a ++rQIq1NL+TmYdCyr3rZWnb1p0qKZ1Aonf8FHjqRZOfKMNCtHruaT4H5Lf76/OFVW3bt++/YHyvrO +58P7X/LUMz/VArXNPegWUXMP9syn7EGB8n7OD5MXBlrXH37NmGs1TpvNnelMDniSlwOETGRM3ZNW +/sGfZYDvk6WOL2zwxFFGeivcwZk/Ufua+Xh4I+v82YK+8vGPwS181srQ03V35u4sPL2etS07Hq6i +l2cO8SeQzcnWU7bYfk/evDWptmhjIbPuKkPizoXL2d68bOswztz12HF/vrixvpjMifDmvLrexSnM +WvON8JrKr/b4NmZeNy+XmnZZ1l+2aV1dOrkuzdK1i9sLe7fOw7t7/pMH0ftPs5Z9f/NiKjXdvnU7 +VusOkLlzV59+cOkq37xezHPpW3dw91+3LYP+5qHRC7B28tQqU9d+7rn2r6u3h2S53PW546PUr2kn +KhiXJtKYJRGJ1flBbsYDXsX5FDZe/LGvyI1LpWB/tiMFZ3LrjJpjTr+f0l1iTs1H8J6wmqGFjEuU +G3d/6r29WKvkcXYXqeJa+rZ/2VaW9tvi8gLxhnf91vqjsvAQ796rJ9sKSPgn1UHi2Z7l9AveFpTL +C44WFM7vrrbUktrtXpGxvnJBoldbqx9+PICIm1fwbS7PAWQec38Fz8R6LBCTY2WQlcN9AMXRLZhm +HxpztP77U141JM03pOnF6eXVH74SHK2cfvn2e+f4v04FGMykmXT4//pvlEwaN550g0B+BGjdEdZ+ +dvX459+r00kzN7kDBH+09OD8cu3byeW3s9/H5/81ucy2/cc7ew/XJpcn7etH8vqtyVmZlXMk78uz +OeqljjDXI4wt7Lwzuf+/8S//fI5/HDsZ/O8+mzExV/5//7/w65H89V3a/vekP/l48u17Z/LThDyT +b1smMe1g0nUi+VfurevE2c+d9Ke85Opv+3pPQ/bBRdVSncmlh78vJ2dfvNo8evH1+M/py//6c9q9 +uJ0Vu9uy+dzrVqvv9hvuuxyB/F9+93+efTk72lz9/O3nr4uGJ8BP/jPY6Hw2yElc4b9PB9r6IGmH +fuxPtgK/HQROjP1nY+i40hi1HROFk63YaUciR0tL3E5MLJvvhO044IexPlrFQXiuPE70vTCKInlR +Ona82EhT2A4EG9omN9RBjfE8/RatJpDBAmPnhO7iSHa15SftyA+NHddEPpviMHL5LaYXBPpeEofe +JJfgYfzAbTtxMNm7UPlu5SP2cHbv9+/jX6efJr+cH3/6JndvbrLltH0/8Vycp4lkTVEkx9yOY18O +Wva27Th+mP6nJdPy3FBWkriTJoi8RD6WOax8waqioO3JGidbrswxkpm4bd8J/IgNbhJgmbI4Garl +ee3IRPHkPs4g9rCxAtOTrSSRTx3X7/009rxYJpHEk4nsbhzG7uTKCQ4Q++ZJl2HbDUPpsndQTMc4 +Omrs+ZMrGBS7myQuLpfbDmNjSj6MPDlGX17xnbZxnNB+6bdlzQIyRrr1XbdsmXJKATYS04tieWel +8UJlj4K2I408Pz8RUMBCPwO6V1YqL1a+YXLpydnl89OTs/NPcta8Rk1Pcun56fHPx8eX59/+E58J +onzwcNMCy8vPZ+e/9Fl6OeXGfjr7eHr04GGCa/vi8r9+nh51pmJfi/ojI9zj/U8Kp+4wcJohKcCi +7lQ4OTs3uf/aIosupH2cYg7HYg4j5yFkRWiL1zaxa4AcTJy0jRdGk3LiAtzepIkjgTy5xNIQOXKM +Jg5129KGE3wVtH3PiXIv4Rhj/PZN4stvTwAuCjBUnEREJ2iTM2BHJvRCvhTpb9+NI/6O3cDVXt1Q +R/IF5mO83PYibEMsl8EPoklAfOBiKEdQl4zpum1P+tGhTNsL5G1pCxzZTzTEoe+hIfLYiSeoaNL1 +BJslYbomT0DelcEjE3DdIa66K3cyjDCQLNpEk56D2xiks/OAvuTW+T53z3BJHrYDh8dLIujSE3gX +ZIlvIsFYAv+TntA3X261CWXHXRlIGtxY3jWh23aNH6MXN5E38FXotBNP8I2MFDqCko1gQLlrySSQ +quvJ7sm9ixPZVs+0k8Az3Adpw17JOxHwgFwCfIROeHUFPwuClWuIkSJfjwmox5O5Yg1uEIMEs82N +bFsY80OQC/kdOoJXDvCO7IHryayBukKcor1v0hDh1hs/BGYM0BD4ga7Ll40J5aG0eYnApPGlm0hf +kgeyUF9QvoOFBjJpnxSCbQZngTZCjOy7L6cnDT4oBiYkGC12BHSkzUQGPQliMLJVMg== + + + 68QIQTSCEbxIlxoL9WHX0uYCbeMlH1ApOFmOkmMpHHixnrF0G8hFwSqkyfd9tnk4SjQ4xtEJei56 +kdMGZpcGYkd+FQgoC17E2gPwfDKmEzm6Y1iVnJsbenwhjgTE+ZEgCVdAWjoUdB2DUWyHSeTpvssc +jBvJNceq5OIGvgK3i78DtrkxoMUNLPiEdlUuwD/A/AhF/EhOV0AJbWEElCANQte9bJeN3J8oDLnF +grl0VdLmOYm2ObgTuIShw64DHINrFAilVzdRKBCayQOQtsR1ZDYGdyG085UtMgZz8T09O7kHOCpp +ixPfdoyLY+SIPMP5JR6wDxrcxDbIVvCr3Eu+IJAdbfNdwQcetg5QaIRsgGcChLhyInwD908ALQxc +PXUZP/DknHz5b4RTN/IQnwugCzKTboTXNTG+CsCC6a4KUY4830eb7wvJNo4sKTDsRgAfDbKDIP5y +FbjdmLXj6gqkLcHxo8EzwnVh8ASLd4RrNnhD4NLua5IIcyT3X+YjlyOcTAREgPbwWzDsZCLXOcB8 +A7n+EQcC7x0IgPgCwJHcYsGCHm6zD1ZC4Ba/5dLyN/gfGUXQtnTOVzgjwXmCFELMLAS5iWQThA3B +1iWhSyiOIulVIEqaHE+YxEhwbSgbhhvqxfztCBbA9hu7b8J5AUPh8nlyPeWnH7r87fmy9/JbtjzE +CSZhQCogiNZzPY5r5CQmhaI6npwlt1CANZSucJFl9VHo8PajKTbaFMQeYCOUO4mzkBuO/wKDyl3B +79gVwBTuVi5cwv3w5BBlpvKnCwoinQSB3H3ipnjSB5sEHCo76+l/0y8EyhND6JPbZLI7CDzpxArz +OCZgnFhhn02AT8BpTAQeJUAvAsCJkg6fSCtuu55eTCB9VzCtTMfFDgqdcDEf2Y4Y2EfwiAkVVI2S +RLnJwqUnWKsbCvIw7NzD7zCSTo0QN7D4wOtxxE+ESpn08J0QfKWgqch19WonYN3lK0E0Qq9ljwUI +8BX40yAE/yD7L8hAXpI9C0CFXWFPwPjLpkFSiQQLxApDLaGECYisSwiV7REsGoKfEGTpy97IbAVr +gf10gjC02FOYXA9cu+d5Op6QPzkiuQex/JYVh4FIIYBY0FBsQkDOJiEzINwAZgDUZ+T2ypVx5YEs +FIxOGCsQCYY2ghPxhlxV7LNQHrl82CzjgxgHkMIEkHgp7fkDuPAEzBggNBTCDyoXCxvjC2DE4E1k +WpHL0xRqG0LMEN5arqHApSAqTy4bUBG2OwSTFctCRJiNOYi0xHKppCkiDQ+BkyKZlu+A7wkF2IQ0 +4FBdNyT7IE0ii/vYDRIz+e25gvsCvuniAnmBnLUHiDHc4FBWKVwYVuKA74lk8Y6AgVx9ApvcwQiY +Rn4nwsfYiy3oGGfiJAL+kxH4IoAyNzYA+ogdYCRhVQLlu6JEKVxgj1F+CxcXyG+BCNly+S2sg0xQ +YFCwJtYiSMmNgC6F8RM2YlIYNQF6AI0TO4IcBIm5iZ9w4qDTwGvyOcQWYMREgERYZUeFqFBWayYh +3oCU+wqiJ0SewvVgENwLbxLIFKDgg6ol+BmHQt0wbeMEFt1y0UKTCAGJHLHDHoTqSXsCDiBCD6FR +qpN4YNSxPXKxgdPBgkW+grTsieB4uxBdOz7BDoIJwbUNE5IKFwI2MFLsKDGR/UKnMkHL2woVIhcq +O0a8gQZBvR5WI1yIEi6RH7DtIrUYpa7SRvHBA7kKgCOAyvFbeAKwK5BJwRXKLsqlVZZYYE5uCKBC +OD0w+6CsOAUX5yjXHvSYLJvQCXL//EquIm6LYHw5QbwkSELYEAFpE6QN5O+kfzkP5e/Y5mpbCIFC +GgJAtyGrHpJ1RJtDUtwOeNkTmbRPUUUQcMR3XAjMwgMLZ+DJCeG3i5sov2XKCRv8SNCjzF34RMsc +OSCjLhCPkPaYzFDgCiyAA/L4FU5cpuMCliJlj6QtgVQv/xWxy51UzgzYR3C6j9+yyZ5cWvBeoEpY +qAteSnApRR4yioESWddV3hQNviBnDCW9ujpUAIbLhXhFYo0GYbdDNAgWTdggIl+MBhFdgnQsvRrY +6IB0E3OMAIiyMjlL5R55sYHk4yRkg5vEbCBHtqrcY5BAHsUuRoH2hDYvZpvjK9vJLZe5AtEf6DsK +OpAXTcR9DIE7gfINWErKhwQuWVmkcqLrKmsCXA1uYqfQ5nvKH7og4WCYgEJD5WGFTxV8Bl430OEC +HJevnGfKCCcO0KNPQsAd4d5KAyQlcuWAEKC+7KtY2VKBi5gkSugHbp2b/YbA5+JGheR+rSRBKAJJ +9dEzWAecsaAsQVUh5Q9LlISiqHDIJt9oG7hVCqABVw7m50DfEdEJ8ouVNjyQRs+n0B76Kg2FEIPk +MCgWr6o4FIH6GCDgULfQs2ohtOGaQdCKYrDWVgizIpsnQjUgkuQDDYlsNoV7yBK+6v9w2nGsJN13 +FWLArVCq83EJYl6G0IcUAA4HtxYNrq9TRJswdGhziRN9YDeXd0wpFAVRHA7gJYzTryicoi2yY/lg +yynTCL9yoBPSlQrgQ5qDcKryJkQ2CsKyvCAisPJOnajcSUptiEZ0f0Lgl8iiGfBtwtiDNATAcnbt +ID3A1FDPGuG6XBH6QQz4LkRuD3ctZXj4kWw4xE60xZBwhYcFB+BD94heArk6wtBNBsqVrFrhHlAJ +tZ2h+sHXTQZ1IKYMIGX7JCFyuxWvC1PCCy//4hAF3woRBrKTe8uGyFWhUyiosccp7EAI6uITScm6 +hZgLkgLXkRCfUbnjya4JVQMhThU+PhQZEVRMbsiX5FgjEoxI1UaRXGoB0kx8g/pJ2CpsKGcMBQ9l +Hh+TohbLqr7SBn4FbY2XfwkaNDdmN8RmULOhAWPFRoU+ofMOgBTqbogpaAghN8h1MgTStAFsIvCW +/YptBrdA37HYRo+5R6OnSlQqxYGOBR3Ia65aAQBrcUweCUhUG1wyTdxwq0rHRlPXvmo/8rFrItZC +pYvfkKcDy5tpAwYKcBbZN/nBd3ROuA2uIAxwhKD7v2wbuEW0gQDs2DZfGUdwzVmTsMNscw1V7EJl +9LvAi20D5EY02LmDogozj7aAcwVxAtGMLM+FhkD4aTClxPQQKkA2wGlFoF4GX0HSSlRChRFFBADp +DlwpNVAYSZqo7RPOlR3vaFtEAgHm2es0qTgMzTd+enKI+C0XQRsi/pTLkHYc436gLYQdBg2JitRg +KPE7SSA/g89PVJaCHAdYiGLKQ5NoEHmLvfigMmgQgsShqB21X+mWxjjhmC8p+sEDqPnREDjaEEWh +TjAG8HD8ADqdHW3jdZK20PdCfhg7ENHRIDfmQF9KhDKxLVEgYVuiWxb5MFOJbMW7AQkArC0aBFzY +k5xNoFMQHOPqtITbsD0BrkJOS7ZHPwwNYEFwCXjZA31JppOwDToHfOjxwvloi4EaacRJIuo0iAla +0JdBTIKSIwxVDo3blC+lSURvOUhgapAQmtnkyrUgDPO53Fo9JB+mMsgUgElBU2gQ6hWyAbfvQF+S +K5OwzeDKQqMSuezJiQL9KoJ6HQ3Cg3NDfCKt2HatGyJtFAYwBU/1Gi0Ila6QfSxNJEDpDFRblxa4 +UUzzWwiJlQqdiIi2FUInhcW4CnGtGMjcnhpNRgnmIrcVVAQsLpYLUSkUOA0cZUtaMPbihLgi7Gvi +K9lNleAYKxEGXxAUgNwNLHaA9sxANAbnhUtsjFIP6BNcsC0tCAtcCIiyq7bBFlh6kWuVj4Ws0QJT +L0sBQRGEh8NGC+0PIVganTnbyKbIwUNE0hZ0b8B0ubYjB+p1MH9OlI1HFt0YpXktqBCpD5PrGEaq +5JAJCnGGLOBa/CPSXxt8laAXYehoIhXsEohwC3IHRlxarH4IYpNMVMdDIxRzEFlwgXfSRqFWfDOI +9VMhj9rg+lmLoB+2eJ3OiO2kkQrNFuQ6IWbkDR0otVqQGQWdBeSTYxWytdGykC5sPS1IjY4POg4w +S8isszdXBJZJ6rmT9Gh9BanQLrTTKPsRWurK/XB8mib9ABhYOxRwADvXApPvRdaRAFsHFVsLSB1k +P2uForQFpROIsTbFRMoyIZEwbafUgeKayT6Dv9pJW0NQBt+yNWkrLREtsClGtwz6RQOrrR8nenBy +hjBRtQBgjlp2eOJUhraA/3GVWhCIyWW3gEuhYiGsuMSK4Coh4Sh0umBffatVixO9D2GsE1AWiJBH +VTPUauRpFUIFoh0MArU1OAAuBFK1mvrBasNaT8mWVnfjcob4A0o2+gPI5bOXHAKP8Dv4QsgUAJtN +YUBKZay1GJ8C8GBLcFS1yxsUQDXbEomI6j5MTgTamCSOjCYXRhkEL5Gy6KeO2rbQqnuGbZYdR2+B +xdpAGyY0hsP6lrqyEWqBtFEvg5co1fekNwsH0M6QQnsWwysAk7J4KtQS0Kn+RhNNKumFEHAI2UoT +EZt8qHvRJIQx1ia9VdgVwH72rePqOqgT4CA0vGK1NCyySeiz7pQgHLstUNZEMLV7sIbDHwFNJgYL +AwshpJgWFEFxomQulSmkEZc+waeQ6rE2KJB8x1I/H7cwob9Iwhbw8orlBSHyZsCgBUZDkLrgNX3L +QK/aSjxVVKDvjKagUVhRbfTt5Y3BtSTq0RDQewDGVkiYWFLkY/NBe5xIeSnXCh0tKP8inhamAx8R +cEJ0MgE0ggVrRbDzkCc0yuVgGmgEdVVY9ZRyCt+XBORiZKwQWCMC0nKUn6SSGC0B8YTrZCatVmTN +Xy0hBCIpy55FMJ6ifxiXIJTD+SIMiJc9tYxyGh6lMV426MJ3tM2NgkQdOSDz2EYy2Gzk7kZWjYer +D5mG4BtRt+5pIxSp/BbYHrODmt4NsjYortgGxUDWGHTaWiF4MCzWoX7FVwSMVlAfaQ1iKmvQJPeW +LxJP6rfCz/AtkdICVcKgVa6Sxx792Oe3MRCIiyaR3iCUoilyiUDakAsV4kLSUlyMVEIJYf8hwlEF +DRpcXgS3I461Qh8cfwxsLS9zREetI/Cx8WLsYQCuHtfFpxXECjcR7LluN/JHI9hT2Fzot4RPY20Q +EA61ITBKSaj6sX3RpIhGyJL6Gp6ixVUHrUiVGmxx1fZDPy5l0Kift5OAAcrnoKppTRtdT4+Zsjxb +yP7A1JyoH5fccV8boABZ1e/IL7agxg0D/U5mjusonByMwmgRlBqiJZNYWzBLuCQaoOXWYyuJ1WfJ +lImZ/CqAoQPsQEgRAw20l0YQKxydpR8mLnmnUAVdeqD5cJuSmRFuMWsy1XI2MKjqMsCsEi+52eY5 +DtipRLg4PWc5UVh4XFpY0RBCk5EQwO26YkUY0M44OMigYwOLXbAjAW9AQuWZ49ttlD6xpTQFgXYH +kGhiP+OZ0ED3BxJmz8JYbJ0sjGouMaHYcci0RZGFE9oAjKOU5ETFbmp6ZEjyufC7o6MAhDzibD+m +Dg3/QldIMSRWLhpthOBYRRQYVQMrhNAljU2CysNUdIiVcfVo6aQsYfR4XJ/XI1JtAw== + + + GhxlD6AqCyEVyQQd8Cfwx4pgR6EopOg/iBwui+puClcBtLZUUio3AiMoFK3ghxyjXzmOcpwwSvEj +ax2VNqgV8FsZZtOmepQNMH6hIVAlO9rkIRl2Ya+s7AfbCxTGLmU/r029ngssplIzADekit9SVJiJ +DY2bZE7BIDnW+SEMEsu1OUBFtLHT54XmT08uY8iRVJ9BP6oIO2H8VJ/RpUBJ9SqQdiLYSgxupupV +0BYaavYpi2sL1chGiUCvlHRiP5T7pfrMMP1QGCd2T8cDtgTQClEJaqKMmczPQie3V+XKZz3ikpz3 +2vwVLKfO5AM65K7BlBwR/1J0gc2Xnmwwj8VyKRRdJ5Nwi8L/0N/N0Au247GG85ID+5V6rIVB1gbX +rQDXwrjq2kTvMzq+weSspkm0mcBae43VIkaeCjN0gEkb8lq7tC3GodJlTXoRQQ3Ga6o5OZRMfxLY +GVTwRF3JhHYABwpaosZSZizXfVIddXLayLTBaiMxsc47vONBrpdE8TyoQ5BOMJEz8oFd1aatasRI +UQU1wGlDlzLSttHqzw2z3WBzXDq/AZlhfa5CKMeKVbjpbHOknkidgyic1ol1BF2zJymoe5IXJHD1 +JCO96GkbhnDgUId76cL0Iw2yGbzdoTWGxYn68xCNuJwqXRtSZ4SsoesgbRskeYiu2gtURsJjEI3H +UJJBtQYqbS2gsfXhDUkYXK7YhW9aSJTgTmarShtOFD6tyTp9yVfLR6cbaJzhli4YBrLjqlVhQxYE +5w+HajT4EGDgJUNtQNrQtS7bhsWTZKXdYHcgSmRjYQdTwykmBGkE20yihikTILKDKJyWnuT8nj1I +bryjzkm/tM11VMlAYy86jNVmrNRfL60qOjL3KZlGrDfQvgMLhOoXXI9X0jZ0rdi2QZyGJYi9OGQV +RFCxY/vQO6cN9kxiWKY6L8mM3QB0L6ShmcuCIjaGB1dozz9Q5w2BFVqYdmxPoQval5BIoyFyQkOl +TsDjDlQzljbY8U3kJl0vkdxQL+qH+YbcWrM2odFGX1KmQ/Y+or4ryCzebEjhzym8RHcRNIAccl1g +sxLa+azZpXCsivZ5ASHDdJ228ga5045UPdE5yQia5sJpQ/yKc+9AmWLyp20bunbAtnVOO1SPr85B +Rio9d512pMrizkvggpL8accqJXeddgxLZOG0Iw1b6Jw29Pgmf9ogBUHhtAVNUJebe4liYue0s4bc +WrO29LQh77hB7iDtWF2nDduD53e9lNAHOzvtWLW4XaddONZmRF5DU+iE/qnbYT225J5RLtQAJ6pk +CWNhQcDIwKEGDHyLpnPIIVge+BhqaKhjij1VD9N0AzUsGBmop6kBMjYYATrsiHongT+Y8KEnoFMV +eLY4UXoKVUFE5tSE2gitgEaowCsdXVJ3kFhZ3LXMI82NxldJHsgeExWmyGWLB49BLCaiQAysKmes +OpNQ5WvjqOsvdTIqN6MD1RNDxZOodlOwiOXd0ZoYK4DJp0b1V0IzoFmQ5TNyB00BNS4wC1nXGaq+ +VKuW0IXWUx0oLUCYngc3JWomhXlVBpUuDKnGLQypDvTowzNJNWRAThtuAR51nfwrYkvqKKoqR8b9 +BGpxo7oxpprHBGoioOY8cYyqEalETlWEqnGCbw6FajY5kRryVL1N/8EwPfcwVRFm2l0PTnGJakRp +tqZajlIf1IsRlVXQ30VuOmwavQTogvWKu0IDGAZJfB66Q9ZOx4XH5olVG6rwLdPz3MRq0YWR8KzB +MlOjU5kFA4VJVakO5s89RvAWNTpOar1zNGYrARqKOCTVy4QmFa44W4/K4kjZXjTwdiWhlW6wFyn0 +JiFdP9GmKkzcxdCqN0O4ePXczpPKeKtJESAZyyLwDKYdxicHplxHlfFZmE7L/tfQaTeAzsWDpCs7 +igAXGCLhWNKCpE7VjAvzjBOqppOHglkFdHOM6IuPWCuhIZBqKZPH8H+lDN37rfA1nmryg9gnJx4z +CmmYcVcGGBdMhAeogSqB9xajbtTHPs3azXL9ucmlF5fn335/mZxdWXlwcnL16/nZ5TFettFGjcOk +avb/HwmRahYG1QB86uKgIEaq+DipAXuIi/PliOFkFKZeB74w5HSA9S3YR4FK1oneA6h+EviUy/Eo +xqTXFZwN4VBCJToQt6HRH+5dDKBzYSqOIuW/qPNwVMELLTKtH/D8MfB4sACHBtzqyAZv8CMoIHQo +upijgR7a0DxTlenDS9TQTT6Cry6/gsJT/kYbiQF8g2Bhp8uk/qZykZ4MJvsIWicosImWfE89KNGA +aaKBWvgIEQRWeYo2KGfRBoccanYQkxFZWobfUL/gtxNk2h9hxjhlVRTDAg4vmQiiKmxKsIBH2otx +rIoP7kuIM4qArl3VT2FkX/XsrcCBUl4aKNEkqTKRDyPcL1AnRkrJ9oWBmmdboQvdnw91i0xBNz20 +SD90aJLFb3pQhY7agtEgZJQvYJFUXBtF8KGvnoUYSXgGkeEjtYpRlZcILFB5bU0zAaQcuLIblZKg +qBNWI6HLCcf2I8S88VhC31GlIFYOjg3Hm/gaMGu8WCGJdgYEy8Jjk7CmDtytMFQ7KxwhSNJhHAG7 +GAGChIUENqLnO7SFiUrfYF1ouYd+ihZ961UjIB/yI0F/CdwlGQ/kpdY0o6cNq1ZIXsBJMrdVOnmQ +KZAOXPW7T81wDOOBNxh0rKE1VwZw4gKnRdaLpnb6f8ZpxACs6aDHINtqGDaeBlRAX2dia19XH1BE +5aVsE3Q3ntqtqbPkPGlEhd4iUWKLGHXYG01qzneszwyNkKZjXzZhos6QXhAoNeeFJ4MDCAABhlSR +egVnfg3w2kIYFTwWEgR+uFygAx0QGkjTEfWUQmhCDaxqsMhZJbFVRgE94FQSOgWlXgH8RkDfUNkd +KNNLNsKJ6RfH6D6eG7gt37qOpuyH5xo481kW2aGJGF7mseVkjFpKaQdOvMxg6bgIVIZN1oNzKgf0 +4IYFLhWmIS6OMSkwldOmwZnCtVmt20EQKluvLIiciocwPpjZHHhLyjuCBlUDAjObC1dTxJkkCCSG +mU1E0oADUgaILOp2aIGI1bjKRllh1rijjQbB/3AXoKuF3FEag+CODhUTzGEh6EkIc4DjpvYrcpU+ +BAWiigghFi4CUrg3uIYqtQr8Jta6C+oDM6VDoy34jRBg75HQ+fTSgZmKEbNgFXSbBHVRKm9hpmrw +clRt1nLpzO0R6ejCEX7NPbHWEjLa0CgrIwlLCMLW4LGpIfV0B0CcVWwvC9qSwM30izRgiGAxqdCs +X9HyCreHJN0QR+N3gUbI5AK5JuqGQt9WLJXe3bBx+imucjWaCIIAvubgASNb1U5Ag4/LkFAIeia1 +VPlwGoMrLTzQ1FJE/13r9ocGan59Ffy4Lqj9ErU0AFcqCVNMQekZnBB9DeFPHpmUBVD3XVBdX1m6 +ALkQhCOPY19lUiIsN1BbHE0ExgadOhr0Cj6f4bCI8mI2BHhcwM0KrACCIHkD4RkuIORHqpFjAwQr +mNMCilAFzkaVbSNGzv9fyxL6yhLCgcIhJvBxZ4Faor4sIVXJ9KFINJob/hpgBD2jGRpAjSJlq2j0 +ACGJgcaEKDJAAQIc1IeWURB6w8AvolZwATHc/JH9guK6H9BoCn4jSA3ViOxFDAiRU2iFTEBjGGmA +KbhSEoggUOPQiXKvdAD04QMe8h3BRgytjdW1s3tRtUJZvy1sJJQBiThUrDBzSKSuNSENz/RSMXZt +8KiME/X52VdtbyJ4HDdUmBzHXtaSj2UPgPCxV0kEfkqlMjhOw/pF91I6i5QOjDHp3LUy0JiI1xV0 +RGbPZt0YVzKK/yev1Hyl0o+CagAm4b+BoDrKFpbrO4PUlwt+YOSHkBdBHdLUrUa4J9U0BUpXLEPp +q5NSoO48aAliqq2gEfSVxUQgDxtiDYWnuiigk3ugHixgKT1PEQsNFuAgqSaiZ17KPCU2sgWNdANM +bPYekkTwFGhRvSK8z+HzeGJdy9QND1kM6JktI4YI5QR1pX8oVhzQLdEDGxZmrmWKbT0b1oUW3SHE +08KFAhsaBNg8T5W/q7rJgXZFyysdy5B8iI4DMhk4BEEG8uD3S0d+iKBgPukwgG2PU/ofuxpgjxNz +I3pnwr8gSr0bwRczzDRRdE6FNSaB3vzEatNwGEBSnp4Fp9UDC6v9tGsIh8orRxDB5wSD6NZCl1pq +qK0pdNHT1CNfgag9npUDMz+cFOHerro1xBVTBpQ77lmvkZ5v4Q6v9lZ5K1InS6taG3jYlebDJsY6 +VgaaN6EBGv9vp1erBpz/QVb/g6z+GWSFpB6uW9DlgsNriq7o38o50YIWlNz92FepCuoCRrUqupLJ +JwGlW+FbEWQelaIrqN5EZp5UxT1kd+COYYZdaT5snOa2gImMKeb+B18NBDu1GAvKv8Rn4Le64v2y +yj8mdoFBkw65jLeJGInPGEtq+mhPhnUTqo5U/UfruqfK9DQUKZHzZeSzF6pii+Ia4nwRjYiro2kD +BPP4Fh1BKIJnJNo8G6fDttDP2tBgmGUEyl1Hw1Fim32F9k9fLcxhAi0s7LUCpv9rgl7zNJgTJUP7 +AQTi4SeXe6BYRsCOic7SCAJBa8grwwB9AjO0HnQ6SnIKXmPNIZocIVXLQllnIk0apFpZeAtCXYFV +pqpaDSL3hTwYY/XAEdNQyeuqGS6cVgP7ogf1r0PPcJOQ72ZwTVOkAv1ibKhODPwoVBwbR469zDFV +rA7iBRPmR/PdiEjFTaC6Y4hPFGAhDJHo/dZzYImgij5QTXrAGz7MuCsDjAv7IpwKmSPCjxtjlcCJ +o/i/DVapBp5arIIke9AlQyUJJP5L2xL6YFovDCpSIzq4M8CaSlNoIdLsDFZHGodIStWOY2S/ga6X +BJ4OB+AZQoQA+qrt9CNrcYKmyqawgJtDmGjQZQJHsdjqpwFyIUM5glQ/7TpM06LmZuqnYamEVwkD +oZH7B3nCXGiw4jjVTzOrJ2w/YN1aVGeFUFjDeAF1FDIKRYHaaqPUdQHGtZhGFOuTiKV6CBIH7xl5 +TPoJFAbmx2FiGuvqzcSDsl8O3UppPoPXKhJoKBkubn4THIFtcibh3gTeh96fzfkOJFX1EUdjaORl +qKhDzs6BNzXWjiCcJKL9wTAzFVCE3HUkQwgmY9qWYMvr/dJ3As1EI5wZ0aX1Pxhm0JWmgyLUPWb6 +OGWmGyOH/yYanT5Q0ycD642jK13mA2T5PqKrkutqAkzhs3+xEVQ2pvMwbEsezftMX4iceAENOIE6 +4SN5qqOR9vJdmGiUF9IYIPKE0WNy0fyUwYT9CFYmhiOpSCGfBTYTJWDG43Cupr1DdLoTajhd7CD0 +n8k0SXp17oy3ihzNrQUCkyCqMgDaCJShza+Ow2FnkZlb/3flq25HyU67ztwkQ91pGIYSzYF3OLJF +IQIIZkV6icOXAkYrxzAUwKFLKlIv0AQCKyv+67VlBoangqnTjEqbtoc4Pti2eZF8eA== + + + R2lgY8iMhDDyKHW9cfRZZ15/EXy4V3mjX4SBlvWPXInZldwqmLF45efp70+7x5dfs8Tks9uV7/Sm +N8bm6fUqB4PSC9cUDApXr3NQ2fXDuScarc5csurso/ctRLCUa139ka/Bi0LrFeBZLxLQJfgb2DAx +3DdX6S+SbtHNMoxU34BMPR59RHAjfJpsyL5nF84Ihwf/jojGTjq2+Ey0EHuOKgHgnIf7FqpdF9N0 +1JANtxXGVDEHmjQgj4KvecW7Vpe7b2U3LI5VenI9BJE69ACM4QiKcSL4SYAD8XTX3STM/stcM3B0 +YPSaAGSCNFVwgdWdT+CL6mvAoPFcqxlARi162iQUe42mVKBHv6s50OWsZHhBURFDuLG9YWTKvhY5 +Gs6JIVI/+Mjulqj6cLixVwYaGwmmfFb/hKdDaMf9P4kbBj6WfwY9PPjH0UMZQugPtY1QAc1TSccA +DHrsdMor0EdQ27L6CmmW9PrSFj0EfThyPhQxH46UD0XIcUB57P1/loQPRcAbcLH/Q7wrb2eeM84f +/DiJdo9bBm4ggQ9RP4A+ZPxO71RiqOpiWn3cKd8wgpIJkXGlBJdTR5cYP7tSdMh1rWiDK8Ucqkgq +CHmKd8pxEtwpZkjK7hTcZj2N46ETiGZKonsNr1Tg2xvlZjcKuah5o2jF8rQoCm5U6NsYhfyialTk +XuLrbrpU5kPfmXgMi6JfPyArpk8/IQxKCwE4hvrgD436RIvDRBBwPQFLw4z0yAzG++Qi7AAngtg9 +pOGF5M70yvBCAbFBmgdmuw9UEGXREoFhBGsx4RQyJsABuPilCUNH3bRasRBOTb7NqiWo0+CGifor +CpnqOybrhyBRF5QVyGwFf+Her5CwURrgB2LSSidIFsNcpywLk/R+FdtUKih34thSJUhGiONz6U7m +lnyUaMoPZk5BFpuVpruiphwUd0BSN1gWxuFSQgCw56+nH+rZl578vylINwfqflr82CYmhuu7m3Sw +g++qHjpW7OAZ3FHkGmJKB5iqWEckdROkj2ZAGQDFMghFsHgBNzA8IwzUDwvZIGKvgxtcOVXgBt/6 +3TMtHJGDr8hB+NFYsYOf0VsmiAR2iGMNAHPgXwbs4PnuZM+iarGDx410RYKJdSNjhGkmTB4I4iqi +RqCZk5FAHVBBjSCz/vB2gT7RdRXu+qzDhLxCSMWe0lqfAdpQPIKmhshi5jJY0jD8BXnzkdARxgho +9hQ3sIIEk9EhI20UOl7Pl0bwQlstjbQBQNFH1IDTZ6oF5LKO+49o77gJma4fllPflHzlqSc8868H ++pHPcCzkCIQisecTJtLzGFBAxSe/QXgD0CmMJphy71e+zQqALFDGTastNdgR2GwCH2lzkTkxDseE +FpzIHrwee2APvezM/12s0BCY+yOFEKtimRTry0mkEKpxijZ9pstF0lAK+QGRQlbpyNWSRNCns5oI +8AJSNQCGmA8IeMFlprZAndWZJcZ0+HBQNrzMqBTZWz/lGWI1ykhvoUULGdMglFfZcDoLM29h6BIt +aBq2wrJq0YKrO4kc8LqTSIOUsK4Pjj8yyonJ/AQMARUhAlLwhwGngid033RDm9YjZBIjFfLhTO8x +VTa8DhSOg5glFmSGUNxHHrO7MnciHBuIEwLkyDJMrg+bCDLeFD/0kRgGexYz1zPSkhIjUDVC61+S +KP2uH483Dr7wqFnlMy1b2Szp8u2jBAMzCuo9RRypQJsL33a3d4oI8GUODZweom7tV4GPyFoaVp2y +rxLFBZop0TOWx+i/JVruzuIElnIaE1Jw7bnrqSs0mKTkyP9dnNAMkhugBLhf4O74YdhBCQy/h7+v +ogSDuppRltQKcUxG5UrP5mwKY82xiphiqoYiyzoyvs9XMcIgVYegBDeIO6wC/BTwchCrHgmuyzhb +lOOArznPooASEpvlx4sdDR5ndnZk2448dXHrWlYtSjDcSPigW5Yr8LVqhW+YhDsMEeGAAgQR9IqA +igBaA/zhIGwBj5BjCbns4dijEQ++1j5E3k/mM3XA0cruhRB2QuJxQbx0qELAJOI8XEdjIoEVQGJj +FBJiXnaYU0o+1OzhMfGC63ZKHwIwAhI0JMNDWZP+g+LeUdaPUHIHpZHKPsLJI90sIsJdYz8SsgAT +LPIjQyFS/h3pN4IUfS/7DsFSml8eupry76woAaAMdJaNtgYRA6wg5iLnsz8WloEwoMa4QP9DIpEw +1qF4+P8udmgG1E3QA3hb3CNEUVr0wHQRaaJejc01SGLmsA4X0INI74zCY40pix58JANDGgkaAVDR +BpU3gR6oUw4DLXCAJG1JkEMPEcMuExshy3yfRA+wwHusCGAUPXiZIEEYBXqIQs234ELGS5Px9yyr +Fj2o0kYkyMTyXj5EmBgxEDTzh05M7ypk2Veg8OgeRPCIKVIkCXkrsucoaoiQyDj1KI60yJ7DylNM +ZUarSYCgJqNZMrkfTE3gKMtAxzsEfaFQnxpfSj4MHNskE0R6DV9D5g0ycZNuwiPSazAkzeTIqiVU +HVHW5fMMtK6Jx+p3Sv4RkWIiVn8Cdi/5yGimwYjl0lQjaWC5SFiEMkaW1d6vEJ7CCgxI5QH/rJWm +exJa50+UEGE08ZiwgmtPPk7P3Z564cz/ZaTQDJQbIAUm/MDtgS4iQwrM2aYCXIsFSByXWQtdoyUQ +WBYUMb9+msE01sy6yP9qjPIMrPHpM996TFcZTBY4wfM6UoTPyyRyQUicwIIXwAn0kvE006nFCZZl +SLSqMnECU694mg0NOIEgUlxVHU6IE26kcAOOsa6W4FxjFu0KWfgK2BYhoxHgUJUKSaTAYWA8JdCE +XqCVjBKPJeeSRFmGQKOFGQMM6xqqNjPFDFzfmAqXyRISuh4iJpWRYi7KhmgN7thqZ3s/lG0MrXI4 +SkJUenFCixXiMNI8nCyu2XdM3jm4qcc8L8cvHQ9YAdUrke8Yvoz4CgF/qK0CH3Bh43o/gruWFxMr +uMhtY/FPiPQ+SB6DpNIlX/mIQotYlYv5HlYa70po8yWx1FDkNvLvaYAW4shNNQv25O25F0/9X0UL +DaG5CVpAwn5cIE0eqVghoicWVXuttLRsnNn4fIavG19zVRMpQMzwWHBAs8yj2A1L5sCWiUy4QApw +K2cgttPhFCDLMj491shkcqhEC9Q5Gk37TLSQxiogURNEVkcrhzLplGIFL3Zs0HFuUbVIIdZtNEwY +gW00IE1IK6HF8ByjZEGE2MQKl66mbxbCgWyIeJYwC0KEgjWasMOH3YHO/CCmIn4m8GZFvDkomKvs +jadprCPNI04Dd+Lb+FGwSMiKEqNUPNix3g8F9q0OpRUhTojpSawnv4d4IyaJdco+LY5J/hypQAwr +dcLYVfYVKz648GJDLUr1/oeOONF6FD70Oz1fhajW5vMEYy/yLS6JY61OARmz5Bs5EuYfhjOaRQlN +tiQIVEiFWEU5YkwYwXXtydtz10MPe4/830UJjWC5AUZgskiPWbEzfWPIb5GpPFSUECEXfWqUCHzN +2Q/3XQCRRQmsRsXa5ZAwkHCCBT8dLcFGl1qABLNURx2UAAMbJI2Y+YA8dRUiSqC+0TDzh2IEL8UI +BmwKMYJVNxIVBLnwua5V1eKECPvoJEJllfgL0IPbxYxYcVUobUCiIFTHs1TCgVVYPT2VeUjiEKmc +XWBETS2JBDFyEYgWUFbDR1FNMFpqLGFsErWhekNZHR7SOVIj0BffQ5V2ZmAVYVmuPoqA9nwp15AJ +1WH3CX2/ExwIqZulCZF53ncbDMp7ByLrsxxvktl1ur/yNX0SyhcjKAxfpSlikR4Huoner5BhCNpa +sBqOb/kS4yMyAwFaUEb1fhQEyp8iG5xrTSXNdsVPNEe0VqppFi7ZADOw9AKPPz18e/RBycH/u8ih +GVA3wQ7AvV6oFYAz7OAROyRpcjDkL40zCyUMUywFyGiQFDv4SCeIdCJupNlnGJYI7IB8TMwHg7Rz +8Iv3g44YESRaWZeRgsAOcCAEdvCt6tGFPSGy8ZopekA6cqIHq3rkDQF6MImWrehaVi16CHUntcoD +3LeQjBRZr0IGDAn37xtiBxdV1pWHZMIkqqCQh0hTvjsQsSHasbYiXaBU++hBYSIwikS+LEAH301b +1M0PY5umM6Hxj5kqNVIHSQ7hchn58KNKkKem+KEnkKrZ4lvYLvCPvkUOcYJsUIlS5H4DEjN4Gq4q +sw0REdb7kacaX7h54aiVy4jciGhRCEYY9X4UOirRQROIFxWbID6LNnD6uPR8FLgahIKQXN9PI4ga +7IdvUz8yLRf4ujGhBddyiJr50IGh1qb57z3xfxctNALmJljBpkyDMilDCiHrggrfpEghcJhPODVR +erZQKAJr/CDFCqhFTqyALHHACsx+5dOSquIiSqiB94MDYooUWDsbmddYKQS0BapmRw0JQAqsQa1I +IeMZWE4HSCFQtyZUWgigYohVtZBfUy1GCLiJcZietxsFaumnKzTqMDj06oSG0yQKEm7o2b9CL1Qx +wkHOI8/Y8n0GJfYiq3FEpTiP5ROhsIGLYMzsrXDF8dQgx/ByqPjhZcHAHB+8PMpbRS4qySVe2Zde +gMyzPlEC/PgY841rgPhF8NDYJ+xE/0GJFfiQVkgDtr/kK/CIQAsiGsEkiK8SBkNZnVLJN/BYNgGN +0AGUAYpKRDLUyCEDIb33K9+m5EMQqDWINtsT32idE9Yhc8elb4R62x69PXjfHnvJof+7WKEZQDdA +C8zqjzsEepbhBYhOKNUQKF5gRcY4s1Pa1MQIFE/LgYShshaQGCOmizYaTgAdFjA7EAP9e4EYTNKR +JeAnFLk6vibzdFhBUqtaATEgfWNki7GkiAHySpRG8hlPS0QCNWAbetZVixt83UrHVoeSwweHijBB +JB82dFEk0xi6zFxNATNJCUjkR8ZKGig3DM8ul6VOWExbvZp8VnZlik2jxNvWAUwQ0x3Q5q5FtBmn +rImDZQibHTNMaDEISj70HeZXBKJCzHQ7QQF1xQ3CNbI2gO+By+o/JnEDi9ZRvghQWaD3K2Q9BM7x +ffVu4ldwNGG5D0H5nin5ijlPYVr29EmqzvA1Xj0MyzYFKUWB6hLNHLvSfE9QFsHQNEzsMibk4Prp +2duTD+y5l5z6v4scmkF0E+QAkwIukW8yV2hG/Rib7hWb7SF1apJZKWEzdphhlBWSLHJgDkUkH/SZ +/wye9Ia6R+Z8JHIwcDWAOBh1uAYgICAH6DaYFRFseuqhAuTAwt0F5KDp2hyt1ETkgHJ4QA6uzZrR +ta5a5EAXMUdTvDmMwNJZGmqxYAClR4sgNlYkVAHCseQjRppawIiPCjU+PapQGj5AMLKr3tA+oyfU +RxjJkwNUBmBYFgKugAKRmAPrF/EdJg7gBt+DRy/KZoUhHHyYA7PnQ2G0Qy2l2QqD0O94/uJooCqI +4J7shP3H5C2PYPDH7JH+uewjWDI0SSTKb6fKCc9yAAKLvZ+QbYgUMSBBrbIafkCxnA== + + + VVmDsoXB6i3QhyR60JKuNN4S2AWQYtGxYsV4UAOSMdqjtwcf6rG7JYf+76KGRvDcBDMAseIGwXcx +wwxUDWkdNN8mgu0UgANp95mL2CARhEUMLCbsGq0/DcTAACRavhQvsB4CS+f5HbwAqGUlSSbK9LQ0 +IvGCVUD6SAiteCGTJjyUriVe8NUmEcKAFXhpmuXCqmrxgqv76PiWD0D5FwZHUgltYIA1ZB5RJC1I +uUlLORKUpSKjBHWfD3stAqhcFKNCDRriBdSEE1YOq4Wt1meZ54B16qTzWIMI0OJ7mqmWiCFGNACS +HmNtWluh90s/ManPRugam71dEYMI5VosCr6Q/ce0iCGGDxGkeRSG6f0qiuxWw0zsmBQzxKwRhYp6 +puSjIFHDc2SLwVopxGVFMBcFSkuXFmny2xi8SGANo402xYs10xuTS0NDPCbckLINoYWCREkCqF/P +wf+7uKEZTPdFDolWz8Q1QjRchhxgsUw0QX4rlQqSzGLpaVYQgIOTOjeFWrUC2IGZPVh/MKYG0gG3 +APSAGGpgB890RArk9QZ2YPJv38IPsENoFZBMIcKA56TDNSSapZ+ZmIkdkIcJ2CHUXLddq6pFDvQS +kwOnqyU20kUiEYAmuB8UTAxVoSDCWmwVkCLxKqAYmHbBb8a4lZD9Qw/civC7LKRK5BCoOZaRiALt +vlUUYrGhhzzqsaepevAEIMb8Yy5KWsIgF8QoGwQHgt4vA09Tg2nhEEedExQ5+AxTRt7zpMmYahWE +G2NEBzVUAOr9CppLlJslBFinI+Ikh+ntAxirS2aJzOwhSy1EbvoViIbRpNkmKPnIZ7G/EJk0PMd6 +PTbbFM8WaU9iZSbHgxt8m64GpjU9+lhPvuTY/13U0Ayim6AGJtOEPchkbtFkUXEDWVDZ0yzLSWq4 +9NQ6jcAg1HazyYASJCFwrSkBiCFCgWJ8i0BMIAYm8kGO9jAXr5yov6DWXwJmgEc2MYPVQjJJu2KG +jG+gYxUxAxOme1o8A5jBCzRdYdeqalGDoxvpWGFB8JugVoAZcBbqHXoMpUwEIN2UW0AZC4KHiZV4 +CCMBv0voYhG35AWs6a2YIUT2o5jyOXwDfXgVQZmLmrjwz8AtZfn4wIaHEDOEDrPje5OBH0GgFim+ +90vZE3BYIGiyXnzupfKE76FgI3A3JKr+g1rU4CGCCgkIIQH1fgW+AcZqOUXHTdWXyGwY0neCASwl +07SVvmHdCFIeBfQmADcaB7BE9n7l22wbyNPgWpfJZtviWQeuxJZQGBNuSL2bEmMPP7FH33vw/y5u +aATSTVAD3FS8UPMXZKiBBWE1xQhQQ4ycAUlmt3S1Chsy/2WB14Gm0UceQs/YJGTwNUISTHVzMshT +CNSAEIgMNYRUNWgxCkwfimaghsjqIUn3iRrijGnwUc+CqEElCnq5ATM4rDZSWFQdZojoLiaIJPZ0 +H0UoSSiAB27MiEqTsGpIIhO0ngyGmJF/edbpJTQI8A5RJhnlhL3Ea9NpyHINDFn04JPpQCGBVTNF +CYNOkaQV6UzgKyI8lGKGxLcVruWRo7S2+F3ouLgmTAGRoIKPsY498A+GSs5XdNpvvJRfgNmbFWuA +vwrfRPaaBvAgi+IUJTihrcIJRVfxGyRqk/uJoAdIoik+gOOS3B8BoN6t8JH8TShb7Gvtn5WGW+GF +6qCRuCi2OS6HR9bO0/O2p61HnZQc9L+KCxoCcQNkwMI3uDdR0HGFRnwiLh3zNUunzHWbZOZKVz0b +oVB2TYYM/EjdW326kUessBOqYgt6CqAD49Je6fudSGstaObCukpsEAFAiQ2s4lH4HcsoxH6KDRhj +D2zgWsUjLdbgYCKmIy0sqxYd0FXMEfzi2bOPAU+oPx85ygSERjXQnhtaf0d499i3fQZNCAsAU5XQ +S/Uy8hF6DHi3GgZZlZlELTmgWQ8xpaH6ikVUacXIYB7TIzQADDJGwvWBOHGrA/DRMBD2fhl6pKkB +ypLQ3q/KBRj6WeAHJ9B3uFS5oKHZ8Iwp+QjOzK7PyAnm20h1C2rJhh2zbCiUqUHuV7hGOVGqdxR4 +guQIG0jZorBw0DQUUCehb7wdzGUjZCRxVO89JrSQHrbJDl3PPC458X8XLzSE5iaIAY5LuEFhDi8k +xAusggy8QAfCJDNXuipKIObfzbKJsnI20EIE34KIfn50kUscTwUIZhcWtODFnUBrEQupWtAsDcAL +yG5AvABhhSV8bFhlaKuZYmKwFkZpsgfgBWilA091nIVF1WIF9RGDzK/CgYlg8PMYXM6cXC5K4cof +bhRahybjphxk4KpsEQQYPjKs8S6iAhy/EUJpzREuHG9cwLCNSGRS9iBNdhM7isVQnca37tBhYCxe +9Q2XF5V8KIMxSs0w2ai1xihiQIVFlHdz6ebQf8zUHgHPURTNckq/Qiimi4wJxDhBihp8A/802bEA +CKXnqyDSNBiIe4psRpcEYOIjhy5cLMu+YmlIxFVrMquVppsChgEoGsk94Tk1HsyQurUZK0Nah1ck +1e099n8XMzSD6CaIARnKcYfADqWYwVdbZYDAGGAGJjphcWtybXKRPU8zplv3JpSHTmiQCMDpATE4 +nhrCqIIMGRpHN3qWH0jxAlgI4AU6GcslihE/C7wQW5VjFNp4yjDK+AWW0wReMFblSG884AU6ZBUX +VYsZ1E1MMKFveYEA3qIucr8HMDYI4YKQCuBwQxszZRhbwL9Y15HqahpOEFQO5BmgQCM4e+IGo84Z +cA1E0TcXKBeqqxCiFu416pVDA0imKo5sUKWgeaRdEe7NhfrALfkwjBGr4jFReRDAt82LrR9DHP// +7b1bb2XZdaX5LkD/gS8JWA+MXvfLoyNkNVyVhg1XCchGoZDIDmXZrqpMGZJVKqPR/73XN+Zcmwye +kzw7QpQJuJmARPIEF8/Ze68517yMOUaS0mNLHKW339NxDJWqiRBi5eqqbITXtKZLOZKCmRqSg2OZ +4rWPCe2GhX3qgptrCJQjgMi1fO2dSrNm5Y5u3p+9J4QRK527Q2Kjv1QqsQt4cSeObT/2Kw/9dT3D +yR19wjWkbKNyFRzh4RqKupVi4sA1lEmTQ3FAl2vQWBNCqWVjnNC/NIUDIaQYmBWsBedQumHm7Whv +hlA6vIPgbO9WUGjzEoNGsLyDlx3lLp56h2FVx+hVxwlKp4pDYN5dXNazzsHQYrlssutYuHomgemG +B01eBLUk1yfdjng5QN8ry75UfCo6LzDVRthWGTnQcQz+EWdVxFrb8JxJdLLF+Cl0Cb1rLKR1S4vk +G2bCM66rgHNcxZ7LdW1Ode1FKFkGynvVfcP6RfwRhNih335LGXk159JFAlauLUo2Ve2hoHuGjApN +0dFZrywCA8+5oFkeL1WO9XeILnE1lPAvV5Vm0qEj2vD/+7P3hGYlBa8BXdZ8qZpj3A98h47GwlGo +kV889Nd1Did39BnnwMGMFa2M4ME5dJtyCdWcQ9YwmbSDTSJ7ira+2XSkO4cCORjOYQLKhFMVQ8A5 +aIRBUp3CWsz8EDhY3CD2OTzDCt0sn5hedZT6pjxDO/IJFHrNNSQRMwfgRZJijRY2fHJNz3oGg4ql +MRybEHPMwvH3IsYeziYDMsS0+Rdi646DjANmIrZIg1NxcAYjylCpliNmL8/QaK+IeVHQ4CTgsQ59 +tKaMM0UhUI9GfINrWG60G1qcFg9YknltJVwl6/Kl3wWTkDqkho3OGBHzwOXUmzo2uqGHVakOGGDt +6apg/re79LQXDsKQcjGl4X5llaJFMsdhT9q8wzrHAAZRtytXFsk5DOUUffhbnbsrGb5P+DyKoWhf +yDskb0O1sR++Pfp65cG/rnc4uatPeIcckkYMxHq8vYMooJhXG+YdNI8Qw04goxErx2ZzUu4dEnNd +cu2SL5mGfFIVM1pi0SWL0piJeeQfhmbAxOEuB8Huk4PwQqRxNjnn+3YQTLPhIPgc8hD8Ix5CfMVP +r+tZD2GIsWSez7qQxlFVh9RR2hTLIFm4FFpsawQHPa04ySbuSoa3bkqyCvwuclX0juUhkGdctxAl +vBglPJXFKN2lyDJNYgQW9ZWUZ04leYj1zbrDGkahrDuurUulYxQKmGZzdiX5h5D1VuulfOYdfaCK +7CgzSY9c1MUicMDOkVHGkR8kT2BgsLmyCPZgopsureA9ULUOkywJnEYic7mqOHRuJG63rzpxR4SA +BCyHxY6XQjIcCIaxn7o983Llib+uazi5nc+4hmzsiOoBHq7B+pVDnUdhy7rmeeHrNS2AJB21RL9c +ngFi7qKjZkgPAn0koKnZxZT4DSSO1AVv/SGpUBSbjMGFObmQmyUV00uRes7yDPUIHSRpJM+QTerP +umniBDKY0yeX9axnMLxYHGWHixC4cbUFoQpKdZXyG9sjjeaFBrX61ScOXRSQswTyGFhTopFBp3WE +BENHC8m5jtZ1FPZMDTcCIHNKG/jMBf2A4xjwMTdAniEZyTZtRMTJXaTq6crM6HQSS02hR1idswVy +Rwt9O52KeuJdvQ1ZNQIDv3649lkbvw0Cpdv0koUBy0knYxuFH+JyFWQeQ/zOalb6JPZYN5JVpUG1 +crlq+YfIDV7+ITmw+txtyQ6fGNkox1/IQexK5Mz76fuzv/LkX9dDnNzWZzwEVWdMaTxwRTdR9QJk +KOYhhFZkhMbEX9YTrXa0tI12gmRsqCa5/KzFDmoo4iHMZWQrEOIh6HsdHmImY8qaNkkr+NMwlTNj +1PPhCWLe7SCgfqQdIjYrHIRQyA60uLiqZx2EocZW6FU3lInHj/PrKijUlXEWlXJmnz40kcLuZoLP +tQZnT4bLZXwUitQOYqj3TdTQxF7NSxT7KSbrxBO+u1tYDLFg949uHgIMGcNrcy6jpMR8uRASu7Uz +Ae5OyhrMvTpPw6gaCSvshRNvuTsP4FCRu+XfrqwqNhnXcdN9eiBQuzhBKVe0a5cHrh6iDzrcXv5c +d3i0ZpI66163K6sK9cch91DKcE6IMzeFIjrV6cFk/gt1Mnn8++H7o5/+4K899td1Dyc39Qn3IM1m +jd/NB5i0qLa2Xi/BmjgFdTPkHeSbbcwsbvcAXQDeQbg0WL4YvMY7BG9ZaMAPbg4w/9s7FJvIDkVI +V4iEmzmH5nSbzA3KO5QjfJhUsuUdVJQU4fiUdzDIw5OLetY7GG6MUY60UbDUDVcEbUCnmkfSWP6Y +iYER9wlzI1CnTWyv1JehtvVXUB9cNsdsA+zS8g6VMXXC/JbpDqvkqjOIBgDxr4QkZ5bEUk6OlO5M +6yhTL1WInnJtJcC2dyaXmblPwcerIIcAeBgAmtUz77lrjFmUl4DbtJUu1yVDRoxoeDcPBbLmjUoO +TF9eWbaeD+ctQ110hY0WqvUiootcexFV/8WyghzV0Lo6N2/MqTuze/CMi9TyYl5iR44+oW8dK4GD +L5/+6zqJk3v7jJMoGiu2ZG87iRbFzRWSz7QNYWTibmtGg6/TBRuxbi+hjgS8WxJ6hw== + + + QGIGlSdb33w/YhGg/TUfggiUjnATlnUWm+rCT2xN+Zh2FFHq4SfoZctPuHacqDDXPpTS68V1Pecn +mmHKSCH2HBUiz/d4rq48s4zS1cYa+uC2NaIXK1fMK3bpFdtyPEYYAzpjhR15GKZG5CeAbNqzSi0X +gZdgV6jWKrRJUgT2qHwO192wIsRsJkQLnDGIzOJyZU+aV5ASNRxtPnnIFFMLzjOuuY8T72pFhRTt +bhCcCrB6uQ7hIa2q/aBvkmAXb9cEBriyrBJpZBm9oN7mLGCRsDtbu8b7LxeWYoikkR4I58/dHfXW +g66wvRRJ5HBJh7S16wwbRchyZQu8qrM4vcHPeAuzqfKIVBqeb3zFyOYrugKIuPucwXCQtJqg9nJf +saIa8RRH0afBMg11ML6iRks4hHTAV1DX3r6C0hM0UVUA+YLLKeYrivuK7mOZXquUEyuWcAyvVfKM +8BTN564eXdOzfsK1+tYj92cOlRN9F2JxHXUQBXRzFDkcG6P4bV8nm/W7EoydMTansQMDTP1FfoJD +GjHp3iQyRWdI0jsKpIrA0GB+IvU1KXiZkxB3Kv4SwG5Hm+fKUqBjEO/hOSW7ONxL5OmF7ITybz3z +ts6/Eqbxg/RQ6PRfWVh3JzFb6mf2vu6mYY2XycbrFwp3CwwQQkO27LSRpRa7sSsm6YBLriwsHsQk +LzCcv0NF+5iV+YWUKbQXfCPsbZBsE1zZAq/rJ05v8BOOQtO7mFV+RDXdjAwyyiLyMKY1Rvit60nf +pKl6Kb1zuYpk/QxcBezkuIosUDHysNmyD6Hul6sQacN2FU0QxHcz2o5Yx59J0gKqwlMkZqyeeArg +m/IUXrvMdSr5UD/04qqedRb9F5vEY49ZCemtFq/I5XMXpDMQ0QYfzQSBNDajxtCBkgodt5jEgSK1 +c5IkK15GiBA4LDm525zeiVGwNY1uGUQR921tsFk2YGqqukiLNa7tL12Wa0t7QHROE48J9NCeSyRz +8+khWIDC1cVP3tdKDYYXhNBXCIArq6gaZi0TA7HZfCtOsrkO+jSvLiwuN7SchQI+o54eQqSuheuW +15KuLmRoYyiOMVbIz7hBxZUKhuZDXiqw2F1wVUO0FbJvhCvb4HUdxtlNfsZfVCNfLPGhVMGWRt6S +tqV6ziDaYtxt0MDuN9U02L/cXVSwCiIX5LhnAh4SOkEzVQPLqkMwpj/jQ2DR5CwEm5K3aMVyEIli +4S0ouchb5LrdRaSa1E1GRdtD9THchVAdTy7qWW/RfuFw2r6n8pImLaIoR5RFp5KmuYu88fipH0FG +p5XILlFNMyaCc2YN6zpXXGYWXjZY1+41c1yMEjdX1Xfhf7NOocrHQ8UGcxYDugLB6iNC0tOluz5d +2AbIRUmUEPNKsuoj5PTLJdsQQYFWcN5+TwsPpsj67qlVm97jxapsD31UU7Qze18WYan7hOwxXVmH +vjYoo0G5MfuyZWlatYJhDZldrirEB1NOonnqce7GFMfgoa/dXmque0gsSY/dv1hYSRn36ZN/XQdx +el+f8RC0+DCm8IiTmnAfF6G/jxQoHfC426EQy4mXnVJJ3h4imYMwHgZo6wlycBDNJouYQy/yEIRk +20MstysXoWkHNO6BRchFNOt1iL7QXMThIdqY5iHYE3iIagL3OuafXtOzHqL6nezTIRDCnJgmThdQ +ZnmDEawmj/qa7YyRt68YZRrMasB9GHOY7zojD2OydYJNZyCYGzRn6RBx4i56Q0Ke1mHtgLruKpa3 +3tdntsAKiQr2fh2402iSrqxtDf5VuNMSRCmZioj5iZKnQb5Kz0reb7+x08i3Lij7O+aD+9V14p+J +rMsx7dAgSdbiXjJf5doycB2oD6lNGafr4jRriGGDU9iXKwu5d1EFDrmT959xg0DAZ4dk9ZdilpO8 +mO8G/1ptJ4xr++B1PcbpfX7DY0SX256GpF82phHvw28gkyTGhSK/oTiOso01SdGxCdVoxWrajgM2 +KzyHKc80ZDyzCpwmjcUhQB5dHSt1eI4mEKA2rTwHDJpDEpDWCJFUVjU6S/ccIryX5/D6ZqXKiOso +Vt785Kqe8RzDk7p1FtDz0qYbWZ2NFHuO5jB2GDHb8ZUNQqM4VwHBoUlUIGNobMIpRqvlKOBrvE8k +9d2ZWlG/MTQK5f2KSoMRQazzEq5Vfi9xaorw5craViCXZth6HW4dZSk7O7/kfd9/xvuWZIjbIU9/ +alzqLxTd4DYfCdC/f/+XHz/+4Ye//+2/fMcvuxWcsdXPehR/Flv9i798dCH/6R+/++fv3//P73/8 +zd999y//+Ne/3L/zH3/yd/7zv/7z959n9bf36A1j/7W9h/3vm3/lp6B3PH7DLvyOz3enT/r7X+iX +UW38u/UpmQ8rCcmCcPw3Qgkf9F//Zfyr8Ml/JT98v5aHa/8l/9V49V/Dr/y/tXx/e/z1dPnrI7z/ +1dP/+FT+7j+1O49H8OSG/PrGgrl/8atv/6Ctefe39sUegt3mr76VuDfUmMwZMrv1g15FSzpLnVhD +bUTBxaQGBaDmLG8SJ87GpvZBq1qzYQNNzVAgAOJOQxGAvMKsZcTi+RLIfXRfV4tT7zFtS0AuaUNS +OjJ1CiSZXkVRZTgHEy7ns4ORjeo3r+iBsDQZrW+FLtrC+U8uTm/HLv7q2y/1tVOUPsEUlfdXMcMn +tQv6OmTuMkCb6UohX33L7JAKlpkWPBkp07NqdsCGntVWCSQcIKWNR+wbPYiV41ITlwzumFD3zKur +21w+sQBZTpNqxnSt7i977/ef9d5FcGkVURhLs7f9b7bZ/o287uc9lD+P0/3wwk7XbBWb31v2ZVzv +w2NpDy5CyQ6Q7FzMoN0NMKY2jGAn9ukVs2qRVrdmYJIWdLeiifuBaBT/KdPKM41R2X8+SLumkc1S +BhimG2R+IDJY0xhP6La1pPza2zvrTgVLBDUQs/7wx8OFrceKHkmY3edkm9gkmUK9uLZbXsCqYnEl +ecsFBpNHp7QYbTZbn0kNufboyxzKepbDauuoZ2p0t46/+hadCLBZGE8KHjROA+yA6JGaK1pty1WN +/m4En37DBimPd5N7JbPttV9b3UMUm1G8K4msOY7phkj8qlIKFy/I3tX3FkEZ/BQ74PrqW8D/olgH +eSwa9cuVPvUMSGyz4Jz/0Gg8SHvV3fefwXN88zdf//qvf7m8xl/838thfPu/66++DfHbGL/9xR12 +/dcxYNK/Piz0lBLxyWf/53Ezv3wdN3PDJk45GYUsR1wSlOCt/+f/vvnjz3/2h530KVT5NFAJd/9h +ffff12t/vCt3f3P3X/5ruPvNz9e//b2xDT8NYb4kgPmy8OXLgpcvCl3+lBTx88OWLw1a/tSQ5UsD +lj8hXPm3TBD/PYYqf/b8MHgxaJi2DTYBpnMbOjoW0pZw6bJOk3FA4JLzI8GBYhJTbuo5Fheqazap +X8EqYupMwGDqqidj6tTnt6mD55Y6gXi/oinbAsjWeAymXozHQ/jubeqU9hm4mt1NHVJeRNWCa5d9 +cl3PFpKL30vxAXMvYb5SWJLKyELLg4v2zVXHrh/rO9tofOIk5tzlX4oE9ixUiaB7xWQFSpmPlhg0 +C06YaoaHK0NJpLfD4KtRfa4jkBI+M6lPFy5rh5MVIrqAkDFE75geQpIp+ow21ALp5nu6VnmxSxyp +Vkpsl4uEBhiO7zW4SxspK/BoAz74p4sK45RzN6X6diu3ry6LA9bguvvKXoLVL+9Hd3zVA8xXHt/r +Vn1vbsoTigFqFzML/kDoJ3FiaPdFzYdlw4AyIOUx+TGNmDCDksOhJBJBTGpQpVuzP8Pznn1wTYYd +TJQwOJ2WDFvC18Dn6z7DbT6jD0EQXRldYpftgRI8mWCAZnWwa7KiihSfHeGPr+lZq852AzNcJnb/ +ctPQ7Ao3KC+zHWY6IKv9AKf1nHxTpHzXJnyvRCzwUSdXFxJZNle1nFKUSlZ/14TkYHKopew21tXE +FfbS7RoRlybpnMFY07i6dpk2dwLGvtBAFBe37BTK7nAODaifeGPr0jAqbn3glW2Gn/rEATFxFYmd +a0+dEDW/InyeV1ZBSN01924V6c+4TFTHqo9Wq932Mja+x91KP766jV8+zNe18Vtb9IxQebTZD6kK +71ZOBJqOHpebOLpkg3wyuo1DwgtCHBVBt/Gw8lWBVo04bi43U8XZadIdTSQDNtEy9rxbEQkvAiLC +PMrEi8J0G9PHwnVyo+d3WHjL2XDqxt+rAxt9s+4H9yeX9KyJJ79/Ygq1G9iSpkByXJZio64xOXVv +ym1DyfSdbQq4OgWLhgItOV+5bFylFkx8nT8eNPcmwB3jX+QlCpqDLGZCZOr8OgThmvsOME0pQ79c +22Z36AQo+ZXf9ryNfJmvK1isNFiw5JvvbEZegkoEdHDj9XfFxp3EZhSn4wdAZehhJrFzvrZuWXmD +hxF6i61OdPZKicGKUayIEPyFyLr3yKLTW/LV5UQvn+frmvnNbXrGzpsrYaQHTgwyM8ycqWTpC2v+ +x5JczSgCzACz2T3abtUmroXUGps8wdNxSYLDhIaYuUSlczvMPFmMHgzkF43MrIPWKFZEVPGwF1PD +25aehTKPw8i0EAHH0FtzpNXja3rWzqPdQPp70W/gYNoAtGW0+LwoBrcptbrR5frONsX6mMvmATau +CI/3rn1DwJq4+Zi+jx4ri2BFsBcQIhiN5IrIjdM2cnSOpJ0QmCEV+PNy5frL1WALOVaNhu4YfZ1K +zEdonERsZzff19EWWb4BDo3ux/iTVUVyxer1TgIrH4R1eRFSuHL1Kks3MAgmXo5Q/dx1oudB1t2d +dfqFTNwZrZOT7vJ1m/jTZ/m6Fn5rg57RCo7GAVHrODAZgNoknQfWGgMfEJeRahU7yBPjQtGV1tzC +qRULAGZTJBNnLPtuZt6AaKtwAPkwb3AvmHfI+xhHrGcaCYLMOzJmUoxlZ5s3lTfIOoez5YGBlsx8 +2fLhj6/pWQsPfgMbsJEg1VmFhjRHMBWZeE1OILJi7h236zttChxbY6gMnCQYnsK0jWycSJhoEA0H +S22HISkEpk+Ob0FrAFOb6WDKW/c6aH8vQ1rXjl+9XAo3lMH083IgsETEbeRtuOpbXJE7t/LmG5uV +j6YKG7kTp/SVZWU6iW018VdnwxhBIZ5Il+LVdd0eLnBy4qv3n3GhWbPHBUqtWfqLWfmu3sV6fLUH +euVxvq6Z39ylZ+y8mYB2gYFm27mo7rDVYB3BBpHekLWZ+G8EnRXFvrjtvHKcJsm7Gap7JE/KWzRO +U3HaMATS44Olq0A+w9zneJKhi2RWhh7Ev6RQxe1cUE0Q/iOZnWvStWaTCby4pufsvE67gzWiBWV3 +sFAVkN5xNT7MIu4J4ybIe/Rc39m2oNIw1xEEHC4H/I/rgYNNg0Zq3TfSj8RcjNgsGefqluFGWgOj +Cd5uRo4CEZWrvFwJXJPl2soVGDtfZE40PZPHsUzGzG4UTDFDHHXifQ1uGYz1XCoJfQ== + + + XluGjYPSbCbH5HRY60lNDSCpYnFlVSObNgunhvP+/FXSUCRk6EMj6i8mubWfZjm+6km2K8/xVQ38 +9vY8pe7dn055DQ4p/gkCCdk30NYBOD8ZjFe9mkjk5bZKf380ibgVtbwYHh8eqrdunbMU9tzGfOic +hapTeaguZSauUD116XQSU8JRwzjROEgmEqM9cOtqmjs6ERs5RPSz/PFlPWvi3pmA8b77PWw21rX8 +xeaunHtIeP35raSh72xjgGOA7rGvhCZJActbaJFJCtJMVGSin6gFvg6hUdv0MpZxOCfY39zKuTOi +eI7r7xWLuJ8uXVbOoMt6zjmh49ud5lWJWrOJiJX4zJJOvK9Z+bom3nVZn7oYl8uWkVsfVf2pvjkx +hz6scV2Ma8uguO7ilJPe6vvzVyldpSEiLUUtL2Tl83igx9dt5ReP8nWt/OYOPWPl9LwBG4NPcDNX +7itJ2+nxekH+jDkZO8WRaubZ9nBI5Sw7UNmtieoTmE6pAlCLxAgjJ3/Q5EU/qm5pGk91T3Ef41HH +uDRSzMaZzSjGNbltnHHkZePdikTZBjikAenn+CcX9ayR+5hbC1R57RYqiw2qrxqEA84i3wsjbS4y +fWf7YiLxtvbDcmppSCPCBr4BC8jDIdKRm+3+ZmTMK4/N040NVXiI2gkQ3ci7JtnWOcZ8Wgjz2lLY +Bo1iI5M81hgPI1+Ho9Exrxy5lHLmjc3K16aRzPk7cfJeWVag/IBQDoLSdtBir0ibWWuFbNfXQU3a +dJhLfu39+evMXNAK1JDvKi9VdRPbsT3H46ueZr/yMF/Xym9u0VOqus1GCsCPbSsnoebfLFlmknZl +MNqr1WZrlNIsOxc4y+089K66GwRnsnONYcnOpz01sUUwKEHJ9zB0o45UmCA7D7JzxkvNzoeV3UoM +R9kN/ktwNr2Yma/Y2ZRvxEn39KqeNXMfVmsVltGg6QA5C4KSxiQ4WyKnfYJrDvfhO9sY4rq0wtVd +EjVebZvYYahvgEJkLbaJh2ruTcxqw4pZIA5HM50nt/NN/o9eDlW1em3tMvT1e8yDLjug4lZ2Xp5r +isYBs8Jx+BBOvLMZ+trgwhuQU+Eqrq4jgCAKiM4cyfQ+5+Ay9CnK5mvLqkWJ0B7sgv7ZC00IYsAs +Bvt4e6mwve4IbSsfdi+0jCvP83VN/eY2PWPqTDhmYrfms5TVeFJMhtrj9oSlwwdXTAmzmV7FOn7T +IWYDIIK9jZy7aAlbEzlDHtWemoRVmUZdv3JYOnexw1L1YOpZlRbroQRKANmokcORmsfaZeu1R7d1 +aBlwQb24gvbjy3rW1n3MrM3Ws9/E0kTsX1cwnE3nrKath9jCJqTXd7Yz1qcoK6WrlPwTdSOoa2Tr +SU1+5uCXf5u2Z4loFOEYzxXkJkw9N3Uq3dThJ4Wh+R5hUXq2+dralQAxbbo+Wy4hSoFmm3qnmyZT +7x1swYl39snprjaF1A3GTyyTfA6m3suhkNUrxqn8RKXGy3X4ePQdis3Yvf+MC00UN4LUzsZ8KZb5 +unmZjyd6WPrl03xdS7+5Sc+oVjG/mOHqTeWwdA5fzCUnA7y1GDH1omDvXixq62muJ6O2q5u6GljL +1C2vbyqYytLROYQfaBKSYek82W3p9LlBrmXPz+F96iLrcEOHEwblVMjSt6HDSQ+BPaySMnTeWhSx +0yhiP7mqZw3dkUWQKU+/h51CUVCd3sWJ+iFTtbZAf/SdbYxlXwXlXvoOiVIgp4OzuuWgOCOaJAab +WJOj3I8h4BV1LThm0K2l2uuWTp0EUFIGqzkEMr5cuyyd6iTic+ucdP5Vs/SZhm3gtV8NrHLznW1g +euXKKrKDB2P49eq6Amc9wUCOh2xu57S9R4tcMohX1iHAmdQ2t8nuz7jShARZE8ZZAyEvZOtejPNi ++661zyuP83Ut/eYuPSVcWdRgFs+fW3p0fTrnYA2mRw+P0Qx2qHd17KoBVdzSW8zSmZFktRgShofv +6h2LbVvgGE17HJbuxO/Isbqp60zvbVrXPNapcnuM8dC3DnOqFGcMUKBQaMfwuYqf6Z9c1rOm7gCj +vgyi+E1c9oN3r2M206arU7knmyGP+fg72xkZ6fa+AhQU05jkBwwjUycnzWbqcxjaCzosuyEr+olm +cLQMMbgZdvyO8LskEiCcoPaYr61tqEdMctlclgGtVGIf6iWmaZ85hRT8lL3xzs6VtkxV7FDIhPzU +Jxb2UHH/1rYD0ET/lCFzQBbX1hWr+6xPKpKE959xpQxdQ9Wgomx5sWN9V+S2ulieh7FfPNDXNfab ++/SMqBTXl2HPznUbu4hTFQUHG1mpcMJOkhdL1fUN8dZoh2pMqaZGCZBetj5Kk0ztOvmHOeiyzLVW +oyLYxt5Mlj5pwEvGPmTsGo6RrVtJTvX4betIdUt9ItmUnWC4fK7kx/onV/WsrTvKaFDzCXc2aCjm +xZZCT6b4K40Fo5/ZwlJp98/pIRTi9LbekSZXL6NtRmeJLAHHlXNkC5tORjBVDVWxq0FVGj7N7Zxc +SepIRXOKKV1Zuswcb8wVPBKLIT5K04hFUoQq6cS7OjdaJF9fNp5Ew3p1mbgMifcp1ajnTh1HNTy9 +39VlxSYykXGpB9T11DUmiNJAQ2crDr2MgbddjNsqUclAjeXKc3xd+761N09pxiG/ANrhOMrR45J1 +qxqGdVNcYU+AN5EI9bJJ1AeDkyu3YqiVxGinEqtu6FhZ9zCf3Eljq5zHgWVP1YL2SOHQrLsrapfQ +i8wbhi+eb8zHkEqAnAeMXfUhWhXW+Vw0FZ9e1bPW7QijMRjEsVsoxhx6iQIHsB+yMxTTDjiEyruj +przg2NenQoY7KSsxqOv6Y8Nou+AKjBa0jyzCKIili/epgg2IEdGagTOsLGAIKG8NtF4uXME6k41w +2a6bZ9LebuErZ6v6mCkJ8XD7bZ3hKKl/zVBuHD+xKrljqMEZldEXRmjhXiM5Hq48XVeOrhpDKe9P +XyTjV4iIdI3pvtS4ymY+obuzv+pRxisP8nUt/Ob+PKPdAjUgqMWNjKkm+IwlKTLFxCl00qdW4LRM +qRDPMFCU8yEZq+4OQvPVTFxGmqX1Zby9AkgsE48HnRkmnjRRFlRlNRPXAV5EMISJZ3vC0h/YJk77 +CHUYAbWFGe8iSF3nqZ3gj6/qWRN3dNF8uIOqGgv21xW6tXrIhIa21YSDq72tXYE2LQSvbOGEUubY +ei1BHG2M9Whqnql/Kgps4DacWrgRMGJn/Irbd3bfRcUcdPi1pWTjQP1oHQZoq486e3Ece2Kqp555 +W6c+t0QkIano476X62hzYN/QGsi+k6s5o/oSrr+b2BCUjku05/35i+RfktHytxBeSF5hHopoLsIS +QzsM/MmDfF3zvrE3zxg3tfPsAPRt3V3ReYQYXsaNKjcz3qLoqxpoUH1dRZsPW7qtSrrNBOahqIAO +FusWwAjqA8bVlnUHejXbuh3gIkVns+4m6842ihRsJrVni3A/epcfoTpAtHWfjjnoAE9CUz+9rOfM +uzi2aHZomINKuaFIJ6GvdDvJhtvRQVtnns8o2ne2K2B5LgA0qcgGdNSrmzj24ATCs4bsmPxiRF55 +etgrBuHRDIjuJl58ynedzsqHri1dJs5QsBRuKXtv+57C9pP0iDrw9nuafScrY6Ja0D07ulwXVy6g +yD569p4yI0hDaYhZzJV1dMmmSI/Vh3l//hqTRtKLMWSnUzxnZyzcu2fBycvCdAtPV57kq9r47Q16 +RgwBHsBcJU+zrXxlrqqtS5JdZo57XlGYql6Yec5Bcfoc28gHYzEot5LAizp2eJSOsatyQtmuUnRp +B449UY0Tis1h7MAbEQBuwR55RCUTXa24SSOwbYSuXItQNt5MXyk2P8E/uahnbdyQRZFJhei30OSB +inp97AeN1WgnjOLJuH1nu4J9oVMI/bsV4xWaSJJlDD6jq+HpZFZTosRnKBpWA4wuX6Ge9fLUOwuv +KFeRlq7jmatr7dradafBmdJCW2++bKJtKwchk+WLkC1M6cw7m52XqtEkpKoErLm6LlDkxs69FcYs +Ox1CNMiEEryyKqPlNCSgUA7w27nrJOlKReQiYpR8ITOfbuajHl+3mV88ztc18xt79IyRI56NPaTN +TF4N+q2x32TYt0rJhmJKNjGsapK/dLXdxtU8g1QyWpmNABkL157BwkFOYuGPimw0A4CwiSnALFzz +CFFcsdl9Ss8mS7AtnJOgoUTd3cJnld5JqH6Kf3JFz1q4s/aExplhd692u38ry5Eo2Domo2ffK4OY +D99oQ2BhSRI9qNKOATIoHcKr1sdH6NGO8BalZT6IjpOZGYUHzGxupuGJsIDhwRoR7uhX1+JK7YTL +6wMbcsgNPGs0DwPvdmdvv7MLmtUuxl+lquXqOhAM3fL36KhYSjcqmdHHLOnqsmzc6VAN13Jk4qcu +dD1xCcSCpmC84WUsHCkFe5Ll+GoP9MrjfF0Lv7lHT4mVBBWmGg1Dt/EKJAgb1/AfNk6bEGmD4vU2 +EfsitZwcw4ZSIr2OFE0IUXq8weP1bNV0KXFX74luQ+cPNJ8jl50X2XmoNotoOUOnU3mYOTkjKHkR +R2QAAeOu8r6uv/7JJT1r5IYnWq6E37QbOLLJnXFTFY0/aA6sywmPv7NdIZ5eShBQ+eF7rNSWyR/Z +SrFTG7ZjfDCDywYuUpin21w73HHS0HAjnwYIWHtfQ9hOofFk6TJydRzqMnKo2Rn0cCNfv2DFg8T0 +z5k3NhsfTdM0jP1sG3+6bJhKLjbeHRNLLX9Kp4CIvLer67If/tl0cN5/xoUiA1m1RToUfC9k4y4i +wAbdX4PJgT59lq9r4Tc36BkLh8GJwV02g1t44/RUhzmY9FPlfEWHuXU7xwNhN1LJZR4WThqPyGET +Yp1Mz/tlh4VHs/BOQ3xbeLRYvRpLRTRYNXqztdoTF1kAo/yxHQn5AFLfjJ1SRs5FVIIIP8k/uapn +jbz6PWzMFQbhKsNUSxwlYIVvve7CTKgby27f2a5Yp/wKJaFMRx/WWwiy8nVsJdfNCMVDkyk+f+jP +xdyCsVGSB41OdczMvAVv/ANcW6bqQeyTtcvMp9laHhEShrxr6nVl1lZU4lCf8cw7m1BhaHYWA5S9 +/oGH9eDJ5beCKjni1pss00tnT9chM1Zk5goX3n/GhdK8odAk/e3+Ukm54Fr2LI+v284vnufrWvqt +XXrC0NX90oT+yIehQx6AoUt2o1DKgduRBnk1vJtAqhI9Tv2QM01ABhKyDVGW3pNn5cVhMBndGCw9 +xIfeWcyGXZuHoWsqQXDYLCqjqcYJ/fZt56gQgJLPNo5WmY1GKSP6Yf7JRT1r5wYkiimgOmd3MDf1 +xEYb07rfy5R80HQZzQ7ZywZMFCw4GSKAJEawJMO1Mguas0/Bij9NobWOqnWF9LrUbkZNgunUsDtn +TOIrpAQCuva4sH8XS9dtDH6TB2C/7nUpARmCzWCs8EqY9Jtv7DLlkkm4l/Isz/7KMg== + + + MXMolx9bSR3Kh6F5grpSvH59XeK4kJ2XUTeE/dyFSv0miqdYAqUvZOe7+FYevuqB1iuP83Xt/OYu +PWPodFIyiNh89MhF3KihkGkxe6EHGk2eUmzTk8ojusXTqdsk99Ml65HlIUVB1sTkWGq1DlpCVBje +4TQfOmihqd1djFsumoCxxgyj92OQ+ezZSoHb1OnF8tyz00jQf1ufq04/0j+5rGdNPftNXInK8JvY +nOIvoJoiU5/q82uMK3g3zb6znVEIgJqh8OBsgUnBTB0WCRIfI7PVLl6ZdNYurtLRxOB4m2Vxqne4 +qad3SUOcecWw76yuf7lWfNM1U2IfM5vCm5l6C9WryDlHZ2m49c5m66XR5bpXDtauvivD4BIlpG63 +G+VgkeVmKXmqoHa5jmuyM11yK+8/40qFgyCMg1NqvBSwte8KXH746rZ++Txf19ZvbtMTti6ChrUl +M1Sc29bhVaS+GbJF7wVmo7g1+wiTqVMyjgTT0QeT8BFgRUxewlc343nLXhSTrTM7UstB42K2HtT3 +zmpsmK1j6s34gPCpVogTm7Cb+org1E5TGU+mTv5SkaEJZlifXNaztm6Qorh2/Sx+ExHhDRLK8+Gz +GBy6vn5pHwX6ThsDbBBcGAMGrqKmsQ2g5s4cgNHcl6ooJTKy0bWJbZ4EhElRCJ1jnQ+WPoUvzYPB +zZjqtaXERgzhQ9axEoWSxh5AXRF4zK4iWmzE79Ybm6Gv+1hMxrDbuNDlsiHuEOy8RyeTAiml+85Y +f77+bpGhYCkOpwfet3PXiXIaI3DKCMNL2fnYdbi0v9jDvPIsX9fKb23QM0aOUgTW8IBo1TigcKHd +A/eqMrtxiBEhMzJKdWQD0TW4MyQtrnkCMTWHZrV2pfUtmJz0MusDBwtp+rS4XQ/RTHwd48vGKxgK +2TjjxXiQTSQxjMCCSZisE0S8wBMTF/Tt6UU9a+HRb2CBbzIIdRWBX2msJuxRFIhntRHC2Oe6vrNd +QX2S0UpiipiR6Y2eoaOCKlBphElFlAlqgayb2LibDjKBTAtbY0rXjZzmggLuoWnu62tl5CI5hlrF +xvrcyEsqln+uo7LmeuadzcpXCqBiGO009bAv1yHzka2CtwfZNLXYVHAM0yhgLpZFS9jQFZ9t99PO +XeiKWpSZQxMcXmz4dOxTPMzjq55ov/I8X9fOb27TE4auSYUMoDwclj6DzZmnHI0USucyt1v60kqG +q6TBNZHupo6jZTq32Vke6api6NriGDqyaVW0NkcpzgfGY4r7KI+YeSnVoVCQmMHhEeeRoKuZ04Z0 +dmXnndqAxGv8KP/kmp419OB3cIoAygYkuLQgJJA/+RoUIQWd4I+/YVNID1wtTL5BndxF+fI0CkUA +msFKyYJVTe3glQpb7CxKVK44PbLyCVnWPdA1iM/rtaXLaqC9W16m8F3l77iVt2DMbzCD5XnifV0P +vFA8lVJDateXDRscVpV++mA6IdQ0I+8esDxdJkVRTFwknu8/4ypFKLmeLpxE3OEXMnHxKgU7wcNx +kPcrT/J1Dfzm9jxj4Aw9YwykyNvAm02XJ3H9lWB8ahH9Fyo7W7MRvz1m2QYuMhluDI08ETcnT82F +Zw/W1JauQkkPFm7RullrkXkbrzPmzcqejIPbzbuNFYdLCNhJJNSFXR9JGNiLK3rOvLPhimLhuLDb +J9lS5uCNj2OSbppx+zhKeJhLKZx/E/6bFdFFmvfB1bsLgJRmirwzB+MviyrkVZFbWLQsWsVlLDU8 +ZOTFSLDuQbkwLZ6urZVxw1K9gslsDHlu2320om5AhqixnHljs24I73nWy9cab/LlumXdIIQo2kW3 +U/RQ1USA/Uo0iVfWRavlDjShypGQn7pQqAMZU2lwSeUXs2/z0qq67OILSg4Xz/JVrfvW7jxh2xPJ +quyTpbJtjDKbbQt5VoLxZDMTPcUVhhRKwrRFqOmmnaaJ6YpIX2TNwxTeFFpo2IBHa5OlR9UtwqwF +RjV71q9Zi47aetoGLthyCkcirpIak23JeSP66OqjiTX06TU9a90OJypjxOo3UIEhJOTV5xVmUCU9 +QNJdH3/Dhqiaka1ECysvYk7KsnCEn3UOJbjhtW/hWlz3gXEZT4UF9MDGWn6wbSOkYZhZr19ZKdNW +r6KEKVXdbdrLTrLSiLVco1s339Ysu6ggel9Ey1ivLhumr45lN1d2yxT6ojnUoM7XlXUBUkbJKwnE ++v4zLtPqKQguzJcaOhv+JMvDFwkCXDzD1zXrW9vyjF0zPZadumnb9bDs26aF6GwHHdkKp+9VyVr/ +suxa1Wy367iCQ5Qbha7Ernvx9HviHbDr7HYd4kHkGJs63SP75AvzJOLycuSzqCqWXatvsu2aajwz +bdFpIhSy12DJxMVFPWvYjiKqmYze9OkSxzRMVU3B2hQniWqtxc9v/4YdsW52gcRyfQAoG7hCmfU6 +zJTdSajCDbtIjx2WO2mTACjhPi5TaenRoZ2GRUqFyZ1Sry21Q5u53EJ+2BGTN9Pusdg41XLrGvW/ ++b5m2S2K75HJMBHLX12mJIfiu/fZcoEZ3eCmfR+7T5cF+2YQzB2E6+cus4EiAv+SDaXyMrY9oz/E +hy+6H0+f4+ua9o2NecuypXFSBfdEL31btuwK0zbMAgjooRO7wr0r05bxd02VybKhPUA0Jdtst8jX +gwfjwjNUg6LXYuWww7BDMzhq3YKsQwd2yK6aIt6pnkzkbJs2BMK0TGK1WdKBRCINjWmV6k8u6lnL +dvRQHZx7dgPB+UdFnMXymhDNsoFwP/6G/dBFS4usyqick5ZqF5Wyjc3AcKxg3WxMD7HaZBAS5ZsY +GBiCh3A8GOAInsPmW/7JWsu1AROWiFrtI8uGpFbx+DqFNeh2+53NtkeQrBtxryh7rq5TBK3GWjmM +ewxJGgUbFri2ToQ0EeNu/aEZfupKK0Ory9wICVFMeiHjzv4cH76w7548ydc17Rs784xpk/1mqB5H +P0ybXosEC7IF4xkds2XarQqd7jq/y7TrcIDasu3Ba4nDJMm0i8hRpw0Uy7Tp1aA4WOdB7hSrBeNN +DsHElhvEfNOHjkzcELKyNLZpi72NMdXoLBDST63Bds7FVT1r244Xaokagt3BrosjFjIWiInSi/xF +k37h8Q1FNpFS0WXI4hMMsFrIugtzsFOjfzl7YDw0cQdEf3ruKuniFX+U1B8KaV34zxKlipHHtaUq +lzcAdcvnSRJ223aZxTB4peQ+04n3NdNe+12j+gwG9/kTy+jByrSnc6vaDIC1BWIt8eo6Ub0r1a7t +mCI9d51i6lw3G+7GGF8K5zKrP8aHL5i2PchyPMfXte1bO/OGcZuQ8TVV9S/SVP8yRfUv0lP/EjX1 +nxQmfmEl9S/VUf9TVdS/VEP9CxXU/xT99H9TKeN/Z1LoN2WMP1ME/YlP+PXPXdSc/5moedD7Hb9h +l33Hp7vT5/z9L/TL6yD55u/WZyzrVCkJnxyO/0Yo4YP+67+MfxU++a/Uh+/X8nDtv+S/Wq7+a/iV +/7eW72/3u5R8+esjvP/V0//4fX/3n9qWxwN4ckPCT+nBw1UYgJ8pqG3mWSUeGRXux6iQvtk4L6O5 +qVv0qqAkCdFv/QNGVUF1Ui+pW0qIeU1AjYBWxA4SpOtjA10fzYsX3GwWQqLd2Qv+S0acBqJt2G+A +Tfrg4V1GtZfhuWbZx2ByjOaw0G8MLqEwAvzBhSv1kl4RyTE/QsjDC3ofAnD7EwrNPjrxd0Q4FPm6 +pupFQb8Fn21qM9mo1YAJUsnTouTNQTBwRU4Ln4UG01TWRn9L1H5hr0DfBTL1zulkLJA6NOj8dGcN +rNwEFICir9pX0ugFwy+ekXL0Em818K1Ra9GxBTGhVdMAjaA15GPKCsEwPg4qc86VMUH19hOduQ+2 +TQqsPc311HmhE12gRh2SCdCIDUVaZNneq1ZLnyptZrWvp+niUTspXgVTPpSHaQDrtFX3m+fQBMOq +NEUYuIMxf5hyiobRKBETwmmR490YOauewok9LWmQvdxd7PSPP33exmRgvbIWIjS4Qgpndg6ikjBJ +GHaaz4nQwbvLTLGoCqwj0xAngk4z8UV0Ao0U40/YEFOxmoRQhbiKiDST3H+j4Ga0bGK6vUE1C6Lg +6cqVdhcTjrhfv5SMVYqz6x44NdMOvAjc5dp71hmkQ5SlDc1xeS/JNn6NascKai+WAf/AoIEnO1v5 +qU8KuFi1ER4zQcULBMBnHsBrhr7n9tBz55zSIDXISx1I8slt08Yb0lKqsi38Jv/dffOdWXeEJqsU +Y6fmCa2bXikskPlkawtapkqA3H08Q0THdHqTAbTEmhNUaprKiArEtKXIjanOwzcUyfAfrfsifEjX +czbsSanmxeFKScqTssEPCeloS2sVQKSk5xcHM9R8QBET4vUUJj65rA/aPs+EZqEMOvg3QjN/lIRZ +md4uno8mxg/2mtgGIPNVCZlYDI5IXpjONJSH/YY0EOVdqSAUuFPfSZrwPjPtWOk4gYmreiFJGpTQ +LtiIW/ZRl0y5SulnMaIb8O9iL2O0DOhqFt4+HquEAsg2saE/HTjRfLJSmWi231AB0T/hjJJBeSd4 +By8kyRgCHvDRsUHLiqpiiPtsr+BoAPB1h/rql3mueTgXXmguI2P3QkjTMvVYaWsIQkFinJ1IV6Ap +c/7Dh4C2bFER5lMJSoNKq8CRaiyqnzypjzc2Qg4rcbsdoz/ETesjPw6PtP2jTmaxRf1g2WCl2lpN +6seGne40RbvJaKKsLaVqJSJYe0FILxsylSBK+5Q0qQZWl4DB8KCD92KPxTgcHOqIWuukUnTKzPpm +Cz5Ur5d0oEO78c1M8/Knp5c0NJlRJWNhUZhCpBZsPOSDbaXA4AWDtbnYYamkurnw98WNeC5bbb/A +TcGjHODd42+Lo7KlMn/64Bzbb1thmZFEByqrkWn/HAE1IjGqLjejCmXwmUTSqpNznXcrOAP6es8q +E1F5ujIOkG9MlXXohkPyYeIveNP3p99U7V4AAWuHSIXtZJ4Z6kgz/sl55pkb/5oH5s1t8+xZ+WlW +o8ylddOtHz7oHNF8nncqAUVLEVZkFoVhHc3opHs2gGNqbhmiQ8IQmp1sJuxHXSdbrRgrzBzrcJd0 +L+9CDAkpQIXvYBpTpHilK2MQsvgkhbA7QipCtY/+qfsKnyQOS7icHN6zXIBqMLx5gpobfte60xrq +z12zUhF1FtHOJvPf6hIrSRENaLWxD18VUMfV0LSyteKw0WoM5rwgVUEK0G1nQ2nCqbhe6bZEAib8 +DP2fXuiG8xZDomddRnfqSBdlgfTHuNuMt+L8NPe0bnWhAeu5Tab3yUPTWAFGhTekmGfI9GKuar2g +9EVuzLXtAMJnH0vWgBTMr8bRW639Tm7q54cq7clzOCWTvAefDLrsVL1U93g7PZM6TOfM6zpH7qkK +N4I4x9Cd937pQaAbwlvToEqa2RsqVBZF3eKsnVB+yPuVKiY8YMCd4VoTWHqylHG+dw== + + + yjXWLYapxYfOQHUP1TO5qcKeXXlXBvNNYDZ5R00IA00Qg6Ma8cpbMm1iwy/DynDvT39anZ7ghUgR +qUK8uc3P23DPN8+Agw0l9prJ9dA3EH2CBO1Oy6BIF2BKyNaN0ZQncyLrce5wtCcb2pdKlOThg+gX +e5TOsDdTm6FWP1qlSfhzQKpTGMlkKruUMKjlS0uDc7o1C5uVLyRL6alYDK/9Ksuwmkb9icLHRXXk +on5ypchyUYm5rNVclnMuCz6fFoSuFY0uKktPS0/XylNXSlhPKlyPamC+4kmd7KKS9qTcdhTpntbk +nlbtLkt7T+t/FxXCq2XEp7XGi2rklULOZbXnoh50tWjUjd4ydRvaEfFrGUqOrNLEXg6KsIO/Ey1g +2jUAak3mmUH75fOk6E2EAGhEZ0x42Oco/iWpAUv3RUk0sLluYBI7zZKfZjstDgZeiA7oY1eHFcIY +elxmyMfUNSo88JtO4i0CxmTbJogHmYZbsqRBkjPrPnd0b3bgQSsKFgzDhk4b+9T9mK5A9dhNPHMC +DhtqB+sgh1RgqVF3sg6baD93AvJBybdFMiJsPPuZOV6GVWBkVWBNWrUuFmV6nYASve41iWkIbeVe +5+XSFTJg7AgaSSUweeuZ6L7qiqdyqnr7Xd+ff9d72jiwZtFwbucID/7C71Mq/+6Psts750Zb6Nmm +yNy/+IcvKq3FkUzNQUIuU/MccTD+GuQH5Tfi0IQ31qZhvSh13ib32kAmfEAs7slrX/MawwEZv7OC +I2KfYbuTGg8oENqXotjMDN5NnRkR0VfAB1SmqKtAaiF1BKoocBfHqqJEwuP2aIHrCp6NeZX4HrfM +C9Iw1rBU5s15j+EO1o6AdZraOK8xuvKBPOinzQ1NW4TjjXKUfNrstsqb6UBjg6J3+DeCKGZwh9Br +0XOIxWr9rIHAjHoHk5Tg06NUkZiPD8bFyjtJ8wGeSdINvRPT4xWS+NaAWnDDpJYWiY2YFAPcM7Nw +u3mkfNzBYHg/3B0/q1QKhKDqRyF6YzS+WC0JtiQ5bVazceSNLOeWB7iYGIIlBNEt7zZaE4sN9cQK +WR4iNZkQCYW5anUtLhodIa2CZ2PorQKJZZQ0bLaBAvYnL+iiowbO2l6lij5xPfQP/GkmfcBAMCat +EU4KH9B9jL6vSVKyFUgj27b5AJ9whYjmroeSwfY2oadsS3TPFLk4BOTZ2HaZ+0ENMJpS2lGDgEWD +f6uCU0+o9JYlGAgTmS24gkD5qdTJqTaCv1Wyz8NpzoFtmyQbLREsHtpJwaYgxTGmVa56v2egsSoy +Ck4WKrhsJKmlU1kb1W5Ft6oY5ybC7HoBvVrok8peM2VSCePTomETHkQ6gCG1zSd2uWItDnCX80pY +9fRtDlY42xoqqLzQUXiV2CN0a52e29qwgDSqob25WxoPIWxjugtiugIXsrIm4LAj2BbmN7qb1KD0 +0XhpRS4QViaoXfTeGvO+8G3PllU12FN7P1tVZQdqSJhImQzhB3ttFpp6sIKCA6rdBiWou1MTZSML +ZvnYdWqobZp/pdr2tb328Hvrvuk1ig0qhMfJTSn+JHiBViJHixQfiEAB5XCXirOOASBnrDJSxKcI +QQCNqsVaaWkFsV/0j0TGQueSiBr3x9sXJMGbPmaDMTSWYIxafG70GmJ25nZeyG5P9GmChWqaFY+c +yUSNkCwUeHXJoFTgj7tgvuxqvX3wt8c30FjRyZF8hyADCx0QJRUIALRKcvS6EJLk9UvZCUCIkjHL +nSBkMcP5qmCpx3otax9JvrDqlEoZKBY8ZdlSjpksYI7wkEZ7/w6JIy8EJsxp8JL8RZKHLM5kji1z +mRCbSNkCRu516exvlJq4dFhCeEEF3CwuYTvaUrZIjlgMYkRGSRPhqm7YeoCM/YP03g+LRcS6GKIG +aRrnQ7fKkvIoTgVqF9H+yjqMzbfgyqXxwRnEqccBAD9e8vHJyAmxvrsTfYNV0HhNDkDhedPvmDOp +5gx4AVgz5iw9A3YYyMgg8iqDqLHDAgc7T5fitRtCgGqCc6JYL91fE6c3IGqIUNf/kJAVSE7gueDE +DhCzZz8VA6YoyeMIUDpCVE9UgloHrSP+qpiWq3Irsx5Y5MhPCQvIFXWtk2kWCIDQaKWkDrwHEF3y +7ctnJTCgM4ShEgjwVHWi8VYoG3A0cWqH4jekWk2WUxr2OC5U7MlDpb8Ag7S/1qhVQXMFs2lUXbAp +HIm0tlDWIeNK0R9Ptw0ONzR9uHXAv1O1S/VDIg6J0jSb13NHyeCynpV4t9hASYP22glE/Wx4BC3x +v22F/7YIQvLcLBfmyBDUa/0jZxEdCqmJlWFO00Z0ea300I0/e9oiS2oJVQmoJBOun6UNrkXjndP+ +rXjAbLR2owwSRT/BYiOM0wThB7PYoI3v3J3cS50dollZt24dV/6aoMOahicW4sypQw9LCOiovZ+N +qiSbGO6nr+kzU5NI1SiL1m36v+yXhLzmJnDeyf6J2AdYavvLXVyi6IMarT9vH/K6MlAHHfA3HxBM +SjW6WC5M7rfSQAtz355MWIieEsEC93SwJesOoMQ1vUyjiSbRfXUCa3qHrSciHjXQumiXBHDghUYt +HzqcYqMZvOafYx0P2fYoDV0mDqONfvFzp1xOlynIcMHQaMdCZP/BXL7UwAnPIW752l6bSFtuH8FR +IVwOVYlWm44g2yqP4oKCE4Dl1up9nG8T8ig2LFTdHIlTgnKYvu1BoQiYBam2rQrhH5So2cogkSYn +FRc6kT370dosKhMmFs1lzRYNXeYMOpCLmCLFEuZuCKiAbkW04E/HL3rHbFhtM9GBk83rY/uqYuAG +xpyVJOy3qpJHj/qAmcNP+mtWFyNAkFqz2p1sTupHZEmgSoVJZHg32qS+Bvk+WpiRZ1dcrz5NpMgk +VqLG0096YcpPu8LrBwt9yHDh66Bq9/WVCOnrF42/8PWkQNSPxhAo5OK1r+21QZBPjb7oTEuG4kXx +VsmNEiHDZXTr8usP0UACxKGkBBPgfjPyF3BSgZJURZ7QiLx1xDQbhNKqzjkEzCIydW8kgwyUaow6 +94fUFcWprhqp7dcQTaeJQ5hG1HLq0Nor+G3Rbvd0j6ghbF3n41fWV/V/u6VedP2W+5nVBBs5wjgj +JiiHJFumTDir9aB0vyx4nAztRaNFD8sFTT/XMBYyggloBNVPJCJH9CVNoHIUfdfJNr3NUSke8S7N +BJCctvWjPriiBEAhbNKpiTP8nWVOAVpQGFSN5lF3DBUKFEiX/ZJLcAtFOCGeZn9hjqwTVxPoWpVd +nCUamIAXGpYXoYNJ+nnIfLPpE7OteWAy04qitu2ogPfBu7V9rLvmbRymt8kLdpRN/JQZSPAQNomG +bOyAp+eocE/8819f38MvZzWjWhoG6hAbWEaDXAkBEQe7D4ITlg0L8scwfajkfJnrSgbnQ1NOI8XS +9QE/+a2ms3U47VP2zG2AH4iK8lVQZgmMDTnZkfBRf0XVXAF8IF8lsa2KYSe5ED8rol7H0rAAjY9b +rHkrJRQIaEzghjI5A3KMRaE5244F2DqtFNKvAQ03v0HfYznf9anFB72cRTJXMIMNRwHn0faM9uE2 +gegkKcuGhAraaBNZprW3OYfgIZ3JVLPhmaSoMpNteDhmi9WqZ7DTBZSNqC+HlVuKMYhCjyMPlW3q +gOsoiv2LpyTM3fIplmFw4I5gxS/mP5JJgKlqMbkM5r07OLCu+4D8ID9Lh55ql9We+n4Y1Vo33ftc +gLhAzFHhIClxG2JFteiRrKsh9d7Jnkir1mPv1T5J1oCpLqHTFErqoTfSVwRHh2UoStKgGqMomKNF +pyzJxsyu2mAxcZRiKf/AAfEnFErSDZv7XawI0C3hpiYJPoK+fFresEtqSRlOSjH5kkljROHa8ojU +MFS78Zk4ljDLmJKpRdgHg2WWPtGQlkPmnhFxd+4FippF/kFlzmVEkFgoJe0MmCYshkm6PlRFpK7R +k/2yJON4RMAZVdmEK8meUbQigYdZHaAjsWc26tJO6CnuoSPLWC/pOIC1MQ9723CUFCsnCIVJ0gn1 +bNbj4a9wldaqLP6+ErPkAKEChMxYhhYff8iwHRYAOss0qe3+SDMDGDWR0doO1qoHAFeTdhMVrw57 +Y7UH140OBEc0bQOKGT+6rCV6d93cebH0tTsHuNirWDF9BZHxCnGHKDA1HKlZerOKAaMzBWCgaiOa +J08I+K0jd2jqykp02fKVATFse6i3YXrYjiTkjUa28blpkyDVbcaavLiWNZ4avJbVaYI+9ckvWqbi +/FWa3UxR7Ad7Cd+hdieT8M6hqM+zPPP6uaQhI/L24HTkCYUQ6vazG4hGSn3rKc5uG0/T6wbF0Uv5 +eOVr/RFoH+ilMoY6m1FCie7LAoMw7HzQ3bBYQsQAVFC4fxNvaEWhvJ7RdLoyoVPdj0avCkUgV+uD +BRNUlr+AGpi9LV+gfrEdZsQJ4AuahagDFMlUT0CInq91aqqxAn0VTsxekiIbnTXmpxmBjp7xBHGM +EsoxMG3D8pL3spcy5Yyv9VIWa3e0Kgbj1ZxWU6gebSylKQoVrQ4OqSHpM0GU05PjKqiFkdQNioZh +Pq5gjGpln6piX9wHpNg/gg6ajIxXU+VsH3hyt4Pa2XIuM3h+Fyxkm9HArERNyerfM4pjAr/TUL2M +7mQQoFyvTwfncm+KMWzNbMUxWsfRtkdBd1XJQk2+Xz7ZuS8aBymybZx3Oj9+sM3WmmC8Ail/rZdW +lm6nYrEtqr52dk2ZOaysSPDgVaY5bTYBSs6WLXbXzG6WVFvUC5RRNimDAtNg2u0lWD1PES+42WgM +mBOVyuJ9LIuYxeBFkTavW6aY2nG2IFuI5FHuBWBHmeGj7m7FaUKtNc2U1LAgf4bCYQLvNbqHlA2t +xksguslz1hXNYh2n7OH6kzv44SWfDked5OaaBaE/6KWkl+gAJVNbyYY+idWQNAIhE1QNbbFPXpHN +MtJBUwZYAsTitZuoENALnD2iT4SoYn+wihbN2tEtdsN3U0tINjuvw5iwrwl2IBWSjyJclu0R6OQN +YAzWNiFQUjM0GbCCsPujGA8HSvcScIwKiDSW5Pzx4pe1LCw2O4N4KDQcKbPpLhRXnqI4C3i/W2bC +Ad1tlwHlaaoqBPny6hnoLvbAAGJNwVjtSjTeEix8SFnjmllVhGJjL6LkzNFYOy2X5aVkQc1Iwllr +A0bPGGAeIDwjT7L721BFonY5TAKGyXN1xQ1Swngrhahh4xUscCEoai50Blrw0IpwJWkoa4Jf0Icy +11tNrkOyAEOGDX374CXaYVFgQ3PeiPBAhJC8vkF72GILBn79Jc0e9SOXlu42PK5qwybjoP8ouk6F +PLF4+E+zsSiU0PgCdNQ06cGsTkPYSikuKLxNiIWW5o34bIUXYfNtHiH0IIdDYxQhAmaQ6KrQ5c5m +pkqXW9G4A6g1v9+ZrQaWlWSU+58M7Cqt60YRWi0mtVNZ4XrA9A2iEMMKEjjGmekV1w== + + + araekAfc6zoJyOg89CruF6keUd8gHXpq3C8a7sDowIwXHYgsx6HzpBWbtZhq5Es/uXiQT7qtCv86 +vGDc5skx+SIeVDN+GEzVs9a8ikQj2IRIPhZVPgQ3GpKW1eZWAHM/hFXUsYgMyD09QfIdAtjYfZxe +fR9V6OR6EBgj66rqjVGdnWLErR6Mq7a7KypTxT2KqNRku7IpoYJNpZa0Kemp8zEFH/DAGHhsHYZf +YK8Eg4oLvW3YLggNdspDrzFLDkdtH3UR07SMVE2uJJ9EyRnStKTP7OGslWGbHeNyUUlCTVrAJKbh +1DTBlTwH4GfiIGhFgeHxc7O9ZXqqWiJJxeQhID8zI4dtaYiwWxNfbWIrXvI5UNEjnYJp04gNFZRQ +QRVaj3KY6uA2HUafTV0wisJV/ZJkEF7VlgQk2fAO9Z2EOGtyTgq2KP8QmeKsSJZpTnidjG8hApAr +NMAJ85I4Ke3KbmTosDBV7+Fbcxh3oiyxuB5KNrxgTB5kJYkHqohpjT3acSBB6XVAMR1dLTbLg2ZV +p6ONRAW5qKykU9YgVgdSOqJhzChYw1IwBu9Pgiuimo6oIjhIxDIMFrBuNBVqijWqcRVaQcsjF49o +qQVxJuGSH/9WVXyMuqI3a1WBoREYDGZWAPzTWckq7uTqXc9k0NPMFAv1d1Iwi+nEDsnNpm3AGW0I +VVpKQxHeyl/73XYWghFajqfTRv0LMz/cdZfitQVt7BsjtOHApUmG05enOBb0boUQKZ4woGE9+lLZ +d14JZYItGHKFrhTBOUbYwDkNF1OJ/HGFEqpJqiFsGXk2EK5gtO6Oq4m5i/BaVARWU4zFOmhYNHuR +iE97F6LcbhEigIMnnvRFvfQb6uwNdfbxDXX2hjp7Q529KursDWH2hjD7+IYwe0OYvSHM3hBmbwiz +N4TZG8LsDWH2hjB7Q5i9NsLsDU72Bid7g5O9wcne4GRvcLI3ONkbnOwNTvYGJ3uDk73Byd7gZG9w +sjc42Ruc7A1O9gYne4OTvcHJvFDfvDOOUygmpMVrKoLSxlOlnHLGzMbQq4ZeX1udMgPOh7zwo+Fw +KHwB5lDlund75IKEUO2l7qW+TnVyfAAhsRi0xKrrAEDIPLBaRRvR6hbc52q0nbymRJaEtqmGgIBJ +FdXkgKIcMIry22i2/9FgLioUARuIRAsS3gwOz3AgmeQ7ab01xyMl6wjrsruBrAwbwxHfBbKiOcAl +bYjVsP3NXoTPT9C3YOCfhPaiwFzqgJnN6H2yOWbBArgAzn619jgduqF9TJh0GFLo435KSQFbEu9x +G+ZPifGEn5tWCI3deqYfDME0INRer42mYK0KYjbgLs+6deYMCSYsQQTsYwiCYMUbnr7QV7BRN8fP +SVULis5ujxYiWBoXVOZU11cEimFno2e+2Hkfn5FfqckEosgixAXOEbLsrpC7og8lAkOXMeR4FV97 +EH5P0UR2CkkSfNRq4JctznIdQD9wjKmLSYWBUwivgNKCqa8UiqfwQ9+vDVHsCi7Wpha9NM35T8/T +SeThrhZTaZdBtRNv6yzy7zjWEKlLomG+XMb2zdZzPdiXz31adjzUj9JSm6dFzsIyFwQ6/2TqyduP +7FXVWm5uuedlCp8Sk/3gEDH8CGX2HsudtYDt50acWLorAEqzLm6ImNUDkoI4ms0VxAG1N4JKWtSZ +KJeTxtNYuuxKigT+woOXna8Ew4UIDdYtnOxtQ7TUhFVhsKu3qS5Xn1YZTIb0kqcSuCkcwC5T9cBb +ZsEQsgO9DBLBedotmV7R8di4LnEv02BqAlusKMxBZSudL8KIqJyQvZTy0REhwajjVxA3BclYkY6R +FVuCvLnv46NQJLoeM/qxODmS2GCYuvUPUyAd/Ta/UccD2qoYEFDdE7BEtZq/Ugs2qnubRFasmPKj +o61CVzisqIMXUrUOxeyCetlZEIbhirIRKBPFdf3Rag46CQscDRpGCY+Qt3ZfxYNSJTwK0mURa3Ka +Z36W4wcf0caBGFK6SahP2izEkA6m8c66srQYyWKUJswDMrRhRFkbIjgXM0kql4fOJSkzyIKNraXE +G+wSVq5jkCE7L4LBs4QhEkrKe19ck2HVk0XmAi5n62SCY+jCqVQhLPGxpMFaVL0COi024gVtOqJJ +KtO8oMohh5c/I9JdauSkLEJZUzWcBolbNmtQST3iZECybVL2S0CYk0E1o8B2sFonQ6FE4kdpaPpt +L8NEVsigkio2A1RG035o8gHdAB0QVW9YpojC47AONcis9acNrjzeCT/CC0J3co7naOf0+tMIXKoA +o5Y5gJIqVDJYVirkyZqb5DQ8PlbVbFhJOKQb9x8pY2+Fh8yOBQCuKG3ZagiO3q92w8HNqq+zjIKk +QXI8apU98X8nTvcCSPCxq0WSdbbPO90lrqFSiMjiiawENiXiJwpJ9rGotlJc89NdUiAsWL6n2czE +xVpic7ufkojJQEKP051QktO9iSX85tsep7tYoS0kbFfWVe+bZVcLeX/+47JhRUsNA7ggVm8H/Oft +uucPeHJ6ekXgrPPYACcdWcUPHBAko1qt32RIwMtSeBIgofeNS1JJa72WGjcnVCt6g2uQjYdiHPAk +y9VTJ9RwOJ7wkqk6MAnrV9ImmEswfRUqNcmR48HOaPQGBBNdf7moiJANAMGHURjK0YEs8gcD9Apd +Q68OhAaXJThn3gMNXDhaLIDFs9Vcea3QMwO/kaP9EjFs1kEf757ewBMOotPJ/vRRdSQKPsdBZMl6 +d9RA0Oaikj8JBZbRJDs6NQBTBV9I2z/EZP2fZXDJe7RPl6YqqYOxvMMQQHse3oHy3D3wDooON99y ++4b1TshRJ8MbXi6rVutHZhOY3/vTn3TSREtSYFGf980vfN5me9YvPGX4+kFgKvWZTOncwFPqS0X7 +VbBRwSL8Ma3oO7pgJBT/EyuaD3xUi7aQqldzAPzAXqD+DqpINBGjhTb8GNe5AVSKk6sE06r6KGyU +wnaq7evWj2FYU36sEtsWZqwoTDlwTjTJqUMhYkbkHtUqVZF5+DQYVazosKJpIHhqeiuy6C7pqkkQ +YDSgG7K5MBeeAJuk1qbPQXS6aVHQBIXJQIg0rxKOW9Vp53PlwfIifqaPrlL6Mhz9e9IDGcnEa3tX +LMi5CN4BpAl1XElAdYFCQJdSyWZA74OACMEHlVYgLWybek0UVsGIPXnmN11ZtOLDp2llpDl93pXR +9aGQQRCByoQK34Uib65A2ufkIicCYxMFJvdkoVp4sPxDAGo54uXSVAFRclD02RyUvH1ZGdXqGJGa +wc133c6s0hW/b4Dr2rUPS5dg7VcESBERfn/+w65dpb4Z+hwCNL25s8/bcM+6M7BBZlyCFRlmKTfL +sClSU14KyYek1m9MEG9dlQcfVpkMuk4rPXCkzXXeJ6udVySjBhLQFl40R59OG6tT72iFMoO2sWWh +UtoBO8Q2onI4bcSDNyldXbAwlm+Z2YRrqOkPQSQSo6CpWhX2gyAl6l3wG2C/1s8q7ScfbwIrLXRi +s5FTA19p2APAYk8CTqn8OyyrenKvbnoBnkDpnzwUGinkfZ/lBaK7AQnj0tIEPJkBUhB2MVk1kKds +dhQ8+IE6VJKcIVsyc7E2Vdo4wl3iBwINywc/gCqo/EDrJ972wREgLiNHoBjkYh1aTjSFmQbfaprn +Pu4ARYfUE5PK9Zyo9P/vPMFzm+55T+AaU9JmtunZ6SA8hmQGBrBiVcYLduKyTEilHAaDmmlYgVck +EBHkQCPjzK2uoKAE68k9fZubVkQvoj65oOrzxJ+RF1Tk8sgLxlReQLWVdiPlGw1ZcdRzQjUDLG4z +Gua81r5cf1HAk4u1y4yQbFx3vc/ucmRuRivPKzpOl5Wh33Xzbbe6YqQ1THIwUyvX1jWDuJRhkOX3 +5z8uwaWp09JfTW8H6mfvumftCPhSqGqWa6z+ByGHksYaNHUJbkj5xrTWOUAhV99Wic2RQNwPZi0E +PmtH1VuxdyP6GpKkC9WK+7RwaRRlHxHuTtpB85zZYbBCakK3o5iNMVDvy9ZO5Y8CayjBUgLhj6IA +eZGBDT5XsggfhA8AHEgeTNU6rUgbMBd1DXKKbsNCgLfAd+yZrFJ8zmTSKlZqMYSx8iETCrNdIAB1 +6DUJ0m30IdssieoakpYVvCGY6DVNhOnZTbNWhKmIA2wxILPm8D/oI6VI9aUYWqUMkwJUqZlJqCfP +70SwX3J92kMK8bOCfWrjFBbbtOTmXrLvQciLQvNnMAwKCKyYgZt7ygQ5aFb3QcWjI07+dOXKiQA1 +gPyfZGZlPhzygwEVKhdiY7j5pvuMH00hCaNU/doyoy5hcwAqen/6o4I45HHfW4zw5pk+d7s9X9EM +lrqBuAFr9YOjeAysMhlZijaAS0mvG5hKwzX0DX22iRaPkLJg4Hq0oXWD5WqiCFxEkS+T8LmBcjRz +iL1Fi9jlRhktSxbkS1kWmy7hQOUI12g5RjJ4n82z2YiB0BPO42CYHMHpJC+a1edTfyU760cOju4E +6GQulnKB8EYAQGkWrTdhvBOfxBUAkSs29qWxGZbw/kFOTGlJtHlgfl5+W7ObGq8XWNxWAPQtKoGq +ZXqPYi5gPz4omtaNEYE5j34bdtmQkDS4k4pB95Dh1GTICond4pgpxzIX010UXIB/m/UPEhhd+0QN +YO4/gTr3shvjjCadP/DxosZSVPjleHmXNFcj3oC44r4n4DFOAE3tCraqQerhSEv6coR5TzfZCe+Z +oWF5vJ3p2tbP856RzYb3XAdplBBxbz7+L+x9M1IR5uZI7Nx7MrtHJXWIuedy2QqTnB2ljylgYTpc +Z6cje68Z2HriHbfr7LhfXGcLoV1Zlq0Ej64xD+/9yQ9arfNt0Kv05jc/c6O9jMzo6V/8mqfyl39d +v/2rH3/z9Xf/+v3v7u/1Qv72/ff/8E8/fvjdb//59z//2R9+//3vfrP+0t3/wb/8+Fte/pvvfvc/ +fn/3P3787R9/vPvxt/9y9//waf+S//vmj9I5Rer07u/5v//Tbox0Tu+++Vd++g/ru/8O0PTuj3fl +7m/u/st/DXe/+fn6179XqYHHNl0QHL/DD1///Gf/SRkJarwreP3h+CFN/8dff6q0qj8E/GH/Hb5/ +9GdS7Mdf8Qmvyz9CE8f/RgLRfPwJTin/C/r2uT+wr4a/8OhiWLevxb6/eimPv/9/7/7pv/kTWo/M +n89XX/3dd//w/X/+3Xf/9D+//93Pf/YPv//uf31/992PP2I83//z+qe1A7///b/89nff3/3+H3/7 +R15h0bHgq6/+6m9/9fOf/X/OP/pG + + + diff --git a/share/gcstar/logos/about.png b/share/gcstar/logos/about.png new file mode 100644 index 0000000..0810e23 Binary files /dev/null and b/share/gcstar/logos/about.png differ diff --git a/share/gcstar/logos/bg_no.png b/share/gcstar/logos/bg_no.png new file mode 100644 index 0000000..dedab7b Binary files /dev/null and b/share/gcstar/logos/bg_no.png differ diff --git a/share/gcstar/logos/book_no.png b/share/gcstar/logos/book_no.png new file mode 100644 index 0000000..1214dc4 Binary files /dev/null and b/share/gcstar/logos/book_no.png differ diff --git a/share/gcstar/logos/button.png b/share/gcstar/logos/button.png new file mode 100644 index 0000000..75b22e4 Binary files /dev/null and b/share/gcstar/logos/button.png differ diff --git a/share/gcstar/logos/cd_no.png b/share/gcstar/logos/cd_no.png new file mode 100644 index 0000000..9ec486f Binary files /dev/null and b/share/gcstar/logos/cd_no.png differ diff --git a/share/gcstar/logos/film_no.png b/share/gcstar/logos/film_no.png new file mode 100644 index 0000000..7c99a68 Binary files /dev/null and b/share/gcstar/logos/film_no.png differ diff --git a/share/gcstar/logos/find.png b/share/gcstar/logos/find.png new file mode 100644 index 0000000..ecfabc0 Binary files /dev/null and b/share/gcstar/logos/find.png differ diff --git a/share/gcstar/logos/install.png b/share/gcstar/logos/install.png new file mode 100644 index 0000000..64d019b Binary files /dev/null and b/share/gcstar/logos/install.png differ diff --git a/share/gcstar/logos/no.png b/share/gcstar/logos/no.png new file mode 100644 index 0000000..7d3ba0a Binary files /dev/null and b/share/gcstar/logos/no.png differ diff --git a/share/gcstar/logos/no_minicars.png b/share/gcstar/logos/no_minicars.png new file mode 100644 index 0000000..281d7f6 Binary files /dev/null and b/share/gcstar/logos/no_minicars.png differ diff --git a/share/gcstar/logos/no_smartcards.png b/share/gcstar/logos/no_smartcards.png new file mode 100644 index 0000000..e9e0aea Binary files /dev/null and b/share/gcstar/logos/no_smartcards.png differ diff --git a/share/gcstar/logos/no_stamp.png b/share/gcstar/logos/no_stamp.png new file mode 100644 index 0000000..3abc5e8 Binary files /dev/null and b/share/gcstar/logos/no_stamp.png differ diff --git a/share/gcstar/logos/periscope_main_logo.svg b/share/gcstar/logos/periscope_main_logo.svg new file mode 100644 index 0000000..2492e09 --- /dev/null +++ b/share/gcstar/logos/periscope_main_logo.svg @@ -0,0 +1,1230 @@ + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/gcstar/logos/splash.png b/share/gcstar/logos/splash.png new file mode 100644 index 0000000..2707231 Binary files /dev/null and b/share/gcstar/logos/splash.png differ diff --git a/share/gcstar/overlays/canevas-timbre.png b/share/gcstar/overlays/canevas-timbre.png new file mode 100644 index 0000000..97ad22a Binary files /dev/null and b/share/gcstar/overlays/canevas-timbre.png differ diff --git a/share/gcstar/overlays/cd.png b/share/gcstar/overlays/cd.png new file mode 100644 index 0000000..c2a2ae2 Binary files /dev/null and b/share/gcstar/overlays/cd.png differ diff --git a/share/gcstar/overlays/dvd.png b/share/gcstar/overlays/dvd.png new file mode 100644 index 0000000..7793a62 Binary files /dev/null and b/share/gcstar/overlays/dvd.png differ diff --git a/share/gcstar/overlays/favourite_large.png b/share/gcstar/overlays/favourite_large.png new file mode 100644 index 0000000..c03cb06 Binary files /dev/null and b/share/gcstar/overlays/favourite_large.png differ diff --git a/share/gcstar/overlays/favourite_med.png b/share/gcstar/overlays/favourite_med.png new file mode 100644 index 0000000..73260a7 Binary files /dev/null and b/share/gcstar/overlays/favourite_med.png differ diff --git a/share/gcstar/overlays/favourite_small.png b/share/gcstar/overlays/favourite_small.png new file mode 100644 index 0000000..fdbf576 Binary files /dev/null and b/share/gcstar/overlays/favourite_small.png differ diff --git a/share/gcstar/overlays/favourite_verysmall.png b/share/gcstar/overlays/favourite_verysmall.png new file mode 100644 index 0000000..5d44db9 Binary files /dev/null and b/share/gcstar/overlays/favourite_verysmall.png differ diff --git a/share/gcstar/overlays/favourite_xlarge.png b/share/gcstar/overlays/favourite_xlarge.png new file mode 100644 index 0000000..e5910c1 Binary files /dev/null and b/share/gcstar/overlays/favourite_xlarge.png differ diff --git a/share/gcstar/overlays/film.png b/share/gcstar/overlays/film.png new file mode 100644 index 0000000..7b5baae Binary files /dev/null and b/share/gcstar/overlays/film.png differ diff --git a/share/gcstar/overlays/flip.png b/share/gcstar/overlays/flip.png new file mode 100644 index 0000000..e81734c Binary files /dev/null and b/share/gcstar/overlays/flip.png differ diff --git a/share/gcstar/overlays/flip2.png b/share/gcstar/overlays/flip2.png new file mode 100644 index 0000000..7c5678e Binary files /dev/null and b/share/gcstar/overlays/flip2.png differ diff --git a/share/gcstar/overlays/lend_large.png b/share/gcstar/overlays/lend_large.png new file mode 100644 index 0000000..47778ac Binary files /dev/null and b/share/gcstar/overlays/lend_large.png differ diff --git a/share/gcstar/overlays/lend_med.png b/share/gcstar/overlays/lend_med.png new file mode 100644 index 0000000..3a16b33 Binary files /dev/null and b/share/gcstar/overlays/lend_med.png differ diff --git a/share/gcstar/overlays/lend_small.png b/share/gcstar/overlays/lend_small.png new file mode 100644 index 0000000..b5465b7 Binary files /dev/null and b/share/gcstar/overlays/lend_small.png differ diff --git a/share/gcstar/overlays/lend_verysmall.png b/share/gcstar/overlays/lend_verysmall.png new file mode 100644 index 0000000..5b46e7e Binary files /dev/null and b/share/gcstar/overlays/lend_verysmall.png differ diff --git a/share/gcstar/overlays/lend_xlarge.png b/share/gcstar/overlays/lend_xlarge.png new file mode 100644 index 0000000..0f76325 Binary files /dev/null and b/share/gcstar/overlays/lend_xlarge.png differ diff --git a/share/gcstar/overlays/minicars.png b/share/gcstar/overlays/minicars.png new file mode 100644 index 0000000..98cf484 Binary files /dev/null and b/share/gcstar/overlays/minicars.png differ diff --git a/share/gcstar/overlays/subtle.png b/share/gcstar/overlays/subtle.png new file mode 100644 index 0000000..3cffa1d Binary files /dev/null and b/share/gcstar/overlays/subtle.png differ diff --git a/share/gcstar/panels/Classic b/share/gcstar/panels/Classic new file mode 100644 index 0000000..8d7e6e7 --- /dev/null +++ b/share/gcstar/panels/Classic @@ -0,0 +1,29 @@ +pageBg = "#ffffff" + +boxBg = "#e9e9e9" +boxColor = "#000000" +boxStyle = "weight='bold'" +boxJustify = "center" + +headerBg = "#ffffff" +headerColor = "#000000" +headerStyle = "size='xx-large' weight='bold'" +headerJustify = "center" + +subheaderBg = "#ffffff" +subheaderColor = "#000000" +subheaderStyle = "size='x-large' weight='bold'" +subheaderJustify = "center" + +labelBg = "#e9e9e9" +labelColor = "#000000" +labelStyle = "weight='bold'" + +fieldBg = "#afafaf" +fieldColor = "#ffffff" +fieldStyle = "" + +expanderBg = "#ffffff" +expanderColor = "#000000" +expanderArrow = "#afafaf" +expanderPrelight = "#e9e9e9" diff --git a/share/gcstar/panels/Dark b/share/gcstar/panels/Dark new file mode 100644 index 0000000..1ca4693 --- /dev/null +++ b/share/gcstar/panels/Dark @@ -0,0 +1,29 @@ +pageBg = "#000000" + +boxBg = "#333333" +boxColor = "#eeeeee" +boxStyle = "weight='bold'" +boxJustify = "center" + +headerBg = "#000000" +headerColor = "#ffffff" +headerStyle = "size='xx-large' weight='bold'" +headerJustify = "center" + +subheaderBg = "#000000" +subheaderColor = "#ffffff" +subheaderStyle = "size='x-large' weight='bold'" +subheaderJustify = "center" + +labelBg = "#333333" +labelColor = "#eeeeee" +labelStyle = "" + +fieldBg = "#333333" +fieldColor = "#eeeeee" +fieldStyle = "" + +expanderBg = "#000000" +expanderColor = "#ffffff" +expanderArrow = "#ffffff" +expanderPrelight = "#333333" diff --git a/share/gcstar/panels/WebSite b/share/gcstar/panels/WebSite new file mode 100644 index 0000000..8096d23 --- /dev/null +++ b/share/gcstar/panels/WebSite @@ -0,0 +1,29 @@ +pageBg = "#ffffff" + +boxBg = "#eeeeee" +boxColor = "#000000" +boxStyle = "weight='bold'" +boxJustify = "center" + +headerBg = "#ffd700" +headerColor = "#ffffff" +headerStyle = "size='xx-large' weight='bold'" +headerJustify = "left" + +subheaderBg = "#ffffff" +subheaderColor = "#1c86ee" +subheaderStyle = "size='x-large' weight='bold'" +subheaderJustify = "center" + +labelBg = "#eeeeee" +labelColor = "#1c86ee" +labelStyle = "weight='bold'" + +fieldBg = "#778899" +fieldColor = "#ffffff" +fieldStyle = "" + +expanderBg = "#ffffff" +expanderColor = "#000000" +expanderArrow = "#1c86ee" +expanderPrelight = "#eeeeee" diff --git a/share/gcstar/schemas/gcm.xsd b/share/gcstar/schemas/gcm.xsd new file mode 100644 index 0000000..4f6d8a7 --- /dev/null +++ b/share/gcstar/schemas/gcm.xsd @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/gcstar/style/GCstar/gtkrc b/share/gcstar/style/GCstar/gtkrc new file mode 100644 index 0000000..b4ef09d --- /dev/null +++ b/share/gcstar/style/GCstar/gtkrc @@ -0,0 +1,277 @@ +# style [= ] +# { +#