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 --- 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 ++ 941 files changed, 147733 insertions(+) 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 (limited to 'lib') 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 (