├── .gitignore ├── lib ├── jxl.jar ├── freemarker.jar ├── gson-2.9.0.jar ├── xmpcore-5.1.3.jar ├── BrowserLauncher2-1_3.jar └── metadata-extractor-2.11.0.jar ├── src └── pattypan │ ├── resources │ ├── gear.png │ ├── image.png │ ├── logo.icns │ ├── logo.png │ └── refresh.png │ ├── Launcher.java │ ├── LogManager.java │ ├── elements │ ├── WikiScrollPane.java │ ├── WikiRadioButton.java │ ├── WikiPasswordField.java │ ├── WikiTextField.java │ ├── WikiLabel.java │ ├── WikiProgressBar.java │ ├── WikiPane.java │ └── WikiButton.java │ ├── Session.java │ ├── text │ ├── messages_cs_CZ.properties │ ├── messages.properties │ ├── messages_nb.properties │ ├── messages_de.properties │ ├── messages_sv.properties │ ├── messages_nl.properties │ ├── messages_pl_PL.properties │ ├── messages_id.properties │ ├── messages_es_ES.properties │ ├── messages_lv.properties │ └── messages_fr_FR.properties │ ├── Main.java │ ├── UploadElement.java │ ├── Template.java │ ├── TemplateField.java │ ├── panes │ ├── LoginPane.java │ ├── ChooseDirectoryPane.java │ ├── CheckPane.java │ ├── StartPane.java │ ├── CreateFilePane.java │ ├── ChooseColumnsPane.java │ ├── UploadPane.java │ └── LoadPane.java │ ├── style │ └── style.css │ ├── Util.java │ └── Settings.java ├── .github └── ISSUE_TEMPLATE.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | bin 3 | tmp 4 | pattypan.jar 5 | *.zip 6 | -------------------------------------------------------------------------------- /lib/jxl.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/lib/jxl.jar -------------------------------------------------------------------------------- /lib/freemarker.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/lib/freemarker.jar -------------------------------------------------------------------------------- /lib/gson-2.9.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/lib/gson-2.9.0.jar -------------------------------------------------------------------------------- /lib/xmpcore-5.1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/lib/xmpcore-5.1.3.jar -------------------------------------------------------------------------------- /lib/BrowserLauncher2-1_3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/lib/BrowserLauncher2-1_3.jar -------------------------------------------------------------------------------- /src/pattypan/resources/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/src/pattypan/resources/gear.png -------------------------------------------------------------------------------- /src/pattypan/resources/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/src/pattypan/resources/image.png -------------------------------------------------------------------------------- /src/pattypan/resources/logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/src/pattypan/resources/logo.icns -------------------------------------------------------------------------------- /src/pattypan/resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/src/pattypan/resources/logo.png -------------------------------------------------------------------------------- /lib/metadata-extractor-2.11.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/lib/metadata-extractor-2.11.0.jar -------------------------------------------------------------------------------- /src/pattypan/resources/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yarl/pattypan/HEAD/src/pattypan/resources/refresh.png -------------------------------------------------------------------------------- /src/pattypan/Launcher.java: -------------------------------------------------------------------------------- 1 | package pattypan; 2 | 3 | import javafx.application.Platform; 4 | 5 | public class Launcher { 6 | public static void main(String[] args) { 7 | // Initialise JavaFX. 8 | Platform.startup(() -> {}); 9 | Main.main(args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | If you are reporting a bug please: 2 | 3 | - Check the bug has already been reported (https://github.com/yarl/pattypan/issues). 4 | - Explain the current issue. 5 | - Describe the expected behavior. 6 | - Add the problematic spreadsheet and a example media file (drag & drop here). 7 | - Add the PattyPan log file (drag & drop here). You find a link to the PattyPan log at the application start screen. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Paweł Marynowski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/pattypan/LogManager.java: -------------------------------------------------------------------------------- 1 | package pattypan; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.logging.ConsoleHandler; 8 | import java.util.logging.FileHandler; 9 | import java.util.logging.Logger; 10 | import java.util.logging.SimpleFormatter; 11 | import pattypan.Settings; 12 | 13 | public class LogManager { 14 | public static final Logger logger = Logger.getLogger(Main.class.getName()); 15 | 16 | public LogManager() { 17 | try { 18 | Path logFileLocation = Paths.get(Util.getApplicationDirectory() + "/logs"); 19 | if (!Files.exists(logFileLocation)) { 20 | Files.createDirectories(logFileLocation); 21 | } 22 | 23 | if (!Settings.getSetting("version").equals(Settings.VERSION)) { 24 | Files.deleteIfExists(Paths.get(logFileLocation.toString() + "/pattypan.log")); 25 | } 26 | 27 | FileHandler fh = new FileHandler(logFileLocation.toString() + "/pattypan.log", true); 28 | fh.setFormatter(new SimpleFormatter()); 29 | logger.addHandler(fh); 30 | logger.addHandler(new ConsoleHandler()); 31 | logger.setUseParentHandlers(false); // remove console prefix for privacy 32 | } catch (SecurityException e) { 33 | e.printStackTrace(); 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiScrollPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.scene.Node; 27 | import javafx.scene.control.ScrollPane; 28 | 29 | public class WikiScrollPane extends ScrollPane { 30 | 31 | public WikiScrollPane(Node content) { 32 | this.setFitToWidth(true); 33 | this.setContent(content); 34 | } 35 | 36 | public WikiScrollPane setWidth(int width) { 37 | this.setMaxWidth(width); 38 | this.setMinWidth(width); 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiRadioButton.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.scene.control.RadioButton; 27 | import javafx.scene.control.ToggleGroup; 28 | 29 | public class WikiRadioButton extends RadioButton { 30 | 31 | public WikiRadioButton(String id, ToggleGroup group) { 32 | super(); 33 | this.setToggleGroup(group); 34 | this.setId(id); 35 | } 36 | 37 | public WikiRadioButton setHeight(int height) { 38 | this.setMinHeight(height); 39 | this.setMaxHeight(height); 40 | return this; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiPasswordField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.scene.control.PasswordField; 27 | import pattypan.Util; 28 | 29 | public class WikiPasswordField extends PasswordField { 30 | 31 | public WikiPasswordField() { 32 | this.getStyleClass().addAll("mw-ui-input"); 33 | } 34 | 35 | public WikiPasswordField setPlaceholder(String text) { 36 | String locText = Util.text(text); 37 | this.setPromptText(locText.isEmpty() ? text : locText); 38 | return this; 39 | } 40 | 41 | public WikiPasswordField setWidth(int width) { 42 | this.setMaxWidth(width); 43 | this.setMinWidth(width); 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiTextField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.scene.control.TextField; 27 | import pattypan.Util; 28 | 29 | public class WikiTextField extends TextField { 30 | 31 | public WikiTextField(String text) { 32 | super(text); 33 | this.getStyleClass().addAll("mw-ui-input"); 34 | } 35 | 36 | public WikiTextField setPlaceholder(String text) { 37 | String locText = Util.text(text); 38 | this.setPromptText(locText.isEmpty() ? text : locText); 39 | return this; 40 | } 41 | 42 | public WikiTextField setWidth(int width) { 43 | this.setMaxWidth(width); 44 | this.setMinWidth(width); 45 | return this; 46 | } 47 | 48 | public WikiTextField setWidth(int width, int maxWidth) { 49 | this.setMaxWidth(maxWidth); 50 | this.setPrefWidth(maxWidth); 51 | this.setMinWidth(width); 52 | return this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/pattypan/Session.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import java.io.File; 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | import javafx.scene.Scene; 32 | import org.wikipedia.Wiki; 33 | import pattypan.LogManager; 34 | import java.util.logging.Logger; 35 | 36 | public final class Session { 37 | 38 | private Session() {}; 39 | 40 | public static Map SCENES = new HashMap<>(); 41 | 42 | public static File DIRECTORY; 43 | public static File FILE; 44 | 45 | public static ArrayList FILES = new ArrayList<>(); 46 | 47 | public static Logger LOGGER = new LogManager().logger; 48 | 49 | public static Wiki WIKI; 50 | 51 | public static String METHOD = "template"; 52 | public static String TEMPLATE = "Artwork"; 53 | public static String WIKICODE = ""; 54 | public static ArrayList VARIABLES = new ArrayList<>(Arrays.asList("path", "name")); 55 | 56 | public static ArrayList FILES_TO_UPLOAD = new ArrayList<>(); 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](http://i.imgur.com/Wjti8vi.png) 2 | 3 | Tool that simplifies [Wikimedia Commons](https://commons.wikimedia.org/) batch file uploading for [GLAM institution](https://outreach.wikimedia.org/wiki/GLAM) volunteers and employees. Created thanks to Wikimedia Foundation [IEG Grant](https://meta.wikimedia.org/wiki/Grants:IEG/Batch_uploader_for_small_GLAM_projects). 4 | 5 | For more information on usage, see [Commons:Pattypan](https://commons.wikimedia.org/wiki/Commons:Pattypan). 6 | 7 | __[:arrow_down: Download](https://github.com/yarl/pattypan/releases)__ 8 | 9 | ---- 10 | 11 | ### Build and run 12 | [Apache Ant](https://ant.apache.org/) is used for building Pattypan. You need to have JDK 11 or later installed as well as [a download of OpenJFX](https://gluonhq.com/products/javafx/) for each platform you want to support. In order to download and build source code, do the following: 13 | 14 | ``` 15 | git clone https://github.com/yarl/pattypan.git 16 | cd pattypan 17 | ant 18 | ``` 19 | 20 | This will run the default `build` target. It assumes that the current directory contains the OpenJFX SDK ZIP(s) and will unpack the required files to the correct locations. The resulting JAR will support Linux, Windows or both. The ZIPs present dictates what platforms will be supported. Note that the ZIPs should have their default name to be included. 21 | 22 | A temporary directory will be used during the build process and removed afterwards. It's default path is *tmp/* and can be set using `ant -Dtmp=...` 23 | 24 | You can also set test server or any other server: 25 | 26 | ``` 27 | java -jar pattypan.jar wiki="test.wikipedia.org" 28 | java -jar pattypan.jar wiki="test2.wikipedia.org" protocol="https://" scriptPath="/w" 29 | 30 | ``` 31 | 32 | Please note, that on test server file upload may be disabled for regular users. Admin account is suggested, you can request rights [here](https://test.wikipedia.org/wiki/Wikipedia:Requests/Permissions). If you have problems with program running, check [article on project wiki](https://github.com/yarl/pattypan/wiki/Run). 33 | 34 | ### License 35 | Copyright (c) 2016 Paweł Marynowski. 36 | 37 | Source code is available under the [MIT License](https://github.com/yarl/pattypan/blob/master/LICENSE). See the LICENSE file for more info. Program is using external libraries listed [here](https://github.com/yarl/pattypan/tree/master/lib). 38 | 39 | #### Credits 40 | Name by Wojciech Pędzich. Logo by [Rickterto](//commons.wikimedia.org/wiki/User:Rickterto), licensed under the Creative Commons [BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) license. 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiLabel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.geometry.Pos; 27 | import javafx.scene.control.Label; 28 | import javafx.scene.text.Text; 29 | import javafx.scene.text.TextAlignment; 30 | import pattypan.Util; 31 | 32 | public class WikiLabel extends Label { 33 | 34 | public WikiLabel(String text) { 35 | String locText = Util.text(text); 36 | this.setText(locText.isEmpty() ? text : locText); 37 | 38 | this.getStyleClass().addAll("mw-ui-text"); 39 | this.wrapTextProperty().setValue(true); 40 | this.setTextAlignment(TextAlignment.CENTER); 41 | } 42 | 43 | public WikiLabel setAlign(String position) { 44 | if(position.equals("left")) { 45 | this.setAlignment(Pos.CENTER_LEFT); 46 | this.setTextAlignment(TextAlignment.LEFT); 47 | } 48 | else if(position.equals("right")) { 49 | this.setAlignment(Pos.CENTER_RIGHT); 50 | this.setTextAlignment(TextAlignment.RIGHT); 51 | } 52 | else if(position.equals("center")) { 53 | this.setAlignment(Pos.CENTER); 54 | this.setTextAlignment(TextAlignment.CENTER); 55 | } 56 | return this; 57 | } 58 | 59 | public WikiLabel setClass(String cssClass) { 60 | this.getStyleClass().add(cssClass); 61 | return this; 62 | } 63 | 64 | public WikiLabel setTranslateByHalf(boolean right) { 65 | Text text = new Text(this.getText()); 66 | text.setFont(this.getFont()); 67 | double textWidth = text.getBoundsInLocal().getWidth(); 68 | this.setTranslateX(textWidth * 0.5 * (right ? 1 : -1)); 69 | return this; 70 | } 71 | 72 | public WikiLabel setWidth(int width) { 73 | this.setMaxWidth(width); 74 | this.setMinWidth(width); 75 | return this; 76 | } 77 | 78 | public WikiLabel setWidth(int width, int maxWidth) { 79 | this.setMaxWidth(maxWidth); 80 | this.setPrefWidth(maxWidth); 81 | this.setMinWidth(width); 82 | return this; 83 | } 84 | 85 | public WikiLabel setHeight(int width) { 86 | this.setMaxHeight(width); 87 | this.setMinHeight(width); 88 | return this; 89 | } 90 | 91 | public WikiLabel setWrapped(boolean wrap) { 92 | this.setWrapText(wrap); 93 | return this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_cs_CZ.properties: -------------------------------------------------------------------------------- 1 | check-intro=Kliknutím na každý z odkazů níže uvidíte náhled popisku, který se se souborem nahraje. 2 | check-name=Kontrola 3 | check-preview=Náhled 4 | choose-columns-advanced=Pokročilé 5 | choose-columns-fields-name=Název pole 6 | choose-columns-intro=V tomto kroce můžete zvolit zdroj pro vkládaný wikikód. Na výběr jsou data z již předpřipravených šablon, kód také můžete napsat sami 7 | choose-columns-name=Zvolte sloupce 8 | choose-columns-radio-buttons=Ano / Pevně daný / Ne 9 | choose-columns-template=Šablona 10 | choose-columns-template-doc=Dokumentace šablony 11 | choose-columns-template-intro=Zvolte pole, která považujete za nezbytná. Pokud vyberete volbu "Ano" můžete si sami zvolit obsah pole. S volbou "Pevně daný" se bude opakovat jedna předdefinovaná hodnota u všech souborů. 12 | choose-columns-value=Hodnota 13 | choose-columns-wikicode=Napište wikikód 14 | choose-directory-include-subdirectories=Zahrnout soubory z podadresářů. 15 | choose-directory-intro=Vyberte složku, která obsahuje soubory k nahrání. Klikněte "Procházet" pro vybrání složky. 16 | choose-directory-name=Vyberte složku 17 | choose-directory-no-files=Žádné soubory nejsou vhodné pro nahrávání. 18 | choose-directory-window-name=Vyberte složku 19 | create-file-back-to-start=Pokračovat a nahrát 20 | create-file-button=Vytvořit soubor 21 | create-file-error=Vytvoření tabulky se nezdařilo! 22 | create-file-filename=Název souboru (bez přípony) 23 | create-file-name=Vytvořit soubor 24 | create-file-open=Otevřít soubor 25 | create-file-success=Tabulka byla úspěšně vytvořena. 26 | create-file-summary=Bude vytvořena tabulka obsahující %s souborů ze složky %s. 27 | generic-back=Zpět 28 | generic-browse=Procházet 29 | generic-cancel=Storno 30 | generic-next=Další 31 | generic-summary=Shrnutí 32 | log-file=Soubor logu 33 | login-2fa=Klikněte sem, pokud jste povolili dvoufázovou autentizaci. 34 | login-failed=Přihlášení se nezdařilo 35 | login-intro=Přihlašte se k vašemu účtu prosím. 36 | login-load=Probíhá přihlašování... 37 | login-login-button=Přihlašte se 38 | login-login-field=Uživatelské jméno 39 | login-name=Přihlášení 40 | login-password-field=Heslo 41 | start-bug-found=Nalezli jste chybu? 42 | start-bug-report=Nahlašte jí! 43 | start-generate-button=Vygenerovat tabulku 44 | start-generate-description=Vyberte složku ve vašem počítači a podle dat v ní vygenerujte tabulku. 45 | start-new-version-available=Je k dispozici nová verze 46 | start-new-version-available-download=Stáhněte si ji nyní 47 | start-validate-button=Zkontrolovat a nahrát 48 | start-validate-description=Zkontrolujte, zda-li je tabulka bez chyb, a nahrajte soubory. 49 | upload-button=Nahrát 50 | upload-continue=Pokračovat 51 | upload-intro=Please click "Upload" in order to upload files. You can stop upload in any time clicking "Stop" button. 52 | upload-log-canceled=Upload cancel requested. No more images will be uploaded after currently uploading one. 53 | upload-log-done=Upload completed. %s files uploaded, %s files skipped. 54 | upload-log-error=Chyba: %s. 55 | upload-log-error-name-taken=Tento název se už používá 56 | upload-log-success=Soubor nahrán! 57 | upload-log-uploading=Nahrává se: %s 58 | upload-name=Nahrát 59 | upload-next-sheet=Nahrát data z další tabulky 60 | upload-retry=Zkusit znovu 61 | upload-stop=Zastavit 62 | validate-file-select=Vybrat soubor 63 | validate-file-type=soubory XLS (*.xls) 64 | validate-intro=Vyberte tabulku se soubory, které jsou připraveny k nahrání. Klikněte na "Procházet" pro vybrání souborů. 65 | validate-name=Načíst 66 | -------------------------------------------------------------------------------- /src/pattypan/text/messages.properties: -------------------------------------------------------------------------------- 1 | check-intro=Click on each link below to see preview of description, that will be uploaded. 2 | check-name=Check 3 | check-preview=Preview 4 | choose-columns-advanced=Advanced 5 | choose-columns-exif=Preload date from Exif 6 | choose-columns-fields-name=Field name 7 | choose-columns-intro=At this step you can set the source of wikicode. You can choose between already created templates and writing wikicode by your own. 8 | choose-columns-name=Choose columns 9 | choose-columns-radio-buttons=Yes / Const / No 10 | choose-columns-template=Template 11 | choose-columns-template-doc=Template documentation 12 | choose-columns-template-intro=Select fields you find necessary by using radio buttons below. If you choose "Yes", you can pre-populate fields using text field. If you choose "Const", value from text field will be applied to all files without possibility to change it per file. 13 | choose-columns-value=Value 14 | choose-columns-wikicode=Write wikicode 15 | choose-directory-include-subdirectories=Include files from subdirectories 16 | choose-directory-intro=Select directory which contains files, that will be described in spreadsheet. Click "Browse" to select directory. 17 | choose-directory-name=Choose directory 18 | choose-directory-no-files=No files suitable for upload. 19 | choose-directory-window-name=Choose directory 20 | create-file-back-to-start=Continue and upload 21 | create-file-button=Create file 22 | create-file-error=Error occurred during creation of spreadsheet file! 23 | create-file-filename=File name (without extension) 24 | create-file-name=Create file 25 | create-file-open=Open file 26 | create-file-success=Spreadsheet created successfully. 27 | create-file-summary=You will create spreadsheet with %s files from directory %s. 28 | generic-back=Back 29 | generic-browse=Browse 30 | generic-cancel=Cancel 31 | generic-next=Next 32 | generic-summary=Summary 33 | login-2fa=Click here if you enabled two-factor authentication. 34 | login-failed=Login failed 35 | log-file=Log file 36 | login-intro=Please login to your upload account. 37 | login-load=Logging in... 38 | login-login-button=Log in 39 | login-login-field=Login 40 | login-name=Login 41 | login-password-field=Password 42 | start-bug-found=Found bug? 43 | start-bug-report=Report it! 44 | start-generate-button=Generate Spreadsheet 45 | start-generate-description=Generate spreadsheet by selecting a directory on your hard drive. 46 | start-new-version-available=New version is available! 47 | start-new-version-available-download=Download now. 48 | start-validate-button=Validate & Upload 49 | start-validate-description=Check correctness of your spreadsheet and upload files. 50 | upload-button=Upload 51 | upload-continue=Continue 52 | upload-intro=Please click "Upload" in order to upload files. You can stop upload in any time clicking "Stop" button. 53 | upload-log-canceled=Upload cancel requested. No more images will be uploaded after currently uploading one. 54 | upload-log-done=Upload completed. %s files uploaded, %s files skipped. 55 | upload-log-error=Error: %s. 56 | upload-log-error-name-taken=This file name is already used 57 | upload-log-error-file-duplicate=This file is already uploaded as %s 58 | upload-log-success=File uploaded! 59 | upload-log-uploading=Uploading %s 60 | upload-name=Upload 61 | upload-next-sheet=Upload next spreadsheet 62 | upload-retry=Retry 63 | upload-stop=Stop 64 | validate-file-select=Select file 65 | validate-file-type=XLS files (*.xls) 66 | validate-intro=Choose spreadsheet with data of files ready to upload. Click "Browse" to select file. 67 | validate-name=Load 68 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_nb.properties: -------------------------------------------------------------------------------- 1 | check-intro=Klikk på hver lenke nedenfor for å se en forhåndsvisning av beskrivelsen som skal lastes opp. 2 | check-name=Sjekk 3 | check-preview=Forhåndsvis 4 | choose-columns-advanced=Avansert 5 | choose-columns-exif=Last inn data fra Exif 6 | choose-columns-fields-name=Feltnavn 7 | choose-columns-intro=I dette steget kan du sette kilden til wikikoden. Du kan velge mellom eksisterende maler og å skrive wikikoden selv. 8 | choose-columns-name=Velg kolonner 9 | choose-columns-radio-buttons=Ja / Konst / Nei 10 | choose-columns-template=Mal 11 | choose-columns-template-doc=Maldokumentasjon 12 | choose-columns-template-intro=Velg felter som du finner nødvendige ved å bruke knappene nedenfor. Om du velger «Ja» kan du fylle inn feltene på forhånd med tekstfeltet. Hvis du velger «Konst» vil verdien fra feltet gjelde alle filer uten mulighet for å endre den per fil. 13 | choose-columns-value=Verdi 14 | choose-columns-wikicode=Skriv wikikode 15 | choose-directory-include-subdirectories=Inkluder filer fra undermapper 16 | choose-directory-intro=Velg mappe som inneholder filer som skal beskrives i regnearket. Klikk «Bla gjennom» for å velge mappe. 17 | choose-directory-name=Velg mappe 18 | choose-directory-no-files=Ingen filer egnet for opplasting. 19 | choose-directory-window-name=Velg mappe 20 | create-file-back-to-start=Fortsett og last opp 21 | create-file-button=Opprett fil 22 | create-file-error=Feil oppsto under opprettelse av regneark! 23 | create-file-filename=Filnavn (uten endelse) 24 | create-file-name=Opprett fil 25 | create-file-open=Åpne fil 26 | create-file-success=Regneark opprettet. 27 | create-file-summary=Du vil opprette et regneark med %s filer fra mappa %s. 28 | generic-back=Tilbake 29 | generic-browse=Bla gjennom 30 | generic-cancel=Avbryt 31 | generic-next=Neste 32 | generic-summary=Sammendrag 33 | login-2fa=Klikk her hvis du har slått på totrinns pålogging. 34 | login-failed=Innlogging mislyktes 35 | log-file=Loggfil 36 | login-intro=Vennligst logg inn på kontoen din. 37 | login-load=Logger inn… 38 | login-login-button=Logg inn 39 | login-login-field=Innlogging 40 | login-name=Innlogging 41 | login-password-field=Passord 42 | start-bug-found=Funnet en feil? 43 | start-bug-report=Rapporter den! 44 | start-generate-button=Generer regneark 45 | start-generate-description=Generer regneark ved å velge ei mappe på harddisken din. 46 | start-new-version-available=Ny versjon tilgjengelig! 47 | start-new-version-available-download=Last ned nå. 48 | start-validate-button=Valider og last opp 49 | start-validate-description=Sjekk korrektheten til regnearket og last opp filer. 50 | upload-button=Last opp 51 | upload-continue=Fortsett 52 | upload-intro=Klikk på «Last opp» for å laste opp filene. Du kan stoppe opplastingen når som helst ved å klikke på «Stopp»-knappen. 53 | upload-log-canceled=Avbrytning av opplasting ønsket. Ingen bilder blir lastet opp etter det som lastes opp for øyeblikket. 54 | upload-log-done=Opplasting ferdig. %s filer lastet opp, %s filer hoppet over. 55 | upload-log-error=Feil: %s. 56 | upload-log-error-name-taken=Dette filnavnet er allerede i bruk. 57 | upload-log-error-file-duplicate=Denne filen er allerede lastet opp som %s 58 | upload-log-success=Fil lastet opp! 59 | upload-log-uploading=Laster opp %s 60 | upload-name=Last opp 61 | upload-next-sheet=Last opp neste regneark 62 | upload-retry=Prøv igjen 63 | upload-stop=Stopp 64 | validate-file-select=Velg fil 65 | validate-file-type=XLS-filer (*.xls) 66 | validate-intro=Velg regneark med data for filene som er klare for opplasting. Klikk «Bla gjennom» for å velge fil. 67 | validate-name=Last 68 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_de.properties: -------------------------------------------------------------------------------- 1 | check-intro=Anklicken der Links öffnet eine Vorschau der Beschreibung, die hochgeladen wird 2 | check-name=Kontrollieren 3 | check-preview=Vorschau 4 | choose-columns-advanced=Erweitert 5 | choose-columns-exif=Datum aus Exif laden 6 | choose-columns-fields-name=Feldname 7 | choose-columns-intro=In diesem Schritt kann der Wikitext eingeben werden. Es können bestehende oder neue Vorlagen verwendet werden 8 | choose-columns-name=Spalte auswählen 9 | choose-columns-radio-buttons=Ja / Konstant / Nein 10 | choose-columns-template=Vorlage 11 | choose-columns-template-doc=Vorlagendokumentation 12 | choose-columns-template-intro=Notwendige Felder können unterhalb ausgewählt werden. Die Option "Ja" füllt Felder automatisch aus. Die Option "Konstant" befüllt das Feld mit einem Text, der nicht für einzelne Felder angepasst wird 13 | choose-columns-value=Wert 14 | choose-columns-wikicode=Wikitext schreiben 15 | choose-directory-include-subdirectories=Datein aus Unterordnern inkludieren 16 | choose-directory-intro=Order auswählen, der die Datein enthält, die in der Tabelle beschrieben werden. Mit "Browse" kann ein Ordner gewählt werden. 17 | choose-directory-name=Ordner auswählen 18 | choose-directory-no-files=Keine uploadbaren Datein gefunden. 19 | choose-directory-window-name=Ordner auswählen 20 | create-file-back-to-start=Weiter und Upload 21 | create-file-button=Datei erstellen 22 | create-file-error=Ein Fehler ist während der Erstellung der Tabelle aufgetreten! 23 | create-file-filename=Dateiname (Ohne Endung) 24 | create-file-name=Datei erstellen 25 | create-file-open=Datei öffnen 26 | create-file-success=Tabelle erfolgreich erstellt. 27 | create-file-summary=Tabelle mit %s Dateien im Ordner %s erstellt. 28 | generic-back=Zurück 29 | generic-browse=Durchsuchen 30 | generic-cancel=Abbrechen 31 | generic-next=Weiter 32 | generic-summary=Zusammenfassung 33 | login-2fa=Hier klicken wenn Ihr Account 2-Faktor-Authentifizierung verwendet. 34 | login-failed=Login fehlgeschlagen 35 | log-file=Log-Datei 36 | login-intro=Bitte mit dem Upload-Account einloggen. 37 | login-load=Warten auf einloggen ... 38 | login-login-button=Einloggen 39 | login-login-field=Einloggen 40 | login-name=Einloggen 41 | login-password-field=Passwort 42 | start-bug-found=Bug gefunden? 43 | start-bug-report=Melde ihn hier! 44 | start-generate-button=Tabelle generieren 45 | start-generate-description=Durch auswählen eines Verzeichnisses wird eine Tabelle auf die Festplatte geschrieben. 46 | start-new-version-available=Eine neue Version ist verfügbar! 47 | start-new-version-available-download=Jetzt herunterladen. 48 | start-validate-button=Validieren & Upload 49 | start-validate-description=Tabelle auf Fehler überprüfen und Datein uploaden. 50 | upload-button=Uploaden 51 | upload-continue=Weiter 52 | upload-intro=Klick auf "Upload" lädt die Datein hoch. Der Upload kann jederzeit mit "Stop" abgebrochen werden. 53 | upload-log-canceled=Upload wird abgebrochen. Nach dieser Datei werden keine weiteren hochgeladen. 54 | upload-log-done=Upload abgeschlossen. %s Datein hochgeladen, %s Dateien übersprungen. 55 | upload-log-error=Fehler: %s. 56 | upload-log-error-name-taken=Dieser Dateiname wird bereits verwendet 57 | upload-log-success=Datei hochgeladen! 58 | upload-log-uploading=Wird hochgeladen %s 59 | upload-name=Upload 60 | upload-next-sheet=Nächste Tabelle hochladen 61 | upload-retry=Wiederholen 62 | upload-stop=Stop 63 | validate-file-select=Datei auswählen 64 | validate-file-type=XLS Dateien (*.xls) 65 | validate-intro=Tabelle auswählen, die zum Upload bereite Datein enthält. "Durchsuchen" erlaubt es Datein auszuwählen. 66 | validate-name=Laden 67 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_sv.properties: -------------------------------------------------------------------------------- 1 | check-intro=Klicka på varje länk nedan för att se en förhandsvisning av beskrivningen som kommer laddas upp. 2 | check-name=Kontrollera 3 | check-preview=Förhandsvisa 4 | choose-columns-advanced=Avancerad 5 | choose-columns-exif=Hämtatum från Exif 6 | choose-columns-fields-name=Fältnamn 7 | choose-columns-intro=Vidtta steg kan du välja källan till wikikoden. Du kan välja mellan en av redan skapade mallarna eller skriva din egen wikikod. 8 | choose-columns-name=Välj kolumner 9 | choose-columns-radio-buttons=Ja / Konstant / Nej 10 | choose-columns-template=Mall 11 | choose-columns-template-doc=Mall dokumentation 12 | choose-columns-template-intro=Välj fälten du har Bähov av genom att använda radioknapparna nedan. Om du väljer "Ja" så kan du genom textfältet förhandsvälja ett standard värde. Om du väljer "Konstant" så kommer värdet från textfältet att användas för alla filer utan att du har möjlighet att ändrat per fil. 13 | choose-columns-value=Värde 14 | choose-columns-wikicode=Skriv wikikod 15 | choose-directory-include-subdirectories=Inkludera filer från undermappar 16 | choose-directory-intro=Välj mappen som innehåller filerna som du vill beskriva i kalkylarket. Klicka "Bläddra" för att välja mapp. 17 | choose-directory-name=Välj mapp 18 | choose-directory-no-files=Inga filer är lämpliga för uppladdning. 19 | choose-directory-window-name=Välj mapp 20 | create-file-back-to-start=Fortsätt och ladda upp 21 | create-file-button=Skapa fil 22 | create-file-error= EttconvertUTF82Char: error1 FE!l inträffade under skapandet av kalkylarksfilen! 23 | create-file-filename=Filnamn (utan filändelse) 24 | create-file-name=Skapa fil 25 | create-file-open=öppna fil 26 | create-file-success=Kalkylarket skapades framgångsrikt. 27 | create-file-summary=Du kommer att skapa ett kalkylark med %s filer från mappen %s. 28 | generic-back=Tillbaka 29 | generic-browse=Bläddra 30 | generic-cancel=Avbryt 31 | generic-next=Nästa 32 | generic-summary=Sammanfattning 33 | login-2fa=Klicka här om du har tvåfaktorsautentisering aktiverat. 34 | login-failed=Inloggningen misslyckades 35 | log-file=Loggfil 36 | login-intro=Logga in med ditt tänkta uppladdningskonto. 37 | login-load=Loggar in... 38 | login-login-button=Logga in 39 | login-login-field=Logga in 40 | login-name=Logga in 41 | login-password-field=Lösenord 42 | start-bug-found=Hittat en bugg? 43 | start-bug-report=Rapporteran! 44 | start-generate-button=Generera kalkylark 45 | start-generate-description=Generera kalkylark genom att välja en mapp på din hårddisk. 46 | start-new-version-available=En ny version är tillgänglig! 47 | start-new-version-available-download=Ladda ner nu. 48 | start-validate-button=Validera och ladda upp 49 | start-validate-description=Kolla riktigheten för informationen i ditt kalkylark och ladda upp filer. 50 | upload-button=Ladda upp 51 | upload-continue=Fortsätt 52 | upload-intro=Klicka på "Ladda upp" för att ladda upp filer. Du kan när som helst avbryta genom att klicka "Stopp". 53 | upload-log-canceled=Uppladdningen avbryts. Inga fler filer änn som just nu laddas upp kommer att laddas upp. 54 | upload-log-done=Uppladdningen är klar. %s filer laddades upp, %s. 55 | upload-log-error=Fel: %s. 56 | upload-log-error-name-taken=Detta filnamn används redan 57 | upload-log-success=Filen är uppladdad! 58 | upload-log-uploading=Laddar upp %s 59 | upload-name=Ladda upp 60 | upload-next-sheet=Ladda upp nästa kalkylark 61 | upload-retry=Försök igen 62 | upload-stop=Stopp 63 | validate-file-select=Välj fil 64 | validate-file-type=XLS filer (*.xls) 65 | validate-intro=Välj kalkylark med informationen om filerna du vill ladda upp. Klicka "Blädda" för att välja fil. 66 | validate-name=Ladda -------------------------------------------------------------------------------- /src/pattypan/text/messages_nl.properties: -------------------------------------------------------------------------------- 1 | check-intro=Klik op elke link hieronder om een voorbeeld van de beschrijving, welke geüpload wordt, te zien. 2 | check-name=Controleren 3 | check-preview=Voorbeeld 4 | choose-columns-advanced=Geavanceerd 5 | choose-columns-exif=Gegevens laden uit Exif 6 | choose-columns-fields-name=Veldnaam 7 | choose-columns-intro=Bij deze stap kun je kiezen welke wikitekst je wilt. 8 | choose-columns-name=Kolommen kiezen 9 | choose-columns-radio-buttons=Ja / Constant / Nee 10 | choose-columns-template=Sjabloon 11 | choose-columns-template-doc=Sjabloonsbeschrijving 12 | choose-columns-template-intro=Selecteer de benodigde velden hieronder door middel van radioknoppen. Kies "ja" indien de waarde per bestand anders is, kies "constant" als deze voor elk bestand gelijk is. 13 | choose-columns-value=Waarde 14 | choose-columns-wikicode=Wikitekst invoeren 15 | choose-directory-include-subdirectories=Inclusief bestanden uit submappen 16 | choose-directory-intro=Selecteer een map die de bestanden bevat welke in het spreadsheet moeten worden beschreven. 17 | choose-directory-name=Map kiezen 18 | choose-directory-no-files=Geen bestanden die geüpload kunnen worden gevonden. 19 | choose-directory-window-name=Kies map 20 | create-file-back-to-start=Doorgaan en uploaden 21 | create-file-button=Maak bestand 22 | create-file-error=Er is een fout opgetreden tijdens het aanmaken van het spreadsheetbestand\! 23 | create-file-filename=Bestandsnaam (zonder extensie) 24 | create-file-name=Bestand maken 25 | create-file-open=Bestand openen 26 | create-file-success=Spreadsheet succesvol aangemaakt. 27 | create-file-summary=Je maakt een spreadsheet voor %s bestanden uit de map %s. 28 | generic-back=Terug 29 | generic-browse=Bladeren 30 | generic-cancel=Annuleer 31 | generic-next=Volgende 32 | generic-summary=Samenvatting 33 | login-2fa=Aanmelden als je two-factor authentication hebt ingeschakeld. 34 | login-failed=Aanmelden mislukt 35 | log-file=Logboekbestand 36 | login-intro=Meld je aan bij je uploadaccount. 37 | login-load=Aanmelden... 38 | login-login-button=Meld aan 39 | login-login-field=Gebruikersnaam 40 | login-name=Aanmelden 41 | login-password-field=Wachtwoord 42 | start-bug-found=Fout gevonden? 43 | start-bug-report=Meld deze hier! 44 | start-generate-button=Spreadsheet aanmaken 45 | start-generate-description=Maak een spreadsheet aan door een map op uw harde schijf te selecteren. 46 | start-new-version-available=Nieuwe versie is beschikbaar! 47 | start-new-version-available-download=Download nu. 48 | start-validate-button=Valideren en uploaden 49 | start-validate-description=Controleer of uw spreadsheet geldig is en upload bestanden. 50 | upload-button=Uploaden 51 | upload-continue=Doorgaan 52 | upload-intro=Klik op "upload" om uw bestanden te uploaden. Dit kan op elk moment worden stopgezet door op "stop" te klikken. 53 | upload-log-canceled=Verzoek om upload te stoppen, er worden geen bestanden meer geüpload na het huidige bestand. 54 | upload-log-done=Upload afgerond: %s bestanden geüpload, %s bestanden overgeslagen. 55 | upload-log-error=Foutmelding: %s. 56 | upload-log-error-name-taken=Deze bestandsnaam is al in gebruik 57 | upload-log-error-file-duplicate=Dit bestand is al geupload als %s 58 | upload-log-success=Bestand geupload! 59 | upload-log-uploading=Uploaden %s 60 | upload-name=Uploaden 61 | upload-next-sheet=Volgende spreadsheet uploaden 62 | upload-retry=Opnieuw proberen 63 | upload-stop=Stop 64 | validate-file-select=Selecteer bestand 65 | validate-file-type=XLS-bestanden (*.xls) 66 | validate-intro=Kies een spreadsheet met gegevens van bestanden die klaar zijn om te worden geupload. Klik op "bladeren" om een bestand te selecteren. 67 | validate-name=Laden 68 | -------------------------------------------------------------------------------- /src/pattypan/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import java.util.logging.Level; 27 | import javafx.application.Application; 28 | import javafx.scene.Scene; 29 | import javafx.scene.image.Image; 30 | import javafx.stage.Stage; 31 | import javafx.stage.WindowEvent; 32 | import org.wikipedia.Wiki; 33 | import pattypan.panes.StartPane; 34 | 35 | public class Main extends Application { 36 | @Override 37 | public void start(Stage stage) { 38 | Image logo = new Image(getClass().getResourceAsStream("/pattypan/resources/logo.png")); 39 | 40 | Scene scene = new Scene(new StartPane(stage), Settings.getSettingInt("windowWidth"), Settings.getSettingInt("windowHeight")); 41 | stage.setResizable(true); 42 | stage.setTitle("pattypan " + Settings.VERSION); 43 | stage.getIcons().add(logo); 44 | stage.setScene(scene); 45 | stage.show(); 46 | 47 | stage.setOnCloseRequest((WindowEvent we) -> { 48 | Settings.setSetting("windowWidth", (int) scene.getWidth() + ""); 49 | Settings.setSetting("windowHeight", (int) scene.getHeight() + ""); 50 | Settings.setSetting("version", Settings.VERSION); 51 | Settings.saveProperties(); 52 | }); 53 | } 54 | 55 | /** 56 | * @param args the command line arguments 57 | */ 58 | public static void main(String[] args) { 59 | String wiki = "commons.wikimedia.org"; 60 | String protocol = "https://"; 61 | String scriptPath = "/w"; 62 | for (String arg : args) { 63 | String[] pair = arg.split("="); 64 | if (pair[0].contains("wiki")) { 65 | wiki = pair[1]; 66 | } else if (pair[0].contains("protocol")) { 67 | protocol = pair[1]; 68 | } else if (pair[0].contains("scriptPath")) { 69 | scriptPath = pair[1]; 70 | } 71 | } 72 | 73 | Settings.readProperties(); 74 | 75 | Session.LOGGER.log(Level.INFO, 76 | "Wiki set as: {0}\nProtocol set as: {1}\nScript path set as: {2}", 77 | new String[]{wiki, protocol, scriptPath} 78 | ); 79 | 80 | String os = System.getProperty("os.name"); 81 | 82 | Session.LOGGER.log(Level.INFO, 83 | "Operating System: {0}\nPattypan Version: {1}", 84 | new String[]{os, Settings.VERSION} 85 | ); 86 | 87 | Session.WIKI = Wiki.newSession(wiki, scriptPath, protocol); 88 | Session.WIKI.setUserAgent(Settings.USERAGENT); 89 | Session.LOGGER.log(Level.INFO, "User-Agent: {0}", Session.WIKI.getUserAgent()); 90 | launch(args); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_pl_PL.properties: -------------------------------------------------------------------------------- 1 | check-intro=Kliknij linki poniżej, aby zobaczyć podgląd opisu ładowanych plików. 2 | check-name=Sprawdź opis 3 | check-preview=Podgląd 4 | choose-columns-advanced=Zaawansowane 5 | choose-columns-exif=Załaduj datę z Exif 6 | choose-columns-fields-name=Nazwa pola 7 | choose-columns-intro=Możesz teraz wybrać, jaki opis pasuje do ładowanych plików\: możesz wybrać jeden z domyślnie stosowanych szablonów lub samodzielnie utworzyć dowolny opis w wikikodzie. 8 | choose-columns-name=Wybierz kolumny 9 | choose-columns-radio-buttons=Tak / Stała / Nie 10 | choose-columns-template=Szablon 11 | choose-columns-template-doc=Dokumentacja szablonu 12 | choose-columns-template-intro=Wybierz pola które zamierzasz używać korzystając z pól jednokrotnego wyboru poniżej. Jeśli wybierzesz "Tak", akrusz zostanie uzupełniony wartością z pola tekstowego. Jeśli wybierzesz "Stała", wartość z pola tekstowego zostanie zastosowana bez możliwości późniejszej zmiany dla pojedynczego pliku. 13 | choose-columns-value=Wartość 14 | choose-columns-wikicode=Wpisz wikikod 15 | choose-directory-include-subdirectories=Uwzględnij pliki z podfolderów 16 | choose-directory-intro=Wybierz folder, który zawiera pliki do opisania w arkuszu kalkulacyjnym. Kliknij "Przeglądaj" aby wybrać folder na komputerze. 17 | choose-directory-name=Wybierz folder 18 | choose-directory-no-files=Brak plików spełniających kryteria wyszukiwania. 19 | choose-directory-window-name=Wybierz folder 20 | create-file-back-to-start=Kontynuuj i prześlij 21 | create-file-button=Utwórz arkusz 22 | create-file-error=Wystąpił błąd podczas generowania arkusza\! 23 | create-file-filename=Nazwa pliku (bez rozszerzenia) 24 | create-file-name=Utwórz arkusz 25 | create-file-open=Otwórz plik 26 | create-file-success=Arkusz został pomyślnie utworzony. 27 | create-file-summary=Zostanie utworzony arkusz z opisami %s plików z katalogu %s. 28 | generic-back=Wstecz 29 | generic-browse=Przeglądaj 30 | generic-cancel=Anuluj 31 | generic-next=Dalej 32 | generic-summary=Podsumowanie 33 | login-failed=Błąd logowania 34 | login-intro=Zaloguj się swoją nazwą użytkownika i hasłem w Wikimedia Commons. 35 | login-load=Logowanie... 36 | login-login-button=Zaloguj się 37 | login-login-field=Login 38 | login-name=Zaloguj 39 | login-password-field=Hasło 40 | start-bug-found=Widzisz błąd? 41 | start-bug-report=Zgłoś go! 42 | start-generate-button=Generuj arkusz 43 | start-generate-description=Aby utworzyć arkusz kalkulacyjny, wybierz folder na komputerze zawierający pliki do załadowania. 44 | start-new-version-available=Nowa wersja programu jest dostępna! 45 | start-new-version-available-download=Pobierz teraz. 46 | start-validate-button=Sprawdź i prześlij 47 | start-validate-description=Sprawdź, czy wprowadzone dane są poprawne, i załaduj pliki. 48 | upload-button=Prześlij 49 | upload-continue=Kontynuuj 50 | upload-intro=Kliknij przycisk "Prześlij", aby przesłać pliki do Wikimedia Commons. Przesyłanie można przerwać klikając przycisk "Zatrzymaj". 51 | upload-log-canceled=Przesyłanie plików zostało zatrzymane. Kolejne pliki nie będą ładowane. 52 | upload-log-done=Przesyłanie zakończone. Załadowano %s plików, nie załadowano %s plików. 53 | upload-log-error=Błąd: %s. 54 | upload-log-error-name-taken=Podana nazwa pliku jest już w użyciu 55 | upload-log-success=Plik przesłany! 56 | upload-log-uploading=Przesyłanie %s 57 | upload-name=Prześlij 58 | upload-next-sheet=Prześlij następny arkusz 59 | upload-retry=Ponów 60 | upload-stop=Zatrzymaj 61 | validate-file-select=Wybierz plik 62 | validate-file-type=pliki XLS (*.xls) 63 | validate-intro=Wybierz arkusz kalkulacyjny z opisami plików gotowych do przesłania. Kliknij "Przeglądaj" aby wybrać plik. 64 | validate-name=Załaduj 65 | log-file=Plik logu 66 | -------------------------------------------------------------------------------- /src/pattypan/UploadElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.net.URL; 29 | import java.net.MalformedURLException; 30 | import java.security.MessageDigest; 31 | import java.security.NoSuchAlgorithmException; 32 | import java.util.Map; 33 | import java.util.logging.Level; 34 | import java.util.logging.Logger; 35 | 36 | public final class UploadElement { 37 | 38 | private Map data; 39 | private String wikicode; 40 | 41 | public UploadElement() { 42 | } 43 | 44 | public UploadElement(Map data, String wikicode) { 45 | setData(data); 46 | setWikicode(wikicode); 47 | } 48 | 49 | private void addNameExtention() { 50 | String pathExt = Util.getExtFromFilename(getData("path")); 51 | String nameExt = Util.getExtFromFilename(getData("name")); 52 | 53 | if (!pathExt.equals(nameExt) && !Util.validUrl(getData("path"))) { 54 | setData("name", getData("name") + "." + pathExt); 55 | } 56 | } 57 | 58 | public Map getData() { 59 | return data; 60 | } 61 | 62 | public File getFile() { 63 | return new File(getData("path")); 64 | } 65 | 66 | public String getFileChecksum() { 67 | File file = this.getFile(); 68 | try { 69 | MessageDigest shaDigest = MessageDigest.getInstance("SHA-1"); 70 | String shaChecksum = Util.getFileChecksum(shaDigest, file); 71 | return shaChecksum; 72 | } catch (NoSuchAlgorithmException | IOException e) { 73 | Session.LOGGER.log(Level.SEVERE, null, e); 74 | return null; 75 | } 76 | } 77 | 78 | public URL getUrl() { 79 | try { 80 | return new URL(getData("path")); 81 | } catch(MalformedURLException e) { 82 | Session.LOGGER.log(Level.SEVERE, null, e); 83 | return null; 84 | } 85 | } 86 | 87 | public String getData(String key) { 88 | return data.get(key); 89 | } 90 | 91 | public String getWikicode() { 92 | return wikicode + "\n[[Category:Uploaded with pattypan]]"; 93 | } 94 | 95 | public UploadElement setData(Map data) { 96 | this.data = data; 97 | addNameExtention(); 98 | return this; 99 | } 100 | 101 | public UploadElement setData(String key, String data) { 102 | this.data.put(key, data); 103 | return this; 104 | } 105 | 106 | public UploadElement setWikicode(String wikicode) { 107 | this.wikicode = wikicode; 108 | return this; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_id.properties: -------------------------------------------------------------------------------- 1 | check-intro=Klik setiap pranala di bawah ini untuk melihat pratayang deskripsi yang akan diunggah. 2 | check-name=Periksa 3 | check-preview=Pratinjau 4 | choose-columns-advanced=Lebih lanjut 5 | choose-columns-exif=Muat tanggal dari Exif 6 | choose-columns-fields-name=Nama bidang teks 7 | choose-columns-intro=Pada tahap ini Anda dapat mengatur sumber kode wiki. Anda dapat memilih antara templat yang telah tersedia atau menulis kode wiki sendiri. 8 | choose-columns-name=Pilih kolom 9 | choose-columns-radio-buttons=Ya / Kons. / Tidak 10 | choose-columns-template=Templat 11 | choose-columns-template-doc=Dokumentasi templat 12 | choose-columns-template-intro=Pilih bidang teks yang Anda perlukan dengan menggunakan tombol radio. Jika Anda memilih "Ya", Anda dapat mengisi bidang dengan menggunakan bidang teks. Jika Anda memilih "Kons.", nilai dari bidang teks akan diterapkan ke seluruh berkas tanpa memungkinkan pengubahan per berkas. 13 | choose-columns-value=Nilai 14 | choose-columns-wikicode=Tulis kode wiki 15 | choose-directory-include-subdirectories=Masukkan berkas dari subdirektori 16 | choose-directory-intro=Pilih direktori yang berisi berkas yang akan dideskripsikan dalam lembatang sebar. Klik "Telusuri" untuk memilih direktori. 17 | choose-directory-name=Pilih direktori 18 | choose-directory-no-files=Tidak ada berkas yang cocok untuk diunggah. 19 | choose-directory-window-name=Pilih direktori 20 | create-file-back-to-start=Lanjutkan dan unggah 21 | create-file-button=Buat berkas 22 | create-file-error=Galat terjadi ketika pembuatan berkas lembatang sebar! 23 | create-file-filename=Nama berkas (tanpa ekstensi) 24 | create-file-name=Buat berkas 25 | create-file-open=Buka berkas 26 | create-file-success=Lembatang sebar sukses dibuat. 27 | create-file-summary=Anda akan membuat lembatang sebar dengan %s berkas dari direktori %s. 28 | generic-back=Mundur 29 | generic-browse=Telusuri 30 | generic-cancel=Batalkan 31 | generic-next=Maju 32 | generic-summary=Ringkasan 33 | login-2fa=Klik di sini jika Anda mengaktifkan autentikasi dua-faktor. 34 | login-failed=Masuk log gagal 35 | log-file=Berkas log 36 | login-intro=Harap masuk log ke akun unggahan Anda. 37 | login-load=Sedang masuk log... 38 | login-login-button=Masuk log 39 | login-login-field=Masuk log 40 | login-name=Masuk log 41 | login-password-field=Kata sandi 42 | start-bug-found=Menemukan kekutu? 43 | start-bug-report=Laporkan! 44 | start-generate-button=Hasilkan Lembatang Sebar 45 | start-generate-description=Hasilan lembatang sebar dengan memilih sebuah direktori dalam penggerak keras Anda. 46 | start-new-version-available=Versi terkini tersedia! 47 | start-new-version-available-download=Unduh sekarang. 48 | start-validate-button=Validasi & Unggah 49 | start-validate-description=Periksa ketepatan lembatang sebar Anda dan unggah berkas. 50 | upload-button=Unggah 51 | upload-continue=Lanjutkan 52 | upload-intro=Harap klik "Unggah" untuk mengunggah berkas. Anda dapat menghentikan unggahan kapan pun dengan mengeklik tombol "Berhenti". 53 | upload-log-canceled=Pembatalan unggahan telah diminta. Tidak ada lagi gambar yang akan diunggah setelah gambar ini. 54 | upload-log-done=Unggahan telah lengkap. %s berkas diunggah, %s berkas diloncati. 55 | upload-log-error=Galat: %s. 56 | upload-log-error-name-taken=Nama berkas ini telah digunakan 57 | upload-log-error-file-duplicate=Berkas ini telah diunggah dengan nama %s 58 | upload-log-success=Berkas diunggah! 59 | upload-log-uploading=Mengunggah %s 60 | upload-name=Unggah 61 | upload-next-sheet=Unggah lembatang sebar selanjutnya 62 | upload-retry=Ulangi 63 | upload-stop=Berhenti 64 | validate-file-select=Pilih berkas 65 | validate-file-type=Berkas XLS (*.xls) 66 | validate-intro=Pilih lembatang sebar dengan data berkas yang telah siap diunggah. Klik "Telusuri" untuk memilih berkas. 67 | validate-name=Muat 68 | -------------------------------------------------------------------------------- /src/pattypan/Template.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Arrays; 28 | import java.util.regex.Matcher; 29 | import java.util.regex.Pattern; 30 | 31 | public class Template { 32 | 33 | public String name; 34 | public TemplateField[] variables; 35 | public String wikicode; 36 | 37 | public Template(String name) { 38 | this.name = name; 39 | } 40 | 41 | public Template(String name, TemplateField[] variables, String wikicode) { 42 | this.name = name; 43 | this.variables = variables; 44 | this.wikicode = wikicode; 45 | } 46 | 47 | /** 48 | * Returns list of variables 49 | * 50 | * @return 51 | */ 52 | public ArrayList getVariables() { 53 | return new ArrayList<>(Arrays.asList(variables)); 54 | } 55 | 56 | /** 57 | * Returns selected template variables with additional "path" and "name" vars 58 | * 59 | * @return 60 | */ 61 | public ArrayList getComputedVariables() { 62 | ArrayList vars = new ArrayList<>(Arrays.asList("path", "name")); 63 | for (TemplateField tf : variables) { 64 | if (tf.isSelected) { 65 | vars.add(tf.name); 66 | } 67 | } 68 | vars.add("categories"); 69 | 70 | return vars; 71 | } 72 | 73 | /** 74 | * Gets "raw" wikicode, removes variables set as "No" and sets constant 75 | * strings for variables set as "Cont" 76 | * 77 | * @return string with computed wikicode 78 | */ 79 | public String getComputedWikicode() { 80 | ArrayList vars = getVariables(); 81 | String text = wikicode; 82 | 83 | for (TemplateField var : vars) { 84 | if (!var.isSelected && !var.value.isEmpty()) { 85 | text = text.replace("${" + var.name + "}", var.value); 86 | } else if (!var.isSelected && var.value.isEmpty()) { 87 | text = text.replace("${" + var.name + "}", ""); 88 | } 89 | } 90 | 91 | return text; 92 | } 93 | 94 | /** 95 | * 96 | * 97 | * @param text input string 98 | * @return 99 | */ 100 | public static ArrayList getComputedVariablesFromString(String text) { 101 | final Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}"); 102 | Matcher m = pattern.matcher(text); 103 | 104 | ArrayList results = new ArrayList<>(); 105 | results.add("path"); 106 | results.add("name"); 107 | 108 | while (m.find()) { 109 | results.add(m.group(1)); 110 | } 111 | return results; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_es_ES.properties: -------------------------------------------------------------------------------- 1 | check-intro=Haz clic en cada uno de los siguientes enlaces para obtener una vista previa de la descripción que se subirá. 2 | check-name=Comprueba 3 | check-preview=Vista previa 4 | choose-columns-advanced=Avanzado 5 | choose-columns-exif=Fecha de precarga de Exif 6 | choose-columns-fields-name=Nombre del campo 7 | choose-columns-intro=En este paso puedes establecer la fuente de wikicódigo. Puedes elegir entre plantillas ya creadas o escribir wikicódigo por tu cuenta. 8 | choose-columns-name=Elige columnas 9 | choose-columns-radio-buttons=Si / Const / No 10 | choose-columns-template=Plantilla 11 | choose-columns-template-doc=Documentación de plantilla 12 | choose-columns-template-intro=Selecciona los campos que consideres necesarios usando los siguientes botones. Si eliges "Sí", puedes completar los campos usando el campo de texto. Si seleccionas "Const", el valor del campo de texto se aplicará a todos los archivos sin posibilidad de cambiarlo en cada archivo. 13 | choose-columns-value=Valor 14 | choose-columns-wikicode=Escribe wikicódigo 15 | choose-directory-include-subdirectories=Incluye archivos de subdirectorios 16 | choose-directory-intro=Selecciona el directorio que contiene los archivos que se describirán en la hoja de cálculo. Haz clic en "Buscar" para seleccionar el directorio. 17 | choose-directory-name=Elige directorio 18 | choose-directory-no-files=No hay archivos adecuados para cargar. 19 | choose-directory-window-name=Elige directorio 20 | create-file-back-to-start=Continúa y carga 21 | create-file-button=Crea archivo 22 | create-file-error=¡Ocurrió un error durante la creación de la hoja de cálculo! 23 | create-file-filename=Nombre de archivo (sin extensión) 24 | create-file-name=Crea archivo 25 | create-file-open=Abre archivo 26 | create-file-success=Hoja de cálculo creada satisfactoriamente. 27 | create-file-summary=Crearás una hoja de cálculo con %s archivos del directorio %s. 28 | generic-back=Atrás 29 | generic-browse=Buscar 30 | generic-cancel=Cancelar 31 | generic-next=Siguiente 32 | generic-summary=Resumen 33 | login-2fa=Haz clic aquí si habilitaste la autenticación de dos factores. 34 | login-failed=Error de inicio de sesión 35 | log-file=Archivo de registro 36 | login-intro=Por favor inicia sesión con tu cuenta. 37 | login-load=Iniciando sesión... 38 | login-login-button=Iniciar sesión 39 | login-login-field=Acceso 40 | login-name=Acceso 41 | login-password-field=Contraseña 42 | start-bug-found=¿Encontraste un error? 43 | start-bug-report=¡Avísanos! 44 | start-generate-button=Generar hoja de cálculo 45 | start-generate-description=Generar una hoja de cálculo seleccionando un directorio de tu disco duro. 46 | start-new-version-available=¡Nueva versión disponible! 47 | start-new-version-available-download=Descarga ahora. 48 | start-validate-button=Validar y cargar 49 | start-validate-description=Verifica si está todo correcto en tu hoja de cálculo y carga los archivos. 50 | upload-button=Cargar 51 | upload-continue=Continúa 52 | upload-intro=Haz clic en "Cargar" para cargar los archivos. Puedes detener la carga en cualquier momento haciendo clic en el botón "Detener". 53 | upload-log-canceled=Cancelación de subida solicitada. No se subirán más imágenes después de cargar una. 54 | upload-log-done=Subida completada. %s archivos subidos, %s archivos omitidos. 55 | upload-log-error=Error: %s. 56 | upload-log-error-name-taken=Este nombre de archivo ya está usado 57 | upload-log-success=¡Archivo subido! 58 | upload-log-uploading=Cargando %s 59 | upload-name=Cargar 60 | upload-next-sheet=Sube la siguiente hoja de cálculo 61 | upload-retry=Intentar de nuevo 62 | upload-stop=Detener 63 | validate-file-select=Selecciona archivo 64 | validate-file-type=archisvos XLS (*.xls) 65 | validate-intro=Elige hoja de cálculo con datos de archivos lista para cargar. Haz clic en "Buscar" para seleccionar el archivo. 66 | validate-name=Carga 67 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_lv.properties: -------------------------------------------------------------------------------- 1 | check-intro=Klikšķini uz katras saites, lai apskatītu apraksta priekšskatījumu, kas tiks augšupielādēts. 2 | check-name=Pārbaudīt 3 | check-preview=Priekšskatīt 4 | choose-columns-advanced=Paplašināti 5 | choose-columns-exif=Ielādēt datumu no Exif 6 | choose-columns-fields-name=Lauka nosaukums 7 | choose-columns-intro=Šajā solī var norādīt vikikoda avotu. Tu vari izvēlēties starp jau izveidotām veidnēm un vikikoda rakstīšanu pašam. 8 | choose-columns-name=Izvēlēties kolonnas 9 | choose-columns-radio-buttons=Jā / Konst / Nē 10 | choose-columns-template=Veidne 11 | choose-columns-template-doc=Veidnes dokumentācija 12 | choose-columns-template-intro=Izvēlies laukus, ko uzskati par nepieciešamiem. Izvēloties "Jā", tu vari aizpildīt laukus, izmantojot teksta lauku. Izvēloties "Konst", visiem failiem tiks pielietota vērtība no teksta lauka, bez iespējas to norādīt individuāli katram failam. 13 | choose-columns-value=Vērība 14 | choose-columns-wikicode=Ieraksti vikikodu 15 | choose-directory-include-subdirectories=Iekļaut failus no apakšdirektorijām 16 | choose-directory-intro=Izvēlies direktoriju, kas satur failus, kas tiks aprakstīti izklājlapā. Klikšķini uz "Pārlūkot", lai izvēlētos direktoriju. 17 | choose-directory-name=Izvēlēties direktoriju 18 | choose-directory-no-files=Augšupielādei nav derīgs neviens fails. 19 | choose-directory-window-name=Izvēlēties direktoriju 20 | create-file-back-to-start=Turpināt un augšupielādēt 21 | create-file-button=Izveidot failu 22 | create-file-error=Veidojot izklājlapas failu, notika kļūda! 23 | create-file-filename=Faila nosaukums (bez paplašinājuma) 24 | create-file-name=Izveidot failu 25 | create-file-open=Atvērt failu 26 | create-file-success=Izklājlapa veiksmīgi izveidota. 27 | create-file-summary=Tiks izveidota izklājlapa ar %s failiem no direktorijas %s. 28 | generic-back=Atpakaļ 29 | generic-browse=Pārlūkot 30 | generic-cancel=Atcelt 31 | generic-next=Tālāk 32 | generic-summary=Kopsavilkums 33 | login-2fa=Klikšķini šeit, tavam kontam ir iespējota divfaktoru autentifikācija. 34 | login-failed=Pieslēgšanās neizdevās 35 | log-file=Žurnāla fails 36 | login-intro=Lūdzu, pieslēdzies ar savu augšupielādes kontu. 37 | login-load=Pieslēdzas... 38 | login-login-button=Pieslēgties 39 | login-login-field=Pieslēgties 40 | login-name=Lietotājvārds 41 | login-password-field=Parole 42 | start-bug-found=Atradi kļūdu? 43 | start-bug-report=Paziņo par to! 44 | start-generate-button=Izveidot izklājlapu 45 | start-generate-description=Izveidot izklājlapu, izvēloties direktoriju no sava datora. 46 | start-new-version-available=Pieejam jauna versija! 47 | start-new-version-available-download=Lejupielādēt tagad. 48 | start-validate-button=Pārbaudīt un augšupielādēt 49 | start-validate-description=Pārbaudi labojumus savā izklājlapā un augšupielādējamos failos. 50 | upload-button=Augšupielādēt 51 | upload-continue=Turpināt 52 | upload-intro=Lai augšupielādētu failus, lūdzu, klikšķini uz "Augšupielādēt". Augšupielādi var apturēt jebkurā brīdī, klikšķinot uz pogas "Apturēt". 53 | upload-log-canceled=Pieprasīta augšupielādes apturēšana. Pēc pašlaik ielādējamā faila vairs netiks ielādēts neviens cits. 54 | upload-log-done=Augšupielāde pabeigta. Augšupielādēti %s faili, izlaisti %s faili. 55 | upload-log-error=Kļūda: %s. 56 | upload-log-error-name-taken=Šis faila nosaukums jau tiek izmantots 57 | upload-log-error-file-duplicate=Šis fails jau ir augšupielādēts kā %s 58 | upload-log-success=Fails augšupielādēts! 59 | upload-log-uploading=Augšupielādē %s 60 | upload-name=Augšupielādēt 61 | upload-next-sheet=Augšupielādēt nākamo izklājlapu 62 | upload-retry=Mēģināt vēlreiz 63 | upload-stop=Apturēt 64 | validate-file-select=Izvēlēties failu 65 | validate-file-type=XLS faili (*.xls) 66 | validate-intro=Izvēlies izklājlapu ar datiem par augšupielādei gatavajiem failiem. Klikšķini uz "Pārlūkot", lai izvēlētos failu. 67 | validate-name=Ielādēt 68 | -------------------------------------------------------------------------------- /src/pattypan/text/messages_fr_FR.properties: -------------------------------------------------------------------------------- 1 | check-intro=Cliquez sur chaque lien ci-dessous pour voir un aperçu de la description qui sera téléchargée. 2 | check-name=Vérifier 3 | check-preview=Aperçu 4 | choose-columns-advanced=Avancé 5 | choose-columns-exif=Précharger la date depuis les métadonnées Exif 6 | choose-columns-fields-name=Nom du champ 7 | choose-columns-intro=A cette étape, vous pouvez définir le wikicode source. Vous pouvez choisir entre les modèles déjà créés et écrire votre propre wikicode. 8 | choose-columns-name=Choisir les colonnes 9 | choose-columns-radio-buttons=Oui / Const / Non 10 | choose-columns-template=Modèle 11 | choose-columns-template-doc=Documentation du modèle 12 | choose-columns-template-intro=Sélectionnez les champs que vous jugez nécessaires en utilisant les boutons radio ci-dessous. Si vous choisissez "Oui", vous pouvez pré-remplir les champs à l'aide du champ texte. Si vous choisissez "Const", la valeur du champ texte sera appliquée à tous les fichiers sans possibilité de la modifier par fichier. 13 | choose-columns-value=Valeur 14 | choose-columns-wikicode=Écrire le wikicode 15 | choose-directory-include-subdirectories=Inclure les fichiers des sous-répertoires 16 | choose-directory-intro=Sélectionnez le répertoire qui contient les fichiers qui seront décrits dans le tableur. Cliquez sur "Parcourir" pour sélectionner le répertoire. 17 | choose-directory-name=Choisir un répertoire 18 | choose-directory-no-files=Aucun fichier ne peut être téléversé. 19 | choose-directory-window-name=Choisir un répertoire 20 | create-file-back-to-start=Continuer et envoyer 21 | create-file-button=Créer un fichier 22 | create-file-error=Une erreur s'est produite lors de la création du fichier tableur ! 23 | create-file-filename=Nom de fichier (sans extension) 24 | create-file-name=Créer un fichier 25 | create-file-open=Ouvrir le fichier 26 | create-file-success=Tableur créé avec succès. 27 | create-file-summary=Vous allez créer un tableur avec %s fichiers à partir du répertoire %s. 28 | generic-back=Retour 29 | generic-browse=Parcourir 30 | generic-cancel=Annuler 31 | generic-next=Suivant 32 | generic-summary=Résumé 33 | login-2fa=Cliquez ici si vous avez activé l'authentification à deux facteurs. 34 | login-failed=Échec de la connexion 35 | log-file=Fichier log 36 | login-intro=Veuillez vous connecter à votre compte de téléversement. 37 | login-load=Connexion... 38 | login-login-button=Connexion 39 | login-login-field=Identifiant 40 | login-name=Identifiant 41 | login-password-field=Mot de passe 42 | start-bug-found=Vous avez trouvé un bug ? 43 | start-bug-report=Le signaler ! 44 | start-generate-button=Générer un tableur 45 | start-generate-description=Générer un tableur en sélectionnant un répertoire sur votre disque dur. 46 | start-new-version-available=Nouvelle version disponible ! 47 | start-new-version-available-download=Télécharger maintenant. 48 | start-validate-button=Valider et envoyer 49 | start-validate-description=Vérifier l'exactitude de votre tableur et envoyer les fichiers. 50 | upload-button=Envoyer 51 | upload-continue=Continuer 52 | upload-intro=Veuillez cliquer sur "Envoyer" pour envoyer les fichiers. Vous pouvez arrêter le téléchargement à tout moment en cliquant sur le bouton "Arrêter". 53 | upload-log-canceled=Annulation de l'envoi demandée. Aucune autre image ne sera envoyée après l'envoi courant. 54 | upload-log-done=Envoi terminé. %s fichiers téléchargés, %s fichiers ignorés. 55 | upload-log-error=Erreur : %s. 56 | upload-log-error-name-taken=Ce nom de fichier est déjà utilisé 57 | upload-log-success=Fichier envoyé ! 58 | upload-log-uploading=Envoi de %s 59 | upload-name=Envoi 60 | upload-next-sheet=Envoyer le tableur suivant 61 | upload-retry=Réessayer 62 | upload-stop=Arrêter 63 | validate-file-select=Sélectionner un fichier 64 | validate-file-type=Fichiers XLS (*.xls) 65 | validate-intro=Choisissez un tableur avec les données des fichiers prêts à être envoyés. Cliquez sur "Parcourir" pour sélectionner le fichier. 66 | validate-name=Charger 67 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiProgressBar.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.geometry.HPos; 27 | import javafx.scene.control.ProgressBar; 28 | import javafx.scene.layout.GridPane; 29 | import javafx.scene.layout.Pane; 30 | import javafx.scene.shape.Circle; 31 | import pattypan.Util; 32 | 33 | public class WikiProgressBar extends GridPane { 34 | 35 | String[] labels; 36 | double progress; 37 | 38 | public WikiProgressBar(double progress, String... labels) { 39 | super(); 40 | this.labels = labels; 41 | if (labels.length == 3) { 42 | createContent(progress); 43 | } else if (labels.length == 4) { 44 | createContent4(progress); 45 | } 46 | } 47 | 48 | private Pane createDot(double number, int translateX) { 49 | Circle c = new Circle(5); 50 | c.getStyleClass().add("mw-ui-progressbar-dot" + (isActive(number) ? "-active" : "")); 51 | 52 | Pane p = new Pane(c); 53 | p.setTranslateX(translateX); 54 | p.setTranslateY(-2); 55 | return p; 56 | } 57 | 58 | private WikiLabel createLabel(double number, String label) { 59 | return new WikiLabel(label).setClass("mw-ui-progressbar-text" + (isActive(number) ? "-active" : "")); 60 | } 61 | 62 | private boolean isActive(double number) { 63 | return progress >= (number / 2.0); 64 | } 65 | 66 | private GridPane createContent(double progress) { 67 | this.progress = progress; 68 | this.setMinWidth(420); 69 | this.setMaxWidth(420); 70 | this.getStyleClass().add("mw-ui-progressbar-container"); 71 | 72 | this.getColumnConstraints().addAll( 73 | Util.newColumn(33, "%", HPos.LEFT), 74 | Util.newColumn(33, "%", HPos.CENTER), 75 | Util.newColumn(33, "%", HPos.RIGHT)); 76 | 77 | ProgressBar pb = new ProgressBar(progress); 78 | pb.getStyleClass().addAll("mw-ui-progressbar"); 79 | pb.setMinWidth(420); 80 | pb.setMaxWidth(420); 81 | pb.setMaxHeight(5); 82 | this.add(pb, 0, 0, 3, 1); 83 | 84 | this.addRow(1, 85 | createDot(0.0, 1), 86 | createDot(1.0, 70), 87 | createDot(2.0, 140)); 88 | 89 | this.addRow(2, 90 | createLabel(0.0, labels[0]).setTranslateByHalf(false), 91 | createLabel(1.0, labels[1]), 92 | createLabel(2.0, labels[2]).setTranslateByHalf(true) 93 | ); 94 | return this; 95 | } 96 | 97 | private GridPane createContent4(double progress) { 98 | this.progress = progress; 99 | this.setMinWidth(420); 100 | this.setMaxWidth(420); 101 | this.getStyleClass().add("mw-ui-progressbar-container"); 102 | 103 | this.getColumnConstraints().addAll( 104 | Util.newColumn(25, "%", HPos.LEFT), 105 | Util.newColumn(25, "%", HPos.CENTER), 106 | Util.newColumn(25, "%", HPos.CENTER), 107 | Util.newColumn(25, "%", HPos.RIGHT)); 108 | 109 | ProgressBar pb = new ProgressBar(progress); 110 | pb.getStyleClass().addAll("mw-ui-progressbar"); 111 | pb.setMinWidth(420); 112 | pb.setMaxWidth(420); 113 | pb.setMaxHeight(5); 114 | this.add(pb, 0, 0, 3, 1); 115 | 116 | this.addRow(1, 117 | createDot(0.0, 1), 118 | createDot(0.66, 33), 119 | createDot(1.32, 66), 120 | createDot(2.0, 100)); 121 | 122 | this.addRow(2, 123 | createLabel(0.0, labels[0]).setTranslateByHalf(false), 124 | createLabel(0.66, labels[1]).setTranslateByHalf(false), 125 | createLabel(1.32, labels[2]).setTranslateByHalf(true), 126 | createLabel(2.0, labels[3]).setTranslateByHalf(true) 127 | ); 128 | return this; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import javafx.geometry.Insets; 27 | import javafx.geometry.Pos; 28 | import javafx.scene.Node; 29 | import javafx.scene.layout.BorderPane; 30 | import javafx.scene.layout.HBox; 31 | import javafx.scene.layout.Pane; 32 | import javafx.scene.layout.Priority; 33 | import javafx.scene.layout.Region; 34 | import javafx.scene.layout.VBox; 35 | import javafx.scene.text.TextAlignment; 36 | import javafx.stage.Stage; 37 | import pattypan.Util; 38 | 39 | public class WikiPane extends BorderPane { 40 | 41 | String css = getClass().getResource("/pattypan/style/style.css").toExternalForm(); 42 | Stage stage; 43 | 44 | public HBox topContainer = new HBox(); 45 | public VBox centerContainer = new VBox(15); 46 | public HBox bottomContainer = new HBox(); 47 | 48 | public WikiButton prevButton = new WikiButton("generic-back", "inversed").setWidth(250); 49 | public WikiButton nextButton = new WikiButton("generic-next", "inversed").setWidth(250); 50 | 51 | private final String[] progressBarLabels = { 52 | Util.text("choose-directory-name"), 53 | Util.text("choose-columns-name"), 54 | Util.text("create-file-name") 55 | }; 56 | private final String[] progressBarLabels2 = { 57 | Util.text("validate-name"), 58 | Util.text("check-name"), 59 | Util.text("login-name"), 60 | Util.text("upload-name") 61 | }; 62 | 63 | public WikiPane(Stage stage, double value) { 64 | this.stage = stage; 65 | this.getStylesheets().add(css); 66 | this.getStyleClass().add("background"); 67 | 68 | topContainer.setAlignment(Pos.CENTER); 69 | topContainer.getChildren().add(new WikiProgressBar( 70 | value > 1.0 ? value - 1.0 : value, 71 | value > 1.0 ? progressBarLabels2 : progressBarLabels) 72 | ); 73 | 74 | centerContainer.setAlignment(Pos.TOP_CENTER); 75 | 76 | Region region = new Region(); 77 | HBox.setHgrow(region, Priority.ALWAYS); 78 | bottomContainer.getChildren().addAll(prevButton, region, nextButton); 79 | BorderPane.setMargin(bottomContainer, new Insets(10, 0, 0, 0)); 80 | 81 | this.setTop(topContainer); 82 | this.setCenter(centerContainer); 83 | this.setBottom(bottomContainer); 84 | } 85 | 86 | public boolean removeElement(Node element) { 87 | return centerContainer.getChildren().remove(element); 88 | } 89 | 90 | public void addElement(Node element) { 91 | centerContainer.getChildren().add(element); 92 | } 93 | 94 | public void addElement(String text, String cssClass, int height) { 95 | WikiLabel label = new WikiLabel(text).setWrapped(true); 96 | label.setTextAlignment(TextAlignment.LEFT); 97 | label.setClass(cssClass); 98 | 99 | VBox box = new VBox(); 100 | box.getChildren().add(label); 101 | box.setMinHeight(height); 102 | addElement(box); 103 | } 104 | 105 | public void addElement(String text, int height) { 106 | addElement(text, "", height); 107 | } 108 | 109 | public void addElement(String text, String cssClass) { 110 | addElement(text, cssClass, 0); 111 | } 112 | 113 | public void addElement(String text) { 114 | addElement(text, "", 0); 115 | } 116 | 117 | public void addElementRow(Pane target, double spacing, Node[] element, Priority[] priority) { 118 | HBox hb = new HBox(spacing); 119 | hb.getChildren().addAll(element); 120 | 121 | for (int i = 0; i < priority.length; i++) { 122 | HBox.setHgrow(element[i], priority[i]); 123 | } 124 | target.getChildren().add(hb); 125 | } 126 | 127 | public void addElementRow(double spacing, Node[] element, Priority[] priority) { 128 | addElementRow(centerContainer, spacing, element, priority); 129 | } 130 | 131 | public void addElementRow(Node[] element, Priority[] priority) { 132 | addElementRow(8, element, priority); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/pattypan/elements/WikiButton.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.elements; 25 | 26 | import java.lang.reflect.Constructor; 27 | import java.lang.reflect.InvocationTargetException; 28 | import java.lang.reflect.Method; 29 | import java.util.HashMap; 30 | import java.util.logging.Level; 31 | import java.util.logging.Logger; 32 | import javafx.event.ActionEvent; 33 | import javafx.scene.Scene; 34 | import javafx.scene.control.Button; 35 | import javafx.scene.image.Image; 36 | import javafx.scene.image.ImageView; 37 | import javafx.scene.layout.Pane; 38 | import javafx.stage.Stage; 39 | import pattypan.Session; 40 | import pattypan.Util; 41 | 42 | public class WikiButton extends Button { 43 | 44 | public WikiButton(String name) { 45 | this(name, "large"); 46 | } 47 | 48 | public WikiButton(String name, String type) { 49 | this(name, type, "large"); 50 | } 51 | 52 | public WikiButton(String name, String... types) { 53 | String locName = Util.text(name); 54 | this.setText(locName.isEmpty() ? name : locName); 55 | 56 | for (int i = 0; i < types.length; i++) { 57 | if(types[i].equals("small")) { 58 | this.setMinHeight(31); 59 | } 60 | 61 | types[i] = types[i].isEmpty() ? "" : "mw-ui-" + types[i]; 62 | } 63 | this.getStyleClass().add("mw-ui-button"); 64 | this.getStyleClass().addAll(types); 65 | 66 | this.wrapTextProperty().setValue(true); 67 | this.setMaxWidth(200); 68 | this.setMinWidth(200); 69 | } 70 | 71 | private Pane getPaneByPaneName(String name, Stage stage) { 72 | try { 73 | Class paneClass = Class.forName("pattypan.panes." + name); 74 | Constructor constructor = paneClass.getConstructor(Stage.class); 75 | Object instance = constructor.newInstance(stage); 76 | Method content = instance.getClass().getMethod("getContent"); 77 | 78 | return (Pane) content.invoke(instance); 79 | } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { 80 | Logger.getLogger(WikiButton.class.getName()).log(Level.SEVERE, null, ex); 81 | } 82 | return new Pane(); 83 | } 84 | 85 | public WikiButton linkTo(String paneName, Stage stage) { 86 | this.setOnAction((ActionEvent event) -> { 87 | goTo(paneName, stage, false); 88 | }); 89 | return this; 90 | } 91 | 92 | public WikiButton linkTo(String paneName, Stage stage, boolean clearScenes) { 93 | this.setOnAction((ActionEvent event) -> { 94 | goTo(paneName, stage, clearScenes); 95 | }); 96 | return this; 97 | } 98 | 99 | public void goTo(String paneName, Stage stage, boolean clearScenes) { 100 | if (clearScenes) { 101 | Session.SCENES = new HashMap<>(); 102 | } 103 | 104 | Scene scene = Session.SCENES.containsKey(paneName) 105 | ? Session.SCENES.get(paneName) 106 | : new Scene(getPaneByPaneName(paneName, stage), Util.WINDOW_WIDTH, Util.WINDOW_HEIGHT); 107 | 108 | if(!Session.SCENES.containsKey(paneName)) { 109 | Session.SCENES.put(paneName, scene); 110 | } 111 | 112 | double oldWidth = stage.getWidth(); 113 | double oldHeight = stage.getHeight(); 114 | stage.setScene(scene); 115 | stage.setWidth(oldWidth); 116 | stage.setHeight(oldHeight); 117 | } 118 | 119 | public WikiButton setIcon(String name) { 120 | Image image = new Image(getClass().getResourceAsStream("/pattypan/resources/" + name)); 121 | ImageView iv = new ImageView(image); 122 | 123 | this.setGraphic(iv); 124 | return this; 125 | } 126 | 127 | public WikiButton setWidth(int width) { 128 | this.setMaxWidth(width); 129 | this.setMinWidth(width); 130 | return this; 131 | } 132 | 133 | public WikiButton setLabel(String text) { 134 | this.setText(text); 135 | return this; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/pattypan/TemplateField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import javafx.beans.value.ObservableValue; 27 | import javafx.event.ActionEvent; 28 | import javafx.scene.control.CheckBox; 29 | import javafx.scene.control.RadioButton; 30 | import javafx.scene.control.Toggle; 31 | import javafx.scene.control.ToggleGroup; 32 | import javafx.scene.input.KeyEvent; 33 | import javafx.scene.layout.HBox; 34 | import javafx.scene.layout.Region; 35 | import javafx.scene.layout.VBox; 36 | import pattypan.elements.WikiLabel; 37 | import pattypan.elements.WikiRadioButton; 38 | import pattypan.elements.WikiTextField; 39 | 40 | public class TemplateField { 41 | 42 | public String name; 43 | public String label; 44 | public boolean isSelected; 45 | public String selection; 46 | public String value; 47 | 48 | CheckBox cb; 49 | ToggleGroup group = new ToggleGroup(); 50 | RadioButton buttonYes = new WikiRadioButton("YES", group).setHeight(35); 51 | RadioButton buttonConst = new WikiRadioButton("CONST", group).setHeight(35); 52 | RadioButton buttonNo = new WikiRadioButton("NO", group).setHeight(35); 53 | 54 | WikiLabel labelElement = new WikiLabel(""); 55 | WikiTextField valueText = new WikiTextField("").setWidth(50, 500); 56 | 57 | public TemplateField(String name, String label, boolean isSelected, String constant) { 58 | this.name = name; 59 | this.label = label; 60 | this.isSelected = isSelected; 61 | this.selection = "YES"; 62 | this.value = constant; 63 | 64 | labelElement = new WikiLabel(label).setWidth(200, 500).setHeight(35); 65 | buttonYes.setSelected(true); 66 | 67 | group.selectedToggleProperty().addListener((ObservableValue ov, Toggle tOld, Toggle tNew) -> { 68 | RadioButton btn = (RadioButton) tNew.getToggleGroup().getSelectedToggle(); 69 | setSelection(btn.getId()); 70 | }); 71 | 72 | valueText.setOnKeyReleased((KeyEvent event) -> { 73 | this.value = valueText.getText(); 74 | }); 75 | } 76 | 77 | public TemplateField(String name, String label) { 78 | this(name, label, true, ""); 79 | } 80 | 81 | public VBox getRow() { 82 | Region spacer = new Region(); 83 | spacer.setMinWidth(20); 84 | 85 | VBox vb = new VBox(5); 86 | HBox hb = new HBox(10); 87 | HBox hbCheckbox = new HBox(10); 88 | 89 | valueText.setText(Settings.getSetting("var-" + name + "-value")); 90 | value = Settings.getSetting("var-" + name + "-value"); 91 | setSelection(Settings.getSetting("var-" + name + "-selection")); 92 | 93 | hb.getChildren().addAll(labelElement, 94 | buttonYes, buttonConst, buttonNo, 95 | spacer, valueText, new Region()); 96 | vb.getChildren().add(hb); 97 | 98 | if (name.equals("date")) { 99 | Region r = new Region(); 100 | r.setMaxWidth(622); 101 | r.setPrefWidth(622); 102 | r.setMinWidth(420); 103 | r.setMinHeight(30); 104 | 105 | CheckBox checkbox = new CheckBox(Util.text("choose-columns-exif")); 106 | checkbox.setMaxWidth(500); 107 | checkbox.setPrefWidth(500); 108 | checkbox.setMinWidth(305); 109 | checkbox.setSelected(Settings.getSetting("exifDate").equals("true")); 110 | checkbox.setOnAction((ActionEvent e) -> { 111 | Settings.setSetting("exifDate", checkbox.isSelected() ? "true" : ""); 112 | }); 113 | 114 | hbCheckbox.getChildren().addAll(r, checkbox); 115 | vb.getChildren().add(hbCheckbox); 116 | } 117 | 118 | return vb; 119 | } 120 | 121 | public void setSelection(String id) { 122 | this.selection = id; 123 | switch (id) { 124 | case "YES": 125 | valueText.setVisible(true); 126 | labelElement.setDisable(false); 127 | buttonYes.setSelected(true); 128 | this.isSelected = true; 129 | break; 130 | case "CONST": 131 | valueText.setVisible(true); 132 | labelElement.setDisable(true); 133 | buttonConst.setSelected(true); 134 | this.isSelected = false; 135 | break; 136 | case "NO": 137 | valueText.setVisible(false); 138 | valueText.setText(""); 139 | labelElement.setDisable(true); 140 | buttonNo.setSelected(true); 141 | this.isSelected = false; 142 | break; 143 | default: 144 | break; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/pattypan/panes/LoginPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import javafx.application.Platform; 27 | import javafx.concurrent.Task; 28 | import javafx.event.ActionEvent; 29 | import javafx.scene.control.Hyperlink; 30 | import javafx.scene.input.KeyCode; 31 | import javafx.scene.input.KeyEvent; 32 | import javafx.scene.text.Text; 33 | import javafx.scene.text.TextAlignment; 34 | import javafx.scene.text.TextFlow; 35 | import javafx.stage.Stage; 36 | import pattypan.Session; 37 | import pattypan.Settings; 38 | import pattypan.Util; 39 | import pattypan.elements.WikiButton; 40 | import pattypan.elements.WikiLabel; 41 | import pattypan.elements.WikiPane; 42 | import pattypan.elements.WikiPasswordField; 43 | import pattypan.elements.WikiTextField; 44 | 45 | public class LoginPane extends WikiPane { 46 | 47 | Stage stage; 48 | 49 | Hyperlink link = new Hyperlink(Util.text("login-2fa")); 50 | WikiTextField loginText = new WikiTextField("").setPlaceholder("login-login-field").setWidth(300); 51 | WikiPasswordField passwordText = new WikiPasswordField().setPlaceholder("login-password-field").setWidth(300); 52 | WikiButton loginButton = new WikiButton("login-login-button").setWidth(300); 53 | WikiLabel loginStatus = new WikiLabel(""); 54 | 55 | public LoginPane(Stage stage) { 56 | super(stage, 1.67); 57 | this.stage = stage; 58 | 59 | setContent(); 60 | setActions(); 61 | } 62 | 63 | /* 64 | * set content and actions 65 | ***************************************************************************** 66 | */ 67 | public WikiPane getContent() { 68 | return this; 69 | } 70 | 71 | private void setActions() { 72 | link.setOnAction(event -> { 73 | Util.openUrl("https://commons.wikimedia.org/wiki/Commons:Pattypan/Two-factor_authentication"); 74 | }); 75 | 76 | passwordText.setOnKeyPressed((KeyEvent event) -> { 77 | if (event.getCode().equals(KeyCode.ENTER)) { 78 | logIn(); 79 | } 80 | }); 81 | 82 | loginButton.setOnAction((ActionEvent event) -> { 83 | logIn(); 84 | }); 85 | 86 | prevButton.linkTo("CheckPane", stage); 87 | nextButton.linkTo("UploadPane", stage).setDisable(true); 88 | } 89 | 90 | private void setContent() { 91 | TextFlow flow = new TextFlow(new Text(Util.text("login-intro")), link); 92 | flow.setTextAlignment(TextAlignment.CENTER); 93 | addElement(flow); 94 | 95 | addElement(loginText); 96 | addElement(passwordText); 97 | addElement(loginButton); 98 | addElement(loginStatus); 99 | 100 | if (!Settings.getSetting("user").isEmpty()) { 101 | loginText.setText(Settings.getSetting("user")); 102 | Platform.runLater(() -> { 103 | passwordText.requestFocus(); 104 | }); 105 | } 106 | } 107 | 108 | /* 109 | * methods 110 | ***************************************************************************** 111 | */ 112 | private void logIn() { 113 | Task task = new Task() { 114 | 115 | private void setLoginSuccess() { 116 | setDisableForm(false); 117 | loginButton.setText(Util.text("login-login-button")); 118 | loginStatus.setText(""); 119 | Settings.setSetting("user", loginText.getText()); 120 | nextButton.setDisable(false); 121 | nextButton.fire(); 122 | } 123 | 124 | private void setLoginFailed() { 125 | setDisableForm(false); 126 | loginButton.setText(Util.text("login-login-button")); 127 | loginStatus.setText(Util.text("login-failed")); 128 | } 129 | 130 | @Override 131 | protected Integer call() throws Exception { 132 | Session.WIKI.login(loginText.getText(), passwordText.getText()); 133 | return 1; 134 | } 135 | 136 | @Override 137 | protected void succeeded() { 138 | super.succeeded(); 139 | setLoginSuccess(); 140 | } 141 | 142 | @Override 143 | protected void cancelled() { 144 | super.cancelled(); 145 | } 146 | 147 | @Override 148 | protected void failed() { 149 | super.failed(); 150 | setLoginFailed(); 151 | } 152 | }; 153 | 154 | setDisableForm(true); 155 | loginButton.setText(Util.text("login-load")); 156 | new Thread(task).start(); 157 | } 158 | 159 | private void setDisableForm(boolean state) { 160 | loginText.setDisable(state); 161 | passwordText.setDisable(state); 162 | loginButton.setDisable(state); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/pattypan/panes/ChooseDirectoryPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import java.io.File; 27 | import java.util.ArrayList; 28 | import java.util.Arrays; 29 | import java.util.Map; 30 | import javafx.event.ActionEvent; 31 | import javafx.scene.Node; 32 | import javafx.scene.control.CheckBox; 33 | import javafx.scene.layout.BorderPane; 34 | import javafx.scene.layout.Priority; 35 | import javafx.scene.layout.VBox; 36 | import javafx.stage.DirectoryChooser; 37 | import javafx.stage.Stage; 38 | import pattypan.Session; 39 | import pattypan.Settings; 40 | import pattypan.Util; 41 | import pattypan.elements.WikiButton; 42 | import pattypan.elements.WikiLabel; 43 | import pattypan.elements.WikiPane; 44 | import pattypan.elements.WikiTextField; 45 | 46 | public class ChooseDirectoryPane extends WikiPane { 47 | 48 | Stage stage; 49 | 50 | WikiLabel descLabel; 51 | WikiTextField browsePath = new WikiTextField(""); 52 | WikiButton browseButton = new WikiButton("generic-browse", "small").setWidth(100); 53 | CheckBox includeSubdirectoriesCheckbox = new CheckBox(Util.text("choose-directory-include-subdirectories")); 54 | VBox checkBoxContainer = new VBox(); 55 | 56 | public ChooseDirectoryPane(Stage stage) { 57 | super(stage, 0.0); 58 | this.stage = stage; 59 | 60 | setContent(); 61 | setActions(); 62 | } 63 | 64 | /* 65 | * set content and actions 66 | ***************************************************************************** 67 | */ 68 | public BorderPane getContent() { 69 | return this; 70 | } 71 | 72 | private void setActions() { 73 | browseButton.setOnAction((ActionEvent e) -> { 74 | chooseAndSetDirectory(); 75 | }); 76 | includeSubdirectoriesCheckbox.setOnAction((ActionEvent e) -> { 77 | if (Session.DIRECTORY != null) { 78 | getFileListByDirectory(Session.DIRECTORY); 79 | } 80 | }); 81 | 82 | prevButton.linkTo("StartPane", stage); 83 | nextButton.linkTo("ChooseColumnsPane", stage); 84 | } 85 | 86 | private void setContent() { 87 | addElement("choose-directory-intro", 40); 88 | addElementRow( 89 | new Node[]{browseButton, browsePath}, 90 | new Priority[]{Priority.NEVER, Priority.ALWAYS} 91 | ); 92 | addElement(new VBox(includeSubdirectoriesCheckbox)); 93 | addElement(checkBoxContainer); 94 | 95 | browsePath.setDisable(true); 96 | nextButton.setDisable(true); 97 | } 98 | 99 | /* 100 | * methods 101 | ***************************************************************************** 102 | */ 103 | /** 104 | * Opens directory chooser 105 | */ 106 | private void chooseAndSetDirectory() { 107 | DirectoryChooser fileChooser = new DirectoryChooser(); 108 | fileChooser.setTitle(Util.text("choose-directory-window-name")); 109 | 110 | Session.DIRECTORY = !Settings.getSetting("path").isEmpty() 111 | ? new File(Settings.getSetting("path")) 112 | : null; 113 | fileChooser.setInitialDirectory(Session.DIRECTORY); 114 | 115 | File file; 116 | try { 117 | file = fileChooser.showDialog(stage); 118 | } catch (IllegalArgumentException ex) { 119 | fileChooser.setInitialDirectory(null); 120 | file = fileChooser.showDialog(stage); 121 | } 122 | 123 | if (file != null) { 124 | Session.DIRECTORY = file; 125 | browsePath.setText(file.getAbsolutePath()); 126 | Settings.setSetting("path", Session.DIRECTORY.getAbsolutePath()); 127 | getFileListByDirectory(file); 128 | } 129 | } 130 | 131 | /** 132 | * Gets list of files from directory 133 | * 134 | * @param directory 135 | */ 136 | private void getFileListByDirectory(File directory) { 137 | checkBoxContainer.getChildren().clear(); 138 | checkBoxContainer.getChildren().add(new WikiLabel("generic-summary").setClass("header")); 139 | 140 | File[] files = Util.getFilesAllowedToUpload(directory, includeSubdirectoriesCheckbox.isSelected()); 141 | Session.FILES = new ArrayList<>(Arrays.asList(files)); 142 | 143 | Map filesByExt = Util.getFilesByExtention(files); 144 | filesByExt.entrySet().stream() 145 | .map((e) -> String.format(".%s (%s files)", e.getKey(), e.getValue())) 146 | .forEach((text) -> { 147 | CheckBox checkbox = new CheckBox(text); 148 | checkbox.setSelected(true); 149 | checkbox.setDisable(true); 150 | checkBoxContainer.getChildren().add(new WikiLabel(text)); 151 | }); 152 | 153 | if (files.length == 0) { 154 | checkBoxContainer.getChildren().add(new WikiLabel("choose-directory-no-files")); 155 | } 156 | 157 | nextButton.setDisable(files.length == 0); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/pattypan/panes/CheckPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import java.io.File; 27 | import java.io.UnsupportedEncodingException; 28 | import java.net.URLEncoder; 29 | import java.util.logging.Level; 30 | import javafx.scene.Node; 31 | import javafx.scene.control.Hyperlink; 32 | import javafx.scene.control.Tooltip; 33 | import javafx.scene.image.Image; 34 | import javafx.scene.image.ImageView; 35 | import javafx.scene.layout.Priority; 36 | import javafx.scene.layout.VBox; 37 | import javafx.stage.Stage; 38 | import pattypan.Session; 39 | import pattypan.UploadElement; 40 | import pattypan.Util; 41 | import pattypan.elements.WikiLabel; 42 | import pattypan.elements.WikiPane; 43 | import pattypan.elements.WikiScrollPane; 44 | 45 | public class CheckPane extends WikiPane { 46 | 47 | Stage stage; 48 | VBox fileListContainer = new VBox(4); 49 | VBox detailsContainer = new VBox(4); 50 | Hyperlink prevLabel = new Hyperlink(); 51 | 52 | public CheckPane(Stage stage) { 53 | super(stage, 1.34); 54 | this.stage = stage; 55 | 56 | setContent(); 57 | } 58 | 59 | /* 60 | * set content and actions 61 | ***************************************************************************** 62 | */ 63 | public WikiPane getContent() { 64 | return this; 65 | } 66 | 67 | private void setContent() { 68 | addElement("check-intro", 40); 69 | 70 | Session.FILES_TO_UPLOAD.stream().map(uploadElement -> { 71 | String name = Util.getNormalizedName(uploadElement.getData("name")); 72 | Hyperlink label = new Hyperlink(name); 73 | 74 | label.setTooltip(new Tooltip(name)); 75 | label.setOnAction(event -> { 76 | setDetails(uploadElement, label); 77 | }); 78 | return label; 79 | }).forEach(label -> { 80 | fileListContainer.getChildren().add(label); 81 | }); 82 | 83 | addElementRow(10, 84 | new Node[]{ 85 | new WikiScrollPane(fileListContainer).setWidth(250), 86 | new WikiScrollPane(detailsContainer) 87 | }, 88 | new Priority[]{Priority.SOMETIMES, Priority.SOMETIMES} 89 | ); 90 | 91 | setDetails( 92 | Session.FILES_TO_UPLOAD.get(0), 93 | (Hyperlink) fileListContainer.getChildren().get(0) 94 | ); 95 | 96 | prevButton.linkTo("LoadPane", stage); 97 | nextButton.linkTo("LoginPane", stage); 98 | } 99 | 100 | /* 101 | * methods 102 | ***************************************************************************** 103 | */ 104 | /** 105 | * Gets thumbnail of file 106 | * 107 | * @param file 108 | * @return 109 | */ 110 | private ImageView getScaledThumbnail(File file) { 111 | Image img = new Image(file.toURI().toString()); 112 | ImageView view = new ImageView(img); 113 | 114 | int originalWidth = (int) img.getWidth(); 115 | int originalHeight = (int) img.getHeight(); 116 | int boundWidth = 400; 117 | int boundHeight = 400; 118 | int width = originalWidth; 119 | int height = originalHeight; 120 | 121 | if (originalWidth > boundWidth) { 122 | width = boundWidth; 123 | height = (width * originalHeight) / originalWidth; 124 | } 125 | if (height > boundHeight) { 126 | height = boundHeight; 127 | width = (height * originalWidth) / originalHeight; 128 | } 129 | 130 | view.setFitHeight(height); 131 | view.setFitWidth(width); 132 | return view; 133 | } 134 | 135 | /** 136 | * Shows details of selected file 137 | * 138 | * @param ue 139 | */ 140 | private void setDetails(UploadElement ue, Hyperlink label) { 141 | String name = Util.getNormalizedName(ue.getData("name")); 142 | 143 | WikiLabel title = new WikiLabel(name).setClass("header").setAlign("left"); 144 | WikiLabel path = new WikiLabel(ue.getData("path")).setAlign("left"); 145 | Hyperlink pathURL = new Hyperlink(ue.getData("path")); 146 | Hyperlink preview = new Hyperlink(Util.text("check-preview")); 147 | WikiLabel wikitext = new WikiLabel(ue.getWikicode()).setClass("monospace").setAlign("left"); 148 | 149 | prevLabel.getStyleClass().remove("bold"); 150 | prevLabel = label; 151 | prevLabel.getStyleClass().add("bold"); 152 | 153 | preview.setOnAction(event -> { 154 | try { 155 | Util.openUrl(Session.WIKI.getProtocol() + Session.WIKI.getDomain() 156 | + "/wiki/Special:ExpandTemplates" 157 | + "?wpRemoveComments=true" 158 | + "&wpInput=" + URLEncoder.encode(ue.getWikicode(), "UTF-8") 159 | + "&wpContextTitle=" + URLEncoder.encode(name, "UTF-8")); 160 | } catch (UnsupportedEncodingException ex) { 161 | Session.LOGGER.log(Level.SEVERE, null, ex); 162 | } 163 | }); 164 | 165 | pathURL.setOnAction(event -> { 166 | Util.openUrl(ue.getData("path")); 167 | }); 168 | 169 | detailsContainer.getChildren().clear(); 170 | 171 | if (ue.getData("path").startsWith("https://") || ue.getData("path").startsWith("http://")) { 172 | detailsContainer.getChildren().addAll(title, pathURL, preview, wikitext); 173 | } else { 174 | detailsContainer.getChildren().addAll(title, path, getScaledThumbnail(ue.getFile()), preview, wikitext); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/pattypan/style/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright 2016 Pawel Marynowski. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | .root { 26 | -fx-accent: #bbb; 27 | -fx-focus-color: #347bff; 28 | -fx-font-size: 12px; 29 | -fx-font-family: "Helvetica"; 30 | } 31 | 32 | .background { 33 | -fx-background-color: #fff; 34 | -fx-padding: 50px; 35 | /* -fx-grid-lines-visible: true;*/ 36 | } 37 | 38 | .scroll-pane { 39 | -fx-background-color: #fff; 40 | -fx-border-width: 0; 41 | -fx-border-insets: 0; 42 | } 43 | 44 | .scroll-pane > .viewport { 45 | -fx-background-color: #fff; 46 | } 47 | 48 | .title { 49 | -fx-font-size: 25px; 50 | -fx-font-weight: bold; 51 | } 52 | 53 | .header, .mw-ui-header { 54 | -fx-font-size: 16px; 55 | -fx-font-weight: bold; 56 | } 57 | 58 | .muted { 59 | -fx-text-fill: #aaa; 60 | } 61 | 62 | .bold { 63 | -fx-font-weight: bold; 64 | } 65 | 66 | .monospace { 67 | -fx-font-family: "monospace"; 68 | } 69 | 70 | .message { 71 | -fx-padding: .5em 1em .5em 1em; 72 | -fx-border-width: 2px; 73 | -fx-border-color: #F9C557; 74 | -fx-border-insets: 0; 75 | -fx-border-radius: 2px; 76 | } 77 | 78 | .hyperlink { 79 | -fx-text-fill: #347bff; 80 | } 81 | 82 | .hyperlink:visited { 83 | -fx-underline: false; 84 | } 85 | 86 | /* INPUT 87 | ******************************************************************************/ 88 | 89 | .mw-ui-input { 90 | -fx-padding: .4em .3em .4em .6em; 91 | -fx-border-width: 2px; 92 | -fx-border-color: #ccc; 93 | -fx-background-color: #fff; 94 | -fx-border-insets: 0; 95 | -fx-border-radius: 2px; 96 | -fx-font-size: 13px; 97 | -fx-font-family: "Helvetica"; 98 | } 99 | 100 | .mw-ui-input:focused { 101 | -fx-border-color: #347bff; 102 | } 103 | 104 | .mw-ui-input:disabled { 105 | -fx-opacity: 1.0; 106 | } 107 | 108 | .mw-ui-input .scroll-pane, .mw-ui-input .content { 109 | -fx-padding: 0; 110 | -fx-border-width: 0; 111 | -fx-background-insets: 0; 112 | } 113 | 114 | /* BUTTONS 115 | ******************************************************************************/ 116 | 117 | .mw-ui-button { 118 | -fx-font-family: "Helvetica"; 119 | -fx-font-size: 16px; 120 | -fx-font-weight: bold; 121 | -fx-text-fill: #555; 122 | -fx-background-color: #ddd; 123 | -fx-padding: .5em 1em .5em 1em; 124 | -fx-border-width: 1px; 125 | -fx-border-color: #ddd; 126 | -fx-border-insets: 0; 127 | -fx-border-radius: 2px; 128 | -fx-text-alignment: center; 129 | -fx-cursor: hand; 130 | } 131 | 132 | .mw-ui-button:hover { 133 | -fx-opacity: 0.7; 134 | } 135 | 136 | .mw-ui-button.mw-ui-progressive, 137 | .mw-ui-button.mw-ui-primary { 138 | -fx-background-color: #347bff; 139 | -fx-text-fill: #fff; 140 | -fx-border-width: 1px; 141 | -fx-border-color: #347bff; 142 | } 143 | 144 | .mw-ui-button.mw-ui-inversed { 145 | -fx-border-width: 1px; 146 | -fx-border-color: #ddd; 147 | -fx-background-color: #fff; 148 | } 149 | 150 | .mw-ui-button.mw-ui-inversed:hover { 151 | -fx-background-color: #ddd; 152 | -fx-border-color: #ddd; 153 | } 154 | 155 | .mw-ui-button.mw-ui-small { 156 | -fx-font-size: 13px; 157 | -fx-font-weight: normal; 158 | -fx-padding: .3em 1em .3em 1em; 159 | } 160 | 161 | .mw-ui-button.mw-ui-noborder { 162 | -fx-padding: 0; 163 | -fx-border-width: 0; 164 | -fx-background-color: #fff; 165 | } 166 | 167 | .mw-ui-button.mw-ui-group-right { 168 | -fx-border-radius: 0 2px 2px 0; 169 | } 170 | 171 | .mw-ui-button.mw-ui-group-left { 172 | -fx-border-radius: 2px 0 0 2px; 173 | } 174 | 175 | /* PROGRESS BAR 176 | ******************************************************************************/ 177 | 178 | .mw-ui-progressbar-container { 179 | -fx-padding: 0 0 40px 0; 180 | } 181 | 182 | .mw-ui-progressbar .track { 183 | -fx-box-border: 0; 184 | -fx-background-color: #dddddd; 185 | -fx-background-insets: 0; 186 | } 187 | 188 | .mw-ui-progressbar .bar { 189 | -fx-background-color: #000000; 190 | -fx-background-radius: 1 0 0 1; 191 | -fx-background-insets: 0; 192 | -fx-padding: 2px; 193 | } 194 | 195 | .mw-ui-progressbar-dot { 196 | -fx-fill: #ddd; 197 | } 198 | 199 | .mw-ui-progressbar-dot-active { 200 | -fx-fill: #000; 201 | } 202 | 203 | .mw-ui-progressbar-text { 204 | -fx-text-fill: #bbb; 205 | } 206 | 207 | .mw-ui-progressbar-text-active { 208 | -fx-text-fill: #000; 209 | } 210 | 211 | /* TABS 212 | ******************************************************************************/ 213 | 214 | .mw-ui-tabs { 215 | -fx-border-width: 0; 216 | -fx-background-insets: 0; 217 | } 218 | 219 | .mw-ui-tabs .tab-header-area, 220 | .mw-ui-tabs .headers-region, 221 | .mw-ui-tabs .tab-header-background { 222 | -fx-background-color: transparent; 223 | } 224 | 225 | .mw-ui-tabs .tab { 226 | /* -fx-padding: .3em .3em .3em .6em;*/ 227 | -fx-border-width: 2px 2px 0 2px; 228 | -fx-border-color: #ccc; 229 | -fx-border-radius: 2px 2px 0 0; 230 | -fx-background-insets: 0; 231 | -fx-background-color: transparent; 232 | } 233 | 234 | .mw-ui-tabs .tab-label { 235 | -fx-border-width: 0; 236 | -fx-background-insets: 0; 237 | } 238 | 239 | .mw-ui-tabs .tab-content-area { 240 | -fx-padding: .3em .3em .3em .6em; 241 | -fx-border-width: 2px; 242 | -fx-border-color: #ccc; 243 | } 244 | 245 | /* 246 | -fx-padding: .3em .3em .3em .6em; 247 | -fx-border-width: 2px; 248 | -fx-border-color: #ccc; 249 | -fx-background-color: #fff; 250 | -fx-border-insets: 0; 251 | 252 | -fx-font-size: 13px; 253 | -fx-font-family: "Helvetica"; 254 | */ -------------------------------------------------------------------------------- /src/pattypan/panes/StartPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import java.net.UnknownHostException; 27 | import java.nio.file.Paths; 28 | import java.text.SimpleDateFormat; 29 | import java.util.ArrayList; 30 | import java.util.Date; 31 | import java.util.logging.Level; 32 | 33 | import com.google.gson.JsonArray; 34 | import com.google.gson.JsonElement; 35 | import com.google.gson.JsonObject; 36 | import com.google.gson.JsonParser; 37 | 38 | import javafx.geometry.Pos; 39 | import javafx.scene.control.Hyperlink; 40 | import javafx.scene.layout.GridPane; 41 | import javafx.scene.layout.HBox; 42 | import javafx.scene.text.Text; 43 | import javafx.scene.text.TextAlignment; 44 | import javafx.scene.text.TextFlow; 45 | import javafx.stage.Stage; 46 | import pattypan.Session; 47 | import pattypan.Settings; 48 | import pattypan.Util; 49 | import pattypan.elements.WikiButton; 50 | import pattypan.elements.WikiLabel; 51 | 52 | public class StartPane extends GridPane { 53 | 54 | String css = getClass().getResource("/pattypan/style/style.css").toExternalForm(); 55 | Stage stage; 56 | 57 | Hyperlink bugLink = new Hyperlink(Util.text("start-bug-report")); 58 | Hyperlink downloadLink = new Hyperlink(Util.text("start-new-version-available-download")); 59 | Hyperlink logFile = new Hyperlink(Util.text("log-file")); 60 | 61 | public StartPane(Stage stage) { 62 | this.stage = stage; 63 | 64 | setContent(); 65 | setActions(); 66 | checkVersion(); 67 | } 68 | 69 | /* 70 | * set content and actions 71 | ***************************************************************************** 72 | */ 73 | public GridPane getContent() { 74 | return this; 75 | } 76 | 77 | private void setActions() { 78 | bugLink.setOnAction(event -> { 79 | Util.openUrl("https://github.com/yarl/pattypan/issues/new"); 80 | }); 81 | 82 | downloadLink.setOnAction(event -> { 83 | Util.openUrl("https://github.com/yarl/pattypan/releases"); 84 | }); 85 | 86 | logFile.setOnAction(event -> { 87 | Util.openDirectory(Paths.get(Util.getApplicationDirectory() + "/logs")); 88 | }); 89 | } 90 | 91 | private GridPane setContent() { 92 | this.getStylesheets().add(css); 93 | this.setAlignment(Pos.CENTER); 94 | this.setHgap(20); 95 | this.setVgap(5); 96 | this.getStyleClass().add("background"); 97 | 98 | this.getColumnConstraints().add(Util.newColumn(400, "px")); 99 | 100 | this.addRow(0, new WikiLabel("pattypan").setClass("title")); 101 | this.addRow(1, new WikiLabel("v. " + Settings.VERSION)); 102 | if (!Session.WIKI.getDomain().equals("commons.wikimedia.org")) { 103 | this.addRow(3, new WikiLabel(Session.WIKI.getDomain())); 104 | } 105 | 106 | this.addRow(20, new HBox(20, 107 | new WikiButton("start-generate-button", "primary") 108 | .setWidth(300) 109 | .linkTo("ChooseDirectoryPane", stage), 110 | new WikiButton("start-validate-button") 111 | .setWidth(300) 112 | .linkTo("LoadPane", stage))); 113 | 114 | this.addRow(22, new HBox(20, 115 | new WikiLabel("start-generate-description").setWidth(300), 116 | new WikiLabel("start-validate-description").setWidth(300))); 117 | 118 | String year = new SimpleDateFormat("yyyy").format(new Date()); 119 | this.addRow(40, new WikiLabel(year + " // Pawel Marynowski").setClass("muted")); 120 | 121 | TextFlow flow = new TextFlow( 122 | new Text(Util.text("start-bug-found")), bugLink, 123 | new Text(" • "), logFile); 124 | flow.setTextAlignment(TextAlignment.CENTER); 125 | 126 | this.addRow(41, flow); 127 | return this; 128 | } 129 | 130 | /* 131 | * methods 132 | ***************************************************************************** 133 | */ 134 | /** 135 | * Checks if this Pattypan version is the lastest one. If no, show alert. 136 | */ 137 | private void checkVersion() { 138 | try { 139 | ArrayList versions = getVersions(); 140 | for (String version : versions) { 141 | if (Util.versionCompare(version, Settings.VERSION) > 0) { 142 | this.addRow(10, showUpdateAlert()); 143 | break; 144 | } 145 | } 146 | } catch (UnknownHostException ex) { 147 | Session.LOGGER.log(Level.INFO, "No internet connection found"); 148 | } catch (Exception ex) { 149 | Session.LOGGER.log(Level.SEVERE, null, ex); 150 | } 151 | } 152 | 153 | /** 154 | * Gets list of stable versions from Github repo 155 | * 156 | * @return list of stable releases from Github 157 | * @throws Exception 158 | */ 159 | public ArrayList getVersions() throws Exception { 160 | ArrayList versions = new ArrayList<>(); 161 | String json = Util.readUrl("https://api.github.com/repos/yarl/pattypan/releases"); 162 | 163 | JsonArray releases = JsonParser.parseString(json).getAsJsonArray(); 164 | for (JsonElement element : releases) { 165 | JsonObject release = element.getAsJsonObject(); 166 | boolean draft = release.get("draft").getAsBoolean(); 167 | boolean pre = release.get("prerelease").getAsBoolean(); 168 | String tag = release.get("tag_name").getAsString(); 169 | 170 | if (!draft && !pre) { 171 | versions.add(tag.contains("v") ? tag.substring(1) : tag); 172 | } 173 | } 174 | return versions; 175 | } 176 | 177 | private TextFlow showUpdateAlert() { 178 | TextFlow flow = new TextFlow(new Text(Util.text("start-new-version-available")), downloadLink); 179 | flow.getStyleClass().add("message"); 180 | flow.setTextAlignment(TextAlignment.CENTER); 181 | return flow; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/pattypan/panes/CreateFilePane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import com.drew.imaging.ImageMetadataReader; 27 | import com.drew.imaging.ImageProcessingException; 28 | import com.drew.metadata.Directory; 29 | import com.drew.metadata.Metadata; 30 | import com.drew.metadata.exif.ExifSubIFDDirectory; 31 | import java.awt.Desktop; 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.text.SimpleDateFormat; 35 | import java.util.Date; 36 | import java.util.TimeZone; 37 | import java.util.logging.Level; 38 | import javafx.scene.control.Hyperlink; 39 | import javafx.scene.layout.Region; 40 | import javafx.scene.text.TextAlignment; 41 | import javafx.scene.text.TextFlow; 42 | import javafx.stage.Stage; 43 | import jxl.CellView; 44 | import jxl.Workbook; 45 | import jxl.read.biff.BiffException; 46 | import jxl.write.Label; 47 | import jxl.write.WritableSheet; 48 | import jxl.write.WritableWorkbook; 49 | import jxl.write.WriteException; 50 | import pattypan.Session; 51 | import pattypan.Settings; 52 | import pattypan.Template; 53 | import pattypan.TemplateField; 54 | import pattypan.Util; 55 | import pattypan.elements.WikiButton; 56 | import pattypan.elements.WikiLabel; 57 | import pattypan.elements.WikiPane; 58 | import pattypan.elements.WikiTextField; 59 | 60 | public class CreateFilePane extends WikiPane { 61 | 62 | Stage stage; 63 | 64 | WikiLabel descLabel; 65 | WikiTextField fileName = new WikiTextField("").setPlaceholder("create-file-filename").setWidth(300); 66 | WikiButton createButton = new WikiButton("create-file-button", "primary").setWidth(300); 67 | 68 | public CreateFilePane(Stage stage) { 69 | super(stage, 1.0); 70 | this.stage = stage; 71 | 72 | setContent(); 73 | setActions(); 74 | } 75 | 76 | public WikiPane getContent() { 77 | return this; 78 | } 79 | 80 | private WikiPane setContent() { 81 | addElement("generic-summary", "header"); 82 | addElement(Util.text("create-file-summary", Session.FILES.size(), Session.DIRECTORY.getName()), 40); 83 | addElement(fileName); 84 | addElement(new Region()); 85 | addElement(createButton); 86 | 87 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH_mm_ss"); 88 | fileName.setText("pattypan " + sdf.format(new Date())); 89 | 90 | prevButton.linkTo("ChooseColumnsPane", stage); 91 | nextButton.setVisible(false); 92 | 93 | return this; 94 | } 95 | 96 | private WikiPane setActions() { 97 | fileName.textProperty().addListener((observable, oldValue, newValue) -> { 98 | createButton.setDisable(newValue.isEmpty()); 99 | }); 100 | 101 | createButton.setOnAction(event -> { 102 | try { 103 | createSpreadsheet(); 104 | showOpenFileButton(); 105 | Settings.saveProperties(); 106 | } catch (IOException | BiffException | WriteException ex) { 107 | addElement(new WikiLabel("create-file-error")); 108 | Session.LOGGER.log(Level.WARNING, 109 | "Error occurred during creation of spreadsheet file: {0}", 110 | new String[]{ex.getLocalizedMessage()} 111 | ); 112 | } 113 | }); 114 | 115 | return this; 116 | } 117 | 118 | private void showOpenFileButton() { 119 | Hyperlink link = new Hyperlink(Util.text("create-file-open")); 120 | TextFlow flow = new TextFlow(new WikiLabel("create-file-success"), link); 121 | flow.setTextAlignment(TextAlignment.CENTER); 122 | addElement(flow); 123 | link.setOnAction(ev -> { 124 | try { 125 | Desktop.getDesktop().open(Session.FILE); 126 | } catch (IOException ex) { 127 | Session.LOGGER.log(Level.WARNING, 128 | "Cannot open file: {0}", 129 | new String[]{ex.getLocalizedMessage()} 130 | ); 131 | } 132 | }); 133 | 134 | nextButton.linkTo("StartPane", stage, true).setText(Util.text("create-file-back-to-start")); 135 | nextButton.setVisible(true); 136 | } 137 | 138 | private void autoSizeColumn(int column, WritableSheet sheet) { 139 | CellView cell = sheet.getColumnView(column); 140 | cell.setAutosize(true); 141 | sheet.setColumnView(column, cell); 142 | } 143 | 144 | private void createSpreadsheet() throws IOException, BiffException, WriteException { 145 | File f = new File(Session.DIRECTORY, fileName.getText() + ".xls"); 146 | WritableWorkbook workbook = Workbook.createWorkbook(f); 147 | 148 | createDataSheet(workbook); 149 | createTemplateSheet(workbook); 150 | 151 | workbook.write(); 152 | workbook.close(); 153 | Session.FILE = f; 154 | } 155 | 156 | /** 157 | * 158 | * @param workbook 159 | * @throws WriteException 160 | */ 161 | private void createDataSheet(WritableWorkbook workbook) throws WriteException { 162 | WritableSheet sheet = workbook.createSheet("Data", 0); 163 | 164 | // first row (header) 165 | int column = 0; 166 | for (String variable : Session.VARIABLES) { 167 | sheet.addCell(new Label(column++, 0, variable)); 168 | } 169 | 170 | // next rows with path and name 171 | int row = 1; 172 | for (File file : Session.FILES) { 173 | sheet.addCell(new Label(0, row, file.getAbsolutePath())); 174 | sheet.addCell(new Label(1, row++, Util.getNameFromFilename(file.getName()))); 175 | } 176 | 177 | if (Session.METHOD.equals("template")) { 178 | Template template = Settings.TEMPLATES.get(Session.TEMPLATE); 179 | for (TemplateField tf : template.variables) { 180 | if (tf.isSelected && !tf.value.isEmpty()) { 181 | column = Session.VARIABLES.indexOf(tf.name); 182 | row = 1; 183 | for (File file : Session.FILES) { 184 | sheet.addCell(new Label(column, row++, tf.value)); 185 | } 186 | } 187 | } 188 | } 189 | 190 | column = Session.VARIABLES.indexOf("date"); 191 | if (column >= 0 && !Settings.getSetting("exifDate").isEmpty()) { 192 | row = 1; 193 | for (File file : Session.FILES) { 194 | sheet.addCell(new Label(column, row++, getExifDate(file))); 195 | } 196 | } 197 | 198 | for (int num = 0; num < sheet.getColumns(); num++) { 199 | autoSizeColumn(num, sheet); 200 | } 201 | } 202 | 203 | /** 204 | * 205 | * @param workbook 206 | * @throws WriteException 207 | */ 208 | private void createTemplateSheet(WritableWorkbook workbook) throws WriteException { 209 | WritableSheet templateSheet = workbook.createSheet("Template", 1); 210 | templateSheet.addCell(new Label(0, 0, "'" + Session.WIKICODE)); 211 | // ^^ 212 | // leading apostrophe prevents turning wikitext into formula in Excel 213 | 214 | autoSizeColumn(0, templateSheet); 215 | } 216 | 217 | /** 218 | * 219 | * @param filePath 220 | * @return 221 | */ 222 | private String getExifDate(File file) { 223 | 224 | try { 225 | Metadata metadata = ImageMetadataReader.readMetadata(file); 226 | Directory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); 227 | int dateTag = ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL; 228 | 229 | if (directory != null && directory.containsTag(dateTag)) { 230 | Date date = directory.getDate(dateTag, TimeZone.getDefault()); 231 | return new SimpleDateFormat("yyyy-MM-dd HH:mm").format(date); 232 | } else { 233 | return ""; 234 | } 235 | } catch (ImageProcessingException | IOException ex) { 236 | Session.LOGGER.log(Level.INFO, 237 | "Exif error for {0}: {1}", 238 | new String[]{file.getName(), ex.getLocalizedMessage()} 239 | ); 240 | return ""; 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/pattypan/panes/ChooseColumnsPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import javafx.event.Event; 27 | import javafx.scene.Node; 28 | import javafx.scene.control.ComboBox; 29 | import javafx.scene.control.Hyperlink; 30 | import javafx.scene.control.TextArea; 31 | import javafx.scene.layout.HBox; 32 | import javafx.scene.layout.Priority; 33 | import javafx.scene.layout.Region; 34 | import javafx.scene.layout.VBox; 35 | import javafx.stage.Stage; 36 | import pattypan.Session; 37 | import pattypan.Settings; 38 | import pattypan.Template; 39 | import pattypan.TemplateField; 40 | import pattypan.Util; 41 | import pattypan.elements.WikiLabel; 42 | import pattypan.elements.WikiPane; 43 | import pattypan.elements.WikiScrollPane; 44 | 45 | public class ChooseColumnsPane extends WikiPane { 46 | 47 | Stage stage; 48 | 49 | VBox templatePane = new VBox(10); 50 | VBox templateDescContainer = new VBox(4); 51 | VBox rightContainer = new VBox(4); 52 | Hyperlink wikicodeLink; 53 | 54 | Hyperlink prevLabel = null; 55 | 56 | VBox wikicodePane = new VBox(10); 57 | TextArea wikicodeText = new TextArea(""); 58 | 59 | public ChooseColumnsPane(Stage stage) { 60 | super(stage, 0.5); 61 | this.stage = stage; 62 | 63 | setContent(); 64 | setActions(); 65 | } 66 | 67 | /* 68 | * set content and actions 69 | ***************************************************************************** 70 | */ 71 | public WikiPane getContent() { 72 | return this; 73 | } 74 | 75 | private WikiPane setActions() { 76 | if (!Settings.getSetting("template").isEmpty()) { 77 | Session.TEMPLATE = Settings.getSetting("template"); 78 | } 79 | 80 | wikicodeLink.setOnAction(event -> { 81 | templateDescContainer.getChildren().clear(); 82 | templateDescContainer.getChildren().add(wikicodePane); 83 | Session.METHOD = "wikicode"; 84 | }); 85 | 86 | prevButton.linkTo("ChooseDirectoryPane", stage); 87 | nextButton.setOnAction(event -> { 88 | if (Session.METHOD.equals("wikicode")) { 89 | String text = wikicodeText.getText().trim(); 90 | Session.VARIABLES = Template.getComputedVariablesFromString(text); 91 | Session.WIKICODE = text; 92 | } 93 | 94 | if (Session.METHOD.equals("template")) { 95 | Template template = Settings.TEMPLATES.get(Session.TEMPLATE); 96 | Session.VARIABLES = template.getComputedVariables(); 97 | Session.WIKICODE = template.getComputedWikicode(); 98 | 99 | for (TemplateField tf : template.variables) { 100 | Settings.setSetting("var-" + tf.name + "-value", tf.value); 101 | Settings.setSetting("var-" + tf.name + "-selection", tf.selection); 102 | } 103 | } 104 | 105 | nextButton.goTo("CreateFilePane", stage, true); 106 | }); 107 | showTemplateFields(Session.TEMPLATE); 108 | return this; 109 | } 110 | 111 | private WikiPane setContent() { 112 | addElement("choose-columns-intro", 40); 113 | 114 | /* templates */ 115 | rightContainer.getChildren().add(new WikiLabel("choose-columns-template").setClass("bold")); 116 | Settings.TEMPLATES.forEach((key, value) -> { 117 | Hyperlink label = new Hyperlink(key); 118 | label.setOnAction(event -> { 119 | 120 | Template template = Settings.TEMPLATES.get(Session.TEMPLATE); 121 | for (TemplateField tf : template.variables) { 122 | Settings.setSetting("var-" + tf.name + "-value", tf.value); 123 | Settings.setSetting("var-" + tf.name + "-selection", tf.selection); 124 | } 125 | 126 | Session.METHOD = "template"; 127 | Session.TEMPLATE = key; 128 | Settings.setSetting("template", Session.TEMPLATE); 129 | 130 | if (prevLabel != null) { 131 | prevLabel.getStyleClass().remove("bold"); 132 | } 133 | prevLabel = label; 134 | prevLabel.getStyleClass().add("bold"); 135 | 136 | showTemplateFields(Session.TEMPLATE); 137 | }); 138 | rightContainer.getChildren().add(label); 139 | }); 140 | 141 | /* advanced */ 142 | wikicodeLink = new Hyperlink(Util.text("choose-columns-wikicode")); 143 | rightContainer.getChildren().addAll( 144 | new Region(), 145 | new WikiLabel("choose-columns-advanced").setClass("bold"), 146 | wikicodeLink 147 | ); 148 | 149 | addElementRow(templatePane, 10, 150 | new Node[]{ 151 | new WikiScrollPane(rightContainer).setWidth(150), 152 | new WikiScrollPane(templateDescContainer) 153 | }, 154 | new Priority[]{Priority.NEVER, Priority.ALWAYS} 155 | ); 156 | addElement(templatePane); 157 | 158 | /* wiki code pane */ 159 | ComboBox templateBox = new ComboBox(); 160 | templateBox.getItems().addAll(Settings.TEMPLATES.keySet().toArray()); 161 | templateBox.setOnAction((Event ev) -> { 162 | String templateName = templateBox 163 | .getSelectionModel() 164 | .getSelectedItem() 165 | .toString(); 166 | Template t = Settings.TEMPLATES.get(templateName); 167 | wikicodeText.setText(t.wikicode); 168 | }); 169 | 170 | wikicodeText.getStyleClass().add("mw-ui-input"); 171 | wikicodeText.setPrefHeight(this.stage.getHeight() - 350); 172 | wikicodeText.setText(Session.WIKICODE); 173 | wikicodePane.getChildren().addAll(templateBox, wikicodeText); 174 | 175 | this.stage.heightProperty().addListener((obs, oldVal, newVal) -> { 176 | wikicodeText.setPrefHeight(this.stage.getHeight() - 350); 177 | }); 178 | 179 | return this; 180 | } 181 | 182 | /* 183 | * methods 184 | ***************************************************************************** 185 | */ 186 | /** 187 | * Adds checkboxes with wikitemplate fields. 188 | * 189 | * @param templateName name of wikitemplate 190 | * @return true, if template exists 191 | */ 192 | private boolean showTemplateFields(String templateName) { 193 | Template template = Settings.TEMPLATES.get(templateName); 194 | 195 | Hyperlink docLink = new Hyperlink(Util.text("choose-columns-template-doc")); 196 | docLink.setMinHeight(25); 197 | docLink.setOnAction(event -> { 198 | Util.openUrl("https://commons.wikimedia.org/wiki/Template:" + template.name.replace(" ", "_") + "/doc"); 199 | }); 200 | 201 | templateDescContainer.getChildren().clear(); 202 | templateDescContainer.getChildren().add(new HBox(10, 203 | new WikiLabel("{{" + template.name + "}}") 204 | .setClass("header") 205 | .setAlign("left"), 206 | docLink 207 | )); 208 | templateDescContainer.getChildren().add( 209 | new WikiLabel("choose-columns-template-intro") 210 | .setAlign("left") 211 | .setHeight(70)); 212 | 213 | HBox headersContainer = new HBox(10); 214 | headersContainer.getChildren().addAll( 215 | new WikiLabel("choose-columns-fields-name") 216 | .setClass("bold") 217 | .setWidth(200, 495) 218 | .setHeight(35), 219 | new WikiLabel("choose-columns-radio-buttons") 220 | .setClass("bold") 221 | .setWidth(150) 222 | .setHeight(35), 223 | new WikiLabel("choose-columns-value") 224 | .setClass("bold") 225 | .setWidth(50, 500) 226 | .setHeight(35)); 227 | templateDescContainer.getChildren().add(headersContainer); 228 | 229 | for (TemplateField tf : template.variables) { 230 | templateDescContainer.getChildren().add(tf.getRow()); 231 | } 232 | return true; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/pattypan/panes/UploadPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import java.io.IOException; 27 | import java.net.UnknownHostException; 28 | import java.util.ArrayList; 29 | import java.util.Map; 30 | import java.util.List; 31 | import java.util.logging.Level; 32 | import java.util.regex.Matcher; 33 | import java.util.regex.Pattern; 34 | import javafx.beans.value.ChangeListener; 35 | import javafx.beans.value.ObservableValue; 36 | import javafx.concurrent.Task; 37 | import javafx.event.ActionEvent; 38 | import javafx.scene.Node; 39 | import javafx.scene.layout.Priority; 40 | import javafx.scene.layout.Region; 41 | import javafx.scene.layout.VBox; 42 | import javafx.stage.Stage; 43 | import javax.security.auth.login.LoginException; 44 | import org.wikipedia.Wiki; 45 | import pattypan.Session; 46 | import pattypan.Settings; 47 | import pattypan.UploadElement; 48 | import pattypan.Util; 49 | import pattypan.elements.WikiButton; 50 | import pattypan.elements.WikiLabel; 51 | import pattypan.elements.WikiPane; 52 | import pattypan.elements.WikiScrollPane; 53 | 54 | public class UploadPane extends WikiPane { 55 | 56 | Stage stage; 57 | 58 | volatile boolean stopRq = false; 59 | 60 | int current = 0; 61 | int uploaded = 0; 62 | int skipped = 0; 63 | ArrayList failedElements = new ArrayList<>(); 64 | 65 | WikiLabel fakeLoger = new WikiLabel(""); 66 | 67 | WikiButton uploadButton = new WikiButton("upload-button", "primary").setWidth(150); 68 | WikiButton stopButton = new WikiButton("upload-stop").setWidth(150); 69 | 70 | VBox infoContainer = new VBox(4); 71 | WikiScrollPane infoContainerScroll = new WikiScrollPane(infoContainer); 72 | WikiLabel statusLabel = new WikiLabel("").setClass("bold"); 73 | 74 | public UploadPane(Stage stage) { 75 | super(stage, 2.0); 76 | this.stage = stage; 77 | 78 | setContent(); 79 | setActions(); 80 | } 81 | 82 | /* 83 | * set content and actions 84 | ***************************************************************************** 85 | */ 86 | public WikiPane getContent() { 87 | return this; 88 | } 89 | 90 | private void resetInfo() { 91 | infoContainer.getChildren().clear(); 92 | current = 0; 93 | uploaded = 0; 94 | skipped = 0; 95 | } 96 | 97 | private void setActions() { 98 | uploadButton.setOnAction((ActionEvent e) -> { 99 | stopRq = false; 100 | uploadButton.setDisable(true); 101 | stopButton.setDisable(false); 102 | nextButton.setVisible(false); 103 | uploadFiles(Session.FILES_TO_UPLOAD); 104 | }); 105 | 106 | stopButton.setOnAction((ActionEvent e) -> { 107 | stopButton.setDisable(true); 108 | statusLabel.setText(Util.text("upload-log-canceled")); 109 | stopRq = true; 110 | }); 111 | 112 | fakeLoger.textProperty().addListener(new ChangeListener() { 113 | @Override 114 | public void changed(ObservableValue ov, String oldValue, String value) { 115 | String[] data = value.split(" \\| "); 116 | String max = String.valueOf(Session.FILES_TO_UPLOAD.size()); 117 | 118 | if (value.contains("UPLOAD_START")) { 119 | String text = String.format("[%s/%s] ", data[1], max) + Util.text("upload-log-uploading", data[2]); 120 | WikiLabel label = new WikiLabel(text).setAlign("left"); 121 | addInfo(label); 122 | statusLabel.setText(Util.text("upload-log-uploading", "") + String.format(" (%s/%s) ", data[1], max)); 123 | } else if (value.contains("UPLOAD_NAME_TAKEN")) { 124 | String text = String.format("[%s/%s] ", data[1], max) + Util.text("upload-log-error", Util.text("upload-log-error-name-taken")); 125 | WikiLabel label = new WikiLabel(text).setAlign("left"); 126 | addInfo(label); 127 | addInfo(new WikiLabel("")); 128 | } else if (value.contains("FILE_DUPLICATE")) { 129 | String text = String.format("[%s/%s] ", data[1], max) + Util.text("upload-log-error", Util.text("upload-log-error-file-duplicate", data[3])); 130 | WikiLabel label = new WikiLabel(text).setAlign("left"); 131 | addInfo(label); 132 | addInfo(new WikiLabel("")); 133 | } else if (value.contains("UPLOAD_SUCCESS")) { 134 | String text = String.format("[%s/%s] ", data[1], max) + Util.text("upload-log-success"); 135 | WikiLabel label = new WikiLabel(text).setAlign("left"); 136 | addInfo(label); 137 | addInfo(new WikiLabel("")); 138 | } else if (value.contains("UPLOAD_ERROR")) { 139 | String text = String.format("[%s/%s] ", data[1], max) + Util.text("upload-log-error", data[3]); 140 | WikiLabel label = new WikiLabel(text).setAlign("left").setClass("bold"); 141 | addInfo(label); 142 | addInfo(new WikiLabel("")); 143 | } else if (value.contains("UPLOAD_COMPLETED")) { 144 | int uploaded = Integer.parseInt(data[1]); 145 | int skipped = Integer.parseInt(data[2]); 146 | int total = Session.FILES_TO_UPLOAD.size(); 147 | 148 | uploadButton.setDisable(uploaded == total); 149 | if (uploaded + skipped == total && skipped > 0) { 150 | uploadButton.setText(Util.text("upload-retry")); 151 | uploadButton.setOnAction((ActionEvent e) -> { 152 | stopRq = false; 153 | uploadButton.setDisable(true); 154 | stopButton.setDisable(false); 155 | resetInfo(); 156 | uploadFiles(failedElements); 157 | }); 158 | } else if (uploaded + skipped < total) { 159 | uploadButton.setText(Util.text("upload-continue")); 160 | } else if (uploaded == total) { 161 | uploadButton.setText(Util.text("upload-button")); 162 | } 163 | 164 | stopButton.setDisable(true); 165 | String text = Util.text("upload-log-done", data[1], data[2]); 166 | WikiLabel label = new WikiLabel(text).setAlign("left").setClass("bold"); 167 | addInfo(label); 168 | addInfo(new WikiLabel("")); 169 | statusLabel.setText(text); 170 | 171 | nextButton.linkTo("StartPane", stage, true).setText(Util.text("upload-next-sheet")); 172 | nextButton.setVisible(true); 173 | } 174 | } 175 | }); 176 | 177 | prevButton.linkTo("LoginPane", stage); 178 | } 179 | 180 | private void setContent() { 181 | addElement("upload-intro", 40); 182 | 183 | stopButton.setDisable(true); 184 | 185 | addElementRow( 186 | new Node[]{new Region(), uploadButton, stopButton, new Region()}, 187 | new Priority[]{Priority.ALWAYS, Priority.NEVER, Priority.NEVER, Priority.ALWAYS} 188 | ); 189 | addElement(infoContainerScroll); 190 | addElement(statusLabel); 191 | 192 | nextButton.setVisible(false); 193 | } 194 | 195 | /* 196 | * methods 197 | ***************************************************************************** 198 | */ 199 | private void addInfo(WikiLabel label) { 200 | infoContainer.getChildren().add(label); 201 | infoContainerScroll.setVvalue(1.0); 202 | } 203 | 204 | /** 205 | * Retuns human-readable error from MediaWiki API 206 | * 207 | * @url https://www.mediawiki.org/wiki/API:Errors_and_warnings#Errors 208 | * @param error raw MediaWiki error 209 | * @return error string 210 | */ 211 | private String getMediaWikiError(Exception error) { 212 | final Pattern errorPattern = Pattern.compile("info=\"(.*?)\""); 213 | Matcher m = errorPattern.matcher(error.getLocalizedMessage()); 214 | while (m.find()) { 215 | return m.group(1); 216 | } 217 | return error.getLocalizedMessage(); 218 | } 219 | 220 | /** 221 | * Checks if file name is taken on Wikimedia Commons 222 | * 223 | * @param name file name (without File: prefix) 224 | * @return true if name is taken 225 | */ 226 | private boolean isFileNameTaken(String name) { 227 | try { 228 | Map map = Session.WIKI.getPageInfo(List.of("File:" + name)).get(0); 229 | return (boolean) map.get("exists"); 230 | } catch (UnknownHostException ex) { 231 | Session.LOGGER.log(Level.WARNING, 232 | "Error occurred during file name check: {0}", 233 | new String[]{"no internet connection"} 234 | ); 235 | return false; 236 | } catch (IOException ex) { 237 | Session.LOGGER.log(Level.WARNING, 238 | "Error occurred during file name check: {0}", 239 | new String[]{ex.getLocalizedMessage()} 240 | ); 241 | return false; 242 | } 243 | } 244 | 245 | private void uploadFiles(ArrayList fileList) { 246 | Task task = new Task() { 247 | @Override 248 | protected Object call() { 249 | final String summary = Settings.NAME + " " + Settings.VERSION; 250 | 251 | // for (UploadElement ue : Session.FILES_TO_UPLOAD) { 252 | for (; current < fileList.size(); current++) { 253 | UploadElement ue = fileList.get(current); 254 | String name = Util.getNormalizedName(ue.getData("name")); 255 | 256 | if (!stopRq) { 257 | updateMessage(String.format( 258 | "UPLOAD_START | %s | %s", 259 | current + 1, name 260 | )); 261 | try { 262 | if (isFileNameTaken(name)) { 263 | updateMessage(String.format( 264 | "UPLOAD_NAME_TAKEN | %s | %s", 265 | current + 1, name 266 | )); 267 | Thread.sleep(500); 268 | skipped++; 269 | continue; 270 | } 271 | 272 | if (ue.getData("path").startsWith("https://") || ue.getData("path").startsWith("http://")) { 273 | Session.WIKI.upload(ue.getUrl(), name, ue.getWikicode(), summary); 274 | } else { 275 | Session.WIKI.upload(ue.getFile(), name, ue.getWikicode(), summary); 276 | } 277 | 278 | Thread.sleep(500); 279 | updateMessage(String.format( 280 | "UPLOAD_SUCCESS | %s | %s", 281 | current + 1, name 282 | )); 283 | uploaded++; 284 | } catch (InterruptedException | IOException | LoginException ex) { 285 | updateMessage(String.format( 286 | "UPLOAD_ERROR | %s | %s | %s", 287 | current + 1, name, getMediaWikiError(ex) 288 | )); 289 | try { 290 | Thread.sleep(500); 291 | } catch (InterruptedException e) { 292 | } 293 | if (!failedElements.contains(ue)) { 294 | failedElements.add(ue); 295 | } 296 | skipped++; 297 | } 298 | } else { 299 | break; 300 | } 301 | } 302 | try { 303 | Thread.sleep(500); 304 | } catch (InterruptedException e) { 305 | } 306 | updateMessage(String.format( 307 | "UPLOAD_COMPLETED | %s | %s", 308 | uploaded, skipped 309 | )); 310 | return true; 311 | } 312 | }; 313 | fakeLoger.textProperty().bind(task.messageProperty()); 314 | new Thread(task).start(); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/pattypan/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import java.awt.Desktop; 27 | import java.awt.EventQueue; 28 | import java.io.File; 29 | import java.io.FileInputStream; 30 | import java.io.IOException; 31 | import java.net.URI; 32 | import java.net.URISyntaxException; 33 | import java.net.URL; 34 | import java.net.http.HttpClient; 35 | import java.net.http.HttpRequest; 36 | import java.net.http.HttpResponse.BodyHandlers; 37 | import java.nio.file.DirectoryStream; 38 | import java.nio.file.Files; 39 | import java.nio.file.Path; 40 | import java.security.MessageDigest; 41 | import java.util.ArrayList; 42 | import java.util.Arrays; 43 | import java.util.HashMap; 44 | import java.util.Map; 45 | import java.util.MissingResourceException; 46 | import java.util.Objects; 47 | import java.util.ResourceBundle; 48 | import java.util.Set; 49 | import java.util.logging.Level; 50 | import java.util.stream.Collectors; 51 | 52 | import edu.stanford.ejalbert.BrowserLauncher; 53 | import edu.stanford.ejalbert.exception.BrowserLaunchingInitializingException; 54 | import edu.stanford.ejalbert.exception.UnsupportedOperatingSystemException; 55 | import javafx.geometry.HPos; 56 | import javafx.scene.layout.ColumnConstraints; 57 | 58 | public final class Util { 59 | 60 | private Util() {} 61 | 62 | public static int WINDOW_WIDTH = 750; 63 | public static int WINDOW_HEIGHT = 550; 64 | static ResourceBundle bundle = ResourceBundle.getBundle("pattypan/text/messages"); 65 | 66 | public static String text(String key) { 67 | try { 68 | return bundle.getString(key); 69 | } catch (final MissingResourceException ex) { 70 | return ""; 71 | } 72 | } 73 | 74 | public static String text(String key, Object... vars) { 75 | String text = text(key); 76 | return String.format(text, vars); 77 | } 78 | 79 | /** 80 | * Open URL in browser 81 | * 82 | * @source http://stackoverflow.com/a/28807079 83 | * @param url website URL 84 | */ 85 | public static void openUrl(String url) { 86 | String os = System.getProperty("os.name").toLowerCase(); 87 | 88 | try { 89 | if (os.contains("win")) { 90 | Desktop.getDesktop().browse(new URI(url)); 91 | } else if (os.contains("mac")) { 92 | Runtime rt = Runtime.getRuntime(); 93 | rt.exec("open " + url); 94 | } else { 95 | new BrowserLauncher().openURLinBrowser(url); 96 | } 97 | } catch (BrowserLaunchingInitializingException | UnsupportedOperatingSystemException ex) { 98 | Session.LOGGER.log(Level.WARNING, null, ex); 99 | } catch (URISyntaxException | IOException ex) { 100 | Session.LOGGER.log(Level.WARNING, null, ex); 101 | } 102 | } 103 | 104 | public static void openDirectory(Path path) { 105 | EventQueue.invokeLater(() -> { 106 | try { 107 | Desktop.getDesktop().open(new File(path.toString())); 108 | } catch (IllegalArgumentException | IOException ex) { 109 | Session.LOGGER.log(Level.WARNING, null, ex); 110 | } 111 | }); 112 | } 113 | 114 | /* row and column utils */ 115 | public static ColumnConstraints newColumn(int value) { 116 | return newColumn(value, "%", HPos.CENTER); 117 | } 118 | 119 | public static ColumnConstraints newColumn(int value, String unit) { 120 | return newColumn(value, unit, HPos.CENTER); 121 | } 122 | 123 | public static ColumnConstraints newColumn(int value, String unit, HPos position) { 124 | ColumnConstraints col = new ColumnConstraints(); 125 | if (unit.equals("%")) { 126 | col.setPercentWidth(value); 127 | } 128 | if (unit.equals("px")) { 129 | col.setMaxWidth(value); 130 | col.setMinWidth(value); 131 | } 132 | 133 | if (position != null) { 134 | col.setHalignment(position); 135 | } 136 | return col; 137 | } 138 | 139 | /* file utils */ 140 | private final static ArrayList allowedFileExtension = new ArrayList<>( 141 | Arrays.asList("djvu", "flac", "gif", "jpg", "jpeg", "mid", 142 | "mkv", "oga", "ogg","ogv", "opus", "pdf", "png", "svg", "tiff", 143 | "tif", "wav", "webm", "webp", "xcf", "mp3", "stl") 144 | ); 145 | 146 | // https://commons.wikimedia.org/wiki/MediaWiki:Filename-prefix-blacklist 147 | private final static ArrayList filenamePrefixBlacklist = new ArrayList<>( 148 | Arrays.asList("CIMG", "DSC_", "DSCF", "DSCN", "DUW", "GEDC", 149 | "IMG", "JD", "MGP", "PICT", "Imagen", "FOTO", "DSC", 150 | "SANY", "SAM") 151 | ); 152 | 153 | // https://www.mediawiki.org/wiki/Manual:Page_title 154 | private final static ArrayList invalidFilenameCharacters = new ArrayList<>( 155 | Arrays.asList("#", "<", ">", "[", "]", "|", "{", "}") 156 | ); 157 | 158 | 159 | public static boolean hasValidFileExtension(String string) { 160 | return allowedFileExtension.parallelStream().anyMatch(string::endsWith); 161 | } 162 | 163 | public static boolean hasPossibleBadFilenamePrefix(String name) { 164 | return filenamePrefixBlacklist.parallelStream().anyMatch(name::startsWith); 165 | } 166 | 167 | public static boolean hasInvalidFilenameCharacters(String name) { 168 | return invalidFilenameCharacters.parallelStream().anyMatch(name::contains); 169 | } 170 | 171 | public static String getNameFromFilename(String filename) { 172 | int pos = filename.lastIndexOf("."); 173 | if (pos > 0) { 174 | filename = filename.substring(0, pos); 175 | } 176 | return filename; 177 | } 178 | 179 | public static String getExtFromFilename(String filename) { 180 | String extension = ""; 181 | 182 | int i = filename.lastIndexOf('.'); 183 | if (i >= 0) { 184 | extension = filename.substring(i + 1).toLowerCase(); 185 | } 186 | return extension; 187 | } 188 | 189 | public static File[] getFilesAllowedToUpload(File directory, boolean includeSubdirectories) { 190 | ArrayList files = new ArrayList<>(); 191 | try (DirectoryStream stream = Files.newDirectoryStream(directory.toPath())) { 192 | for (Path path : stream) { 193 | if (path.toFile().isDirectory() && includeSubdirectories) { 194 | File[] dirFiles = getFilesAllowedToUpload(path.toFile(), includeSubdirectories); 195 | ArrayList dirFilesList = new ArrayList<>(Arrays.asList(dirFiles)); 196 | files.addAll(dirFilesList); 197 | } else if (isFileAllowedToUpload(path.toFile().getName())) { 198 | files.add(path.toFile()); 199 | } 200 | } 201 | } catch (IOException e) { 202 | } 203 | return files.stream().toArray(File[]::new); 204 | } 205 | 206 | public static File[] getFilesAllowedToUpload(File directory, String ext) { 207 | File[] files = directory.listFiles((File dir, String name) -> name.toLowerCase().endsWith(ext)); 208 | Arrays.sort(files); 209 | return files; 210 | 211 | } 212 | 213 | public static Map getFilesByExtention(File[] files) { 214 | Map map = new HashMap<>(); 215 | 216 | for (File file : files) { 217 | String ext = getExtFromFilename(file.getName()); 218 | if (map.containsKey(ext)) { 219 | int count = map.get(ext); 220 | map.replace(ext, ++count); 221 | } else { 222 | map.put(ext, 1); 223 | } 224 | } 225 | return map; 226 | } 227 | 228 | // @TODO: remove fancy chars 229 | public static String getNormalizedName(String name) { 230 | return name.trim().replaceAll(" +", " "); 231 | } 232 | 233 | public static boolean isFileAllowedToUpload(String name) { 234 | return allowedFileExtension.indexOf(getExtFromFilename(name)) > -1; 235 | } 236 | 237 | // @source: https://howtodoinjava.com/java/io/how-to-generate-sha-or-md5-file-checksum-hash-in-java/ 238 | public static String getFileChecksum(MessageDigest digest, File file) throws IOException { 239 | //Get file input stream for reading the file content 240 | FileInputStream fis = new FileInputStream(file); 241 | 242 | //Create byte array to read data in chunks 243 | byte[] byteArray = new byte[1024]; 244 | int bytesCount = 0; 245 | 246 | //Read file data and update in message digest 247 | while ((bytesCount = fis.read(byteArray)) != -1) { 248 | digest.update(byteArray, 0, bytesCount); 249 | }; 250 | 251 | //close the stream; We don't need it now. 252 | fis.close(); 253 | 254 | //Get the hash's bytes 255 | byte[] bytes = digest.digest(); 256 | 257 | //This bytes[] has bytes in decimal format; 258 | //Convert it to hexadecimal format 259 | StringBuilder sb = new StringBuilder(); 260 | for(int i=0; i< bytes.length ;i++) 261 | { 262 | sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); 263 | } 264 | 265 | //return complete hash 266 | return sb.toString(); 267 | } 268 | 269 | public static String readUrl(String urlString) throws Exception { 270 | try { 271 | HttpClient client = HttpClient.newHttpClient(); 272 | 273 | HttpRequest request = HttpRequest.newBuilder( 274 | URI.create(urlString)) 275 | .header("user-agent", Settings.USERAGENT) 276 | .build(); 277 | 278 | var response = client.send(request, BodyHandlers.ofString()); 279 | return response.body(); 280 | } catch (Exception e) { 281 | throw new Exception("Error while getting JSON from " + urlString); 282 | } 283 | } 284 | 285 | public static boolean validUrl(String path) { 286 | try { 287 | URL url = new URL(path); 288 | url.toURI(); 289 | return true; 290 | } catch(Exception e) { 291 | return false; 292 | } 293 | } 294 | 295 | /** 296 | * Compares two version strings. 297 | * 298 | * Use this instead of String.compareTo() for a non-lexicographical comparison 299 | * that works for version strings. e.g. "1.10".compareTo("1.6"). 300 | * 301 | * @note It does not work if "1.10" is supposed to be equal to "1.10.0". 302 | * 303 | * @param str1 a string of ordinal numbers separated by decimal points. 304 | * @param str2 a string of ordinal numbers separated by decimal points. 305 | * @return The result is a negative integer if str1 is _numerically_ less than 306 | * str2. The result is a positive integer if str1 is _numerically_ greater 307 | * than str2. The result is zero if the strings are _numerically_ equal. 308 | * @source http://stackoverflow.com/a/6702029/1418878 309 | */ 310 | public static Integer versionCompare(String str1, String str2) { 311 | String[] vals1 = str1.split("\\."); 312 | String[] vals2 = str2.split("\\."); 313 | int i = 0; 314 | // set index to first non-equal ordinal or length of shortest version string 315 | while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { 316 | i++; 317 | } 318 | // compare first non-equal ordinal number 319 | if (i < vals1.length && i < vals2.length) { 320 | int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); 321 | return Integer.signum(diff); 322 | } // the strings are equal or one string is a substring of the other 323 | // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" 324 | else { 325 | return Integer.signum(vals1.length - vals2.length); 326 | } 327 | } 328 | 329 | /** 330 | * Returns keys by value 331 | * 332 | * @param map map with data 333 | * @param value searched value 334 | * @return list of keys that has searched value 335 | * @source http://stackoverflow.com/a/2904266/1418878 336 | */ 337 | public static Set getKeysByValue(Map map, E value) { 338 | return map.entrySet() 339 | .stream() 340 | .filter(entry -> Objects.equals(entry.getValue(), value)) 341 | .map(Map.Entry::getKey) 342 | .collect(Collectors.toSet()); 343 | } 344 | 345 | /** 346 | * Gets directory for local application files 347 | * 348 | * @source http://stackoverflow.com/a/16660314/1418878 349 | * @return path to local Pattypan directory 350 | */ 351 | public static String getApplicationDirectory() { 352 | String dir; 353 | String OS = (System.getProperty("os.name")).toUpperCase(); 354 | 355 | if (OS.contains("WIN")) { 356 | dir = System.getenv("AppData") + "/Pattypan"; 357 | } else if (OS.contains("NUX")) { 358 | dir = System.getProperty("user.home") + "/.pattypan"; 359 | } else { 360 | dir = System.getProperty("user.home"); 361 | dir += "/Library/Application Support/Pattypan"; 362 | } 363 | return dir; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/pattypan/panes/LoadPane.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan.panes; 25 | 26 | import freemarker.core.InvalidReferenceException; 27 | import freemarker.template.Configuration; 28 | import freemarker.template.Template; 29 | import freemarker.template.TemplateException; 30 | import freemarker.template.TemplateExceptionHandler; 31 | import java.io.File; 32 | import java.io.IOException; 33 | import java.io.StringReader; 34 | import java.io.StringWriter; 35 | import java.text.SimpleDateFormat; 36 | import java.util.ArrayList; 37 | import java.util.HashMap; 38 | import java.util.Map; 39 | import java.util.Set; 40 | import java.util.TimeZone; 41 | import javafx.scene.Node; 42 | import javafx.scene.layout.Priority; 43 | import javafx.scene.layout.VBox; 44 | import javafx.stage.FileChooser; 45 | import javafx.stage.Stage; 46 | import jxl.Cell; 47 | import jxl.CellType; 48 | import jxl.DateCell; 49 | import jxl.Sheet; 50 | import jxl.Workbook; 51 | import jxl.WorkbookSettings; 52 | import jxl.read.biff.BiffException; 53 | import pattypan.Session; 54 | import pattypan.Settings; 55 | import pattypan.UploadElement; 56 | import pattypan.Util; 57 | import pattypan.elements.WikiButton; 58 | import pattypan.elements.WikiLabel; 59 | import pattypan.elements.WikiPane; 60 | import pattypan.elements.WikiScrollPane; 61 | import pattypan.elements.WikiTextField; 62 | 63 | public class LoadPane extends WikiPane { 64 | 65 | Stage stage; 66 | Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); 67 | 68 | WikiTextField browsePath = new WikiTextField(""); 69 | WikiButton browseButton = new WikiButton("generic-browse", "small").setWidth(100); 70 | WikiButton reloadButton = new WikiButton("", "small", "inversed").setWidth(40).setIcon("refresh.png"); 71 | VBox infoContainer = new VBox(6); 72 | 73 | public LoadPane(Stage stage) { 74 | super(stage, 1.01); 75 | this.stage = stage; 76 | 77 | cfg.setDefaultEncoding("UTF-8"); 78 | cfg.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER); 79 | 80 | setContent(); 81 | setActions(); 82 | } 83 | 84 | /* 85 | * set content and actions 86 | ***************************************************************************** 87 | */ 88 | public WikiPane getContent() { 89 | return this; 90 | } 91 | 92 | private void setActions() { 93 | browseButton.setOnAction(event -> { 94 | selectSpreadSheetFile(); 95 | }); 96 | reloadButton.setOnAction(event -> { 97 | loadSpreadsheet(Session.FILE); 98 | }); 99 | } 100 | 101 | private void setContent() { 102 | addElement("validate-intro", 40); 103 | 104 | browsePath.setDisable(true); 105 | reloadButton.setDisable(true); 106 | 107 | addElementRow( 108 | new Node[]{browseButton, browsePath, reloadButton}, 109 | new Priority[]{Priority.NEVER, Priority.ALWAYS, Priority.NEVER} 110 | ); 111 | addElement(new WikiScrollPane(infoContainer)); 112 | 113 | prevButton.linkTo("StartPane", stage); 114 | nextButton.linkTo("CheckPane", stage); 115 | nextButton.setDisable(true); 116 | } 117 | 118 | /* 119 | * methods 120 | ***************************************************************************** 121 | */ 122 | private boolean checkIfFileExist(String path) { 123 | String fixedPath = path.trim() 124 | .replace("/", File.separator) 125 | .replace("\\", File.separator); 126 | 127 | File file = new File(fixedPath); 128 | return file.isFile(); 129 | } 130 | 131 | /** 132 | * 133 | * @param descriptions 134 | * @param template 135 | * @throws TemplateException 136 | * @throws IOException 137 | * @throws Exception 138 | */ 139 | private void addFilesToUpload(ArrayList> descriptions, Template template) throws TemplateException, IOException, Exception { 140 | Session.FILES_TO_UPLOAD = new ArrayList<>(); 141 | 142 | ArrayList errors = new ArrayList<>(); 143 | ArrayList warnings = new ArrayList<>(); 144 | 145 | for (Map description : descriptions) { 146 | String namePath = String.format("%s (%s)", 147 | description.get("name"), description.get("path")); 148 | 149 | if (description.get("path").isEmpty() && description.get("name").isEmpty()) { 150 | continue; 151 | } 152 | 153 | try { 154 | if (description.get("path").isEmpty()) { 155 | throw new Exception("empty path"); 156 | } 157 | if (description.get("name").isEmpty()) { 158 | throw new Exception("empty name"); 159 | } 160 | if (description.get("path").startsWith("https://") || description.get("path").startsWith("http://")) { 161 | if (!Util.validUrl(description.get("path"))) { 162 | throw new Exception("invalid URL"); 163 | } 164 | 165 | // when uploaded from URL the extension is not automatically added 166 | if (!Util.hasValidFileExtension(description.get("name"))) { 167 | throw new Exception("filename does not include a valid file extension"); 168 | } 169 | } else { 170 | if (!checkIfFileExist(description.get("path"))) { 171 | throw new Exception("file not found"); 172 | } 173 | } 174 | 175 | if (Util.hasPossibleBadFilenamePrefix(description.get("name"))) { 176 | warnings.add(description.get("name") + ": filename shouldn't have name from camera (DSC, DSCF, etc)"); 177 | } 178 | 179 | if (Util.hasInvalidFilenameCharacters(description.get("name"))) { 180 | throw new Exception(description.get("name") + ": filename shouldn't contain invalid characters (#, ], {, etc)"); 181 | } 182 | 183 | Set keys = Util.getKeysByValue(description, ""); 184 | if (keys.size() > 0) { 185 | String values = keys.toString(); 186 | warnings.add(namePath + ": empty values for " + values.substring(1, values.length() - 1)); 187 | } 188 | 189 | StringWriter writer = new StringWriter(); 190 | template.process(description, writer); 191 | String wikicode = writer.getBuffer().toString(); 192 | 193 | if (String.valueOf(wikicode.charAt(0)).equals("'")) { 194 | wikicode = wikicode.substring(1); 195 | } 196 | 197 | if (wikicode.isEmpty()) { 198 | throw new Exception("Error: empty template!"); 199 | } 200 | 201 | Session.FILES_TO_UPLOAD.add(new UploadElement(description, wikicode)); 202 | 203 | } catch (Exception ex) { 204 | errors.add(namePath + " " + ex.getMessage()); 205 | } 206 | } 207 | 208 | infoContainer.getChildren().add(new WikiLabel("Summary").setAlign("left").setClass("header")); 209 | addInfo(Session.FILES_TO_UPLOAD.size() + " files loaded successfully"); 210 | addInfo(errors.size() + " errors", "bold"); 211 | errors.stream().forEach((error) -> { 212 | addInfo(error); 213 | }); 214 | addInfo(warnings.size() + " warnings", "bold"); 215 | warnings.stream().forEach((warning) -> { 216 | addInfo(warning); 217 | }); 218 | 219 | if (Session.FILES_TO_UPLOAD.size() > 0) { 220 | nextButton.setDisable(false); 221 | } 222 | } 223 | 224 | private void addInfo(String text) { 225 | addInfo(text, ""); 226 | } 227 | 228 | private void addInfo(String text, String cssClass) { 229 | infoContainer.getChildren().add(new WikiLabel(text).setAlign("left").setClass(cssClass)); 230 | } 231 | 232 | /** 233 | * Checks headers of data sheet (first row). 234 | * 235 | * @param sheet sheet with data 236 | * @throws Exception when essential headers are missing 237 | */ 238 | private void readHeaders(Sheet sheet) throws Exception { 239 | int columns = sheet.getColumns(); 240 | ArrayList cols = new ArrayList<>(); 241 | for (int col = 0; col < columns; col++) { 242 | cols.add(sheet.getCell(col, 0).getContents()); 243 | } 244 | 245 | if (cols.isEmpty()) { 246 | throw new Exception("Header error: columns not found!"); 247 | } 248 | if (!cols.contains("path") || !cols.contains("name")) { 249 | throw new Exception("Header error: found " + cols.size() + " headers but 'path' and/or 'name' headers are missing"); 250 | } 251 | } 252 | 253 | /** 254 | * Get value of cell 255 | * 256 | * @param sheet sheet with data 257 | * @param column number of column 258 | * @param row number of cell 259 | * @return string with data in cell 260 | */ 261 | private String getCellValue(Sheet sheet, int column, int row) { 262 | SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd"); 263 | SimpleDateFormat formatDateHour = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 264 | formatDate.setTimeZone(TimeZone.getTimeZone("UTC")); 265 | formatDateHour.setTimeZone(TimeZone.getTimeZone("UTC")); 266 | 267 | Cell valueCell = sheet.getCell(column, row); 268 | String value; 269 | 270 | if (valueCell.getType() == CellType.DATE) { 271 | DateCell dateCell = (DateCell) valueCell; 272 | //@TODO: more elegant hour detection 273 | value = dateCell.getContents().contains(":") 274 | ? formatDateHour.format(dateCell.getDate()) 275 | : formatDate.format(dateCell.getDate()); 276 | } else { 277 | value = sheet.getCell(column, row).getContents().trim(); 278 | } 279 | return value; 280 | } 281 | 282 | /** 283 | * Sets all session stuff and reads spreadsheet. 284 | * 285 | * @param file spreadsheet file 286 | */ 287 | private void loadSpreadsheet(File file) { 288 | Session.DIRECTORY = file.getParentFile(); 289 | Session.FILE = file; 290 | browsePath.setText(file.getAbsolutePath()); 291 | Settings.setSetting("path", Session.DIRECTORY.getAbsolutePath()); 292 | 293 | try { 294 | readSpreadSheet(); 295 | } catch (IOException ex) { 296 | addInfo("File error: there are problems opening file. It may be corrupted."); 297 | } catch (BiffException ex) { 298 | addInfo("File error: file needs to be saved in binnary format. Please save your file in \"Excel 97-2003 format\""); 299 | } catch (InvalidReferenceException ex) { 300 | addInfo("File error: variables mismatch. Column headers variables must match wikitemplate variables."); 301 | } catch (Exception ex) { 302 | addInfo(ex.getMessage()); 303 | } 304 | } 305 | 306 | /** 307 | * Reads file descriptions from data sheet 308 | * 309 | * @param sheet sheet with file descriptions 310 | * @return 311 | * @throws Exception 312 | */ 313 | private ArrayList> readDescriptions(Sheet sheet) { 314 | ArrayList> descriptions = new ArrayList<>(); 315 | int rows = sheet.getRows(); 316 | int columns = sheet.getColumns(); 317 | 318 | for (int row = 1; row < rows; row++) { 319 | Map description = new HashMap(); 320 | for (int column = 0; column < columns; column++) { 321 | String label = sheet.getCell(column, 0).getContents().trim(); 322 | if (label.isEmpty()) { 323 | continue; 324 | } 325 | String value = getCellValue(sheet, column, row); 326 | description.put(label, value); 327 | } 328 | descriptions.add(description); 329 | } 330 | return descriptions; 331 | } 332 | 333 | /** 334 | * Reads spreadsheet stored in Session.FILE. 335 | */ 336 | private void readSpreadSheet() throws BiffException, IOException, Exception { 337 | infoContainer.getChildren().clear(); 338 | Session.SCENES.remove("CheckPane"); 339 | 340 | WorkbookSettings ws = new WorkbookSettings(); 341 | ws.setEncoding("Cp1252"); 342 | 343 | try { 344 | Workbook workbook = Workbook.getWorkbook(Session.FILE, ws); 345 | Sheet dataSheet = workbook.getSheet(0); 346 | Sheet templateSheet = workbook.getSheet(1); 347 | readHeaders(dataSheet); 348 | addFilesToUpload(readDescriptions(dataSheet), readTemplate(templateSheet)); 349 | } catch (IndexOutOfBoundsException ex) { 350 | throw new Exception("Error: your spreadsheet should have minimum two tabs."); 351 | } 352 | 353 | reloadButton.setDisable(false); 354 | } 355 | 356 | /** 357 | * Reads wikitemplate from template sheet 358 | * 359 | * @param sheet sheet with wikitemplate 360 | * @return 361 | * @throws IOException 362 | */ 363 | private Template readTemplate(Sheet sheet) throws Exception { 364 | try { 365 | String text = sheet.getCell(0, 0).getContents(); 366 | return new Template("wikitemplate", new StringReader(text), cfg); 367 | } catch (ArrayIndexOutOfBoundsException ex) { 368 | throw new Exception("Error: template in spreadsheet looks empty. Check if wikitemplate is present in second tab of your spreadsheet (first row and first column)."); 369 | } 370 | } 371 | 372 | /** 373 | * Shows spreadsheet shooser dialog. 374 | */ 375 | private void selectSpreadSheetFile() { 376 | FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter(Util.text("validate-file-type"), "*.xls"); 377 | 378 | FileChooser fileChooser = new FileChooser(); 379 | fileChooser.setTitle(Util.text("validate-file-select")); 380 | fileChooser.getExtensionFilters().add(extFilter); 381 | 382 | File initialDir = !Settings.getSetting("path").isEmpty() 383 | ? new File(Settings.getSetting("path")) 384 | : null; 385 | fileChooser.setInitialDirectory(initialDir); 386 | 387 | File spreadsheet; 388 | try { 389 | spreadsheet = fileChooser.showOpenDialog(stage); 390 | } catch (IllegalArgumentException ex) { 391 | fileChooser.setInitialDirectory(null); 392 | spreadsheet = fileChooser.showOpenDialog(stage); 393 | } 394 | 395 | if (spreadsheet != null) { 396 | loadSpreadsheet(spreadsheet); 397 | } 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/pattypan/Settings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2016 Pawel Marynowski. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package pattypan; 25 | 26 | import java.io.File; 27 | import java.io.FileInputStream; 28 | import java.io.FileNotFoundException; 29 | import java.io.FileOutputStream; 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.io.OutputStream; 33 | import java.util.HashMap; 34 | import java.util.Map; 35 | import java.util.Properties; 36 | import java.util.logging.Level; 37 | 38 | import pattypan.Util; 39 | 40 | public final class Settings { 41 | 42 | private Settings() {}; 43 | 44 | public static final String NAME = "pattypan"; 45 | public static final String VERSION = "22.03"; 46 | public static final String USERAGENT = NAME + "/" + VERSION + " (https://github.com/yarl/pattypan)"; 47 | 48 | public static final Map SETTINGS = new HashMap<>(); 49 | public static final Map TEMPLATES = new HashMap<>(); 50 | 51 | static { 52 | SETTINGS.put("path", ""); 53 | SETTINGS.put("user", ""); 54 | SETTINGS.put("windowWidth", "800"); 55 | SETTINGS.put("windowHeight", "600"); 56 | SETTINGS.put("exifDate", ""); 57 | 58 | TEMPLATES.put( 59 | "Information", 60 | new Template("Information", 61 | new TemplateField[]{ 62 | new TemplateField("description", "Description"), 63 | new TemplateField("date", "Date"), 64 | new TemplateField("source", "Source"), 65 | new TemplateField("author", "Author"), 66 | new TemplateField("permission", "Permission"), 67 | new TemplateField("other_versions", "Other versions"), 68 | new TemplateField("license", "License"), 69 | }, "=={{int:filedesc}}==\n" 70 | + "{{Information\n" 71 | + " |description = ${description}\n" 72 | + " |date = ${date}\n" 73 | + " |source = ${source}\n" 74 | + " |author = ${author}\n" 75 | + " |permission = ${permission}\n" 76 | + " |other versions = ${other_versions}\n" 77 | + "}}\n\n" 78 | + "=={{int:license-header}}==\n${license}\n\n" 79 | + "<#if categories ? has_content>\n" 80 | + "<#list categories ? split(\";\") as category>\n" 81 | + "[[Category:${category?trim}]]\n" 82 | + "\n" 83 | + "<#else>{{subst:unc}}\n" 84 | + "" 85 | ) 86 | ); 87 | TEMPLATES.put( 88 | "Artwork", 89 | new Template("Artwork", 90 | new TemplateField[]{ 91 | new TemplateField("artist", "Artist"), 92 | new TemplateField("author", "Author"), 93 | new TemplateField("title", "Title"), 94 | new TemplateField("description", "Description"), 95 | new TemplateField("date", "Date"), 96 | new TemplateField("medium", "Medium"), 97 | new TemplateField("dimensions", "Dimensions"), 98 | new TemplateField("institution", "Institution"), 99 | new TemplateField("department", "Department"), 100 | new TemplateField("place_of_discovery", "Place of discovery"), 101 | new TemplateField("object_history", "Object history"), 102 | new TemplateField("exhibition_history", "Exhibition history"), 103 | new TemplateField("credit_line", "Credit line"), 104 | new TemplateField("inscriptions", "Inscriptions"), 105 | new TemplateField("notes", "Notes"), 106 | new TemplateField("accession_number", "Accession number"), 107 | new TemplateField("place_of_creation", "Place of creation"), 108 | new TemplateField("source", "Source"), 109 | new TemplateField("permission", "Permission"), 110 | new TemplateField("other_versions", "Other versions"), 111 | new TemplateField("references", "References"), 112 | new TemplateField("wikidata", "Wikidata"), 113 | new TemplateField("license", "License"), 114 | new TemplateField("partnership", "Partnership") 115 | }, "=={{int:filedesc}}==\n" 116 | + "{{Artwork\n" 117 | + " |artist = ${artist}\n" 118 | + " |author = ${author}\n" 119 | + " |title = ${title}\n" 120 | + " |description = ${description}\n" 121 | + " |date = ${date}\n" 122 | + " |medium = ${medium}\n" 123 | + " |dimensions = ${dimensions}\n" 124 | + " |institution = ${institution}\n" 125 | + " |department = ${department}\n" 126 | + " |place of discovery = ${place_of_discovery}\n" 127 | + " |object history = ${object_history}\n" 128 | + " |exhibition history = ${exhibition_history}\n" 129 | + " |credit line = ${credit_line}\n" 130 | + " |inscriptions = ${inscriptions}\n" 131 | + " |notes = ${notes}\n" 132 | + " |accession number = ${accession_number}\n" 133 | + " |place of creation = ${place_of_creation}\n" 134 | + " |source = ${source}\n" 135 | + " |permission = ${permission}\n" 136 | + " |other_versions = ${other_versions}\n" 137 | + " |references = ${references}\n" 138 | + " |wikidata = ${wikidata}\n" 139 | + "}}\n\n" 140 | + "=={{int:license-header}}==\n" 141 | + "${license}${partnership}" 142 | + "\n\n" 143 | + "<#if categories ? has_content>\n" 144 | + "<#list categories ? split(\";\") as category>\n" 145 | + "[[Category:${category?trim}]]\n" 146 | + "\n" 147 | + "<#else>{{subst:unc}}\n" 148 | + "" 149 | ) 150 | ); 151 | TEMPLATES.put( 152 | "Art Photo", 153 | new Template("Art Photo", 154 | new TemplateField[]{ 155 | new TemplateField("wikidata", "Wikidata"), 156 | new TemplateField("artwork_license", "Artwork license"), 157 | new TemplateField("photo_description", "Photo description"), 158 | new TemplateField("photo_date", "Photo date"), 159 | new TemplateField("photographer", "Photographer"), 160 | new TemplateField("source", "Source"), 161 | new TemplateField("photo_license", "Photo license"), 162 | new TemplateField("other_versions", "Other versions"), 163 | new TemplateField("partnership", "Partnership") 164 | }, "=={{int:filedesc}}==\n" 165 | + "{{Art Photo\n" 166 | + " |wikidata = ${wikidata}\n" 167 | + " |artwork license = ${artwork_license}\n" 168 | + " |photo description = ${photo_description}\n" 169 | + " |photo date = ${photo_date}\n" 170 | + " |photographer = ${photographer}\n" 171 | + " |source = ${source}\n" 172 | + " |photo license = ${photo_license}\n" 173 | + " |other_versions = ${other_versions}\n" 174 | + "}}\n\n" 175 | + "=={{int:license-header}}==\n" 176 | + "${artwork_license}${photo_license}${partnership}" 177 | + "\n\n" 178 | + "<#if categories ? has_content>\n" 179 | + "<#list categories ? split(\";\") as category>\n" 180 | + "[[Category:${category?trim}]]\n" 181 | + "\n" 182 | + "<#else>{{subst:unc}}\n" 183 | + "" 184 | ) 185 | ); 186 | TEMPLATES.put( 187 | "Book", 188 | new Template("Book", 189 | new TemplateField[]{ 190 | new TemplateField("author", "Author"), 191 | new TemplateField("translator", "Translator"), 192 | new TemplateField("editor", "Editor"), 193 | new TemplateField("illustrator", "Illustrator"), 194 | new TemplateField("title", "Title"), 195 | new TemplateField("subtitle", "Subtitle"), 196 | new TemplateField("series_title", "Series title"), 197 | new TemplateField("volume", "Volume"), 198 | new TemplateField("edition", "Edition"), 199 | new TemplateField("publisher", "Publisher"), 200 | new TemplateField("printer", "Printer"), 201 | new TemplateField("date", "Date"), 202 | new TemplateField("city", "City"), 203 | new TemplateField("language", "Language"), 204 | new TemplateField("description", "Description"), 205 | new TemplateField("source", "Source"), 206 | new TemplateField("permission", "Permission"), 207 | new TemplateField("image", "Image"), 208 | new TemplateField("image_page", "Image page"), 209 | new TemplateField("pageoverview", "Page overview"), 210 | new TemplateField("wikisource", "Wikisource"), 211 | new TemplateField("homecat", "Home category"), 212 | new TemplateField("other_versions", "Other versions"), 213 | new TemplateField("isbn", "ISBN"), 214 | new TemplateField("lccn", "LCCN"), 215 | new TemplateField("oclc", "OCLC"), 216 | new TemplateField("references", "References"), 217 | new TemplateField("linkback", "Linkback"), 218 | new TemplateField("wikidata", "Wikidata"), 219 | new TemplateField("license", "License"), 220 | new TemplateField("partnership", "Partnership") 221 | }, "=={{int:filedesc}}==\n" 222 | + "{{Book\n" 223 | + " |Author = ${author}\n" 224 | + " |Translator = ${translator}\n" 225 | + " |Editor = ${editor}\n" 226 | + " |Illustrator = ${illustrator}\n" 227 | + " |Title = ${title}\n" 228 | + " |Subtitle = ${subtitle}\n" 229 | + " |Series title = ${series_title}\n" 230 | + " |Volume = ${volume}\n" 231 | + " |Edition = ${edition}\n" 232 | + " |Publisher = ${publisher}\n" 233 | + " |Printer = ${printer}\n" 234 | + " |Date = ${date}\n" 235 | + " |City = ${city}\n" 236 | + " |Language = ${language}\n" 237 | + " |Description = ${description}\n" 238 | + " |Source = ${source}\n" 239 | + " |Permission = ${permission}\n" 240 | + " |Image = ${image}\n" 241 | + " |Image page = ${image_page}\n" 242 | + " |Pageoverview = ${pageoverview}\n" 243 | + " |Wikisource = ${wikisource}\n" 244 | + " |Homecat = ${homecat}\n" 245 | + " |Other_versions = ${other_versions}\n" 246 | + " |ISBN = ${isbn}\n" 247 | + " |LCCN = ${lccn}\n" 248 | + " |OCLC = ${oclc}\n" 249 | + " |References = ${references}\n" 250 | + " |Linkback = ${linkback}\n" 251 | + " |Wikidata = ${wikidata}\n" 252 | + "}}\n\n" 253 | + "=={{int:license-header}}==\n" 254 | + "${license}${partnership}" 255 | + "\n\n" 256 | + "<#if categories ? has_content>\n" 257 | + "<#list categories ? split(\";\") as category>\n" 258 | + "[[Category:${category?trim}]]\n" 259 | + "\n" 260 | + "<#else>{{subst:unc}}\n" 261 | + "" 262 | ) 263 | ); 264 | TEMPLATES.put("Map", 265 | new Template("Map", 266 | new TemplateField[]{ 267 | new TemplateField("title", "Title"), 268 | new TemplateField("description", "Description"), 269 | new TemplateField("legend", "Legend"), 270 | new TemplateField("author", "Author"), 271 | new TemplateField("date", "Date"), 272 | new TemplateField("source", "Source"), 273 | new TemplateField("permission", "Permission"), 274 | new TemplateField("map_date", "Map date"), 275 | new TemplateField("location", "Location"), 276 | new TemplateField("projection", "Projection"), 277 | new TemplateField("scale", "Scale"), 278 | new TemplateField("heading", "Heading"), 279 | new TemplateField("latitude", "Latitude"), 280 | new TemplateField("longitude", "Longitude"), 281 | new TemplateField("warp_status", "Warp status"), 282 | new TemplateField("set", "Set"), 283 | new TemplateField("sheet", "Sheet"), 284 | new TemplateField("type", "Type"), 285 | new TemplateField("language", "Language"), 286 | new TemplateField("publisher", "Publisher"), 287 | new TemplateField("printer", "Printer"), 288 | new TemplateField("print_date", "Print date"), 289 | new TemplateField("institution", "Institution"), 290 | new TemplateField("accession_number", "Accession number"), 291 | new TemplateField("dimensions", "Dimensions"), 292 | new TemplateField("medium", "Medium"), 293 | new TemplateField("inscriptions", "Inscriptions"), 294 | new TemplateField("notes", "Notes"), 295 | new TemplateField("other_versions", "Other versions"), 296 | new TemplateField("license", "License"), 297 | new TemplateField("partnership", "Partnership") 298 | }, "=={{int:filedesc}}==\n" 299 | + "{{Map\n" 300 | + " |title = ${title}\n" 301 | + " |description = ${description}\n" 302 | + " |legend = ${legend}\n" 303 | + " |author = ${author}\n" 304 | + " |date = ${date}\n" 305 | + " |source = ${source}\n" 306 | + " |permission = ${permission}\n" 307 | + " |map date = ${map_date}\n" 308 | + " |location = ${location}\n" 309 | + " |projection = ${projection}\n" 310 | + " |scale = ${scale}\n" 311 | + " |heading = ${heading}\n" 312 | + " |latitude = ${latitude}\n" 313 | + " |longitude = ${longitude}\n" 314 | + " |warp status = ${warp_status}\n" 315 | + " |set = ${set}\n" 316 | + " |sheet = ${sheet}\n" 317 | + " |type = ${type}\n" 318 | + " |language = ${language}\n" 319 | + " |publisher = ${publisher}\n" 320 | + " |printer = ${printer}\n" 321 | + " |print date = ${print_date}\n" 322 | + " |institution = ${institution}\n" 323 | + " |accession number = ${accession_number}\n" 324 | + " |dimensions = ${dimensions}\n" 325 | + " |medium = ${medium}\n" 326 | + " |inscriptions = ${inscriptions}\n" 327 | + " |notes = ${notes}\n" 328 | + " |other versions = ${other_versions}\n" 329 | + "}}\n\n" 330 | + "=={{int:license-header}}==\n" 331 | + "${license}${partnership}" 332 | + "\n\n" 333 | + "<#if categories ? has_content>\n" 334 | + "<#list categories ? split(\";\") as category>\n" 335 | + "[[Category:${category?trim}]]\n" 336 | + "\n" 337 | + "<#else>{{subst:unc}}\n" 338 | + "" 339 | ) 340 | ); 341 | TEMPLATES.put("Musical work", 342 | new Template("Musical work", 343 | new TemplateField[]{ 344 | new TemplateField("composer", "Composer"), 345 | new TemplateField("lyrics_writer", "Lyrics writer"), 346 | new TemplateField("performer", "Performer"), 347 | new TemplateField("title", "Title"), 348 | new TemplateField("description", "Description"), 349 | new TemplateField("composition_date", "Composition date"), 350 | new TemplateField("performance_date", "Performance date"), 351 | new TemplateField("notes", "Notes"), 352 | new TemplateField("record_id", "Record ID"), 353 | new TemplateField("image", "Image"), 354 | new TemplateField("references", "References"), 355 | new TemplateField("source", "Source"), 356 | new TemplateField("permission", "Permission"), 357 | new TemplateField("other_versions", "Other versions"), 358 | new TemplateField("license", "License"), 359 | new TemplateField("partnership", "Partnership") 360 | }, "=={{int:filedesc}}==\n" 361 | + "{{Musical work\n" 362 | + " |composer = ${composer}\n" 363 | + " |lyrics_writer = ${lyrics_writer}\n" 364 | + " |performer = ${performer}\n" 365 | + " |title = ${title}\n" 366 | + " |description = ${description}\n" 367 | + " |composition_date = ${composition_date}\n" 368 | + " |performance_date = ${performance_date}\n" 369 | + " |notes = ${notes}\n" 370 | + " |record_ID = ${record_id}\n" 371 | + " |image = ${image}\n" 372 | + " |references = ${references}\n" 373 | + " |source = ${source}\n" 374 | + " |permission = ${permission}\n" 375 | + " |other_versions = ${other_versions}\n" 376 | + "}}\n\n" 377 | + "=={{int:license-header}}==\n" 378 | + "${license}${partnership}" 379 | + "\n\n" 380 | + "<#if categories ? has_content>\n" 381 | + "<#list categories ? split(\";\") as category>\n" 382 | + "[[Category:${category?trim}]]\n" 383 | + "\n" 384 | + "<#else>{{subst:unc}}\n" 385 | + "" 386 | ) 387 | ); 388 | TEMPLATES.put("Photograph", 389 | new Template("Photograph", 390 | new TemplateField[]{ 391 | new TemplateField("photographer", "Photographer"), 392 | new TemplateField("title", "Title"), 393 | new TemplateField("description", "Description"), 394 | new TemplateField("depicted_people", "Depicted people"), 395 | new TemplateField("depicted_place", "Depicted place"), 396 | new TemplateField("date", "Date"), 397 | new TemplateField("medium", "Medium"), 398 | new TemplateField("dimensions", "Dimensions"), 399 | new TemplateField("institution", "Institution"), 400 | new TemplateField("department", "Department"), 401 | new TemplateField("references", "References"), 402 | new TemplateField("object_history", "Object history"), 403 | new TemplateField("exhibition_history", "Exhibition history"), 404 | new TemplateField("credit_line", "Credit line"), 405 | new TemplateField("inscriptions", "Inscriptions"), 406 | new TemplateField("notes", "Notes"), 407 | new TemplateField("accession_number", "Accession number"), 408 | new TemplateField("source", "Source"), 409 | new TemplateField("permission", "Permission"), 410 | new TemplateField("other_versions", "Other versions"), 411 | new TemplateField("license", "License"), 412 | new TemplateField("partnership", "Partnership") 413 | }, "=={{int:filedesc}}==\n" 414 | + "{{Photograph\n" 415 | + " |photographer = ${photographer}\n" 416 | + " |title = ${title}\n" 417 | + " |description = ${description}\n" 418 | + " |depicted people = ${depicted_people}\n" 419 | + " |depicted place = ${depicted_place}\n" 420 | + " |date = ${date}\n" 421 | + " |medium = ${medium}\n" 422 | + " |dimensions = ${dimensions}\n" 423 | + " |institution = ${institution}\n" 424 | + " |department = ${department}\n" 425 | + " |references = ${references}\n" 426 | + " |object history = ${object_history}\n" 427 | + " |exhibition history = ${exhibition_history}\n" 428 | + " |credit line = ${credit_line}\n" 429 | + " |inscriptions = ${inscriptions}\n" 430 | + " |notes = ${notes}\n" 431 | + " |accession number = ${accession_number}\n" 432 | + " |source = ${source}\n" 433 | + " |permission = ${permission}\n" 434 | + " |other_versions = ${other_versions}\n" 435 | + "}}\n\n" 436 | + "=={{int:license-header}}==\n" 437 | + "${license}${partnership}" 438 | + "\n\n" 439 | + "<#if categories ? has_content>\n" 440 | + "<#list categories ? split(\";\") as category>\n" 441 | + "[[Category:${category?trim}]]\n" 442 | + "\n" 443 | + "<#else>{{subst:unc}}\n" 444 | + "" 445 | ) 446 | ); 447 | 448 | /* 449 | TEMPLATES.put("Name", 450 | new Template("Name", 451 | new TemplateField[]{ 452 | new TemplateField("", ""), 453 | new TemplateField("", ""), 454 | }, "" 455 | ) 456 | ); 457 | */ 458 | } 459 | 460 | public static String getSetting(String key) { 461 | return SETTINGS.get(key) != null ? SETTINGS.get(key) : ""; 462 | } 463 | 464 | public static int getSettingInt(String key) { 465 | return Integer.parseInt(SETTINGS.get(key)); 466 | } 467 | 468 | /** 469 | * Reads user properties from config.properties file 470 | */ 471 | public static void readProperties() { 472 | Properties prop = new Properties(); 473 | InputStream input = null; 474 | 475 | try { 476 | File f = new File(Util.getApplicationDirectory() + "/config.properties"); 477 | input = new FileInputStream(f); 478 | prop.load(input); 479 | 480 | prop.forEach((key, value) -> { 481 | if (!value.toString().isEmpty()) { 482 | SETTINGS.put(key.toString(), value.toString()); 483 | } 484 | }); 485 | 486 | } catch (FileNotFoundException ex) { 487 | Session.LOGGER.log(Level.INFO, "Settings file not found, use default"); 488 | } catch (IOException ex) { 489 | Session.LOGGER.log(Level.SEVERE, null, ex); 490 | } finally { 491 | if (input != null) { 492 | try { 493 | input.close(); 494 | } catch (IOException ex) { 495 | Session.LOGGER.log(Level.SEVERE, null, ex); 496 | } 497 | } 498 | } 499 | } 500 | 501 | /** 502 | * Saves user properties to config.properties file 503 | */ 504 | public static void saveProperties() { 505 | Properties prop = new Properties(); 506 | OutputStream output = null; 507 | 508 | try { 509 | new File(Util.getApplicationDirectory()).mkdirs(); 510 | File f = new File(Util.getApplicationDirectory() + "/config.properties"); 511 | output = new FileOutputStream(f); 512 | SETTINGS.forEach((key, value) -> { 513 | prop.setProperty(key, value); 514 | }); 515 | prop.store(output, null); 516 | } catch (IOException ex) { 517 | Session.LOGGER.log(Level.SEVERE, null, ex); 518 | } finally { 519 | if (output != null) { 520 | try { 521 | output.close(); 522 | } catch (IOException ex) { 523 | Session.LOGGER.log(Level.SEVERE, null, ex); 524 | } 525 | } 526 | } 527 | } 528 | 529 | public static void setSetting(String key, String value) { 530 | SETTINGS.put(key, value); 531 | } 532 | } 533 | --------------------------------------------------------------------------------