/** * cemu_UI * * Copyright 2017-2019 <@Seil0> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.cemu_UI.application; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.sql.SQLException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Locale; import java.util.Properties; import java.util.ResourceBundle; import javax.imageio.ImageIO; import javax.swing.ProgressMonitor; import javax.swing.ProgressMonitorInputStream; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.cemu_UI.controller.CloudController; import com.cemu_UI.controller.DBController; import com.cemu_UI.controller.SmmdbAPIController; import com.cemu_UI.controller.UpdateController; import com.cemu_UI.controller.XMLController; import com.cemu_UI.datatypes.CourseTableDataType; import com.cemu_UI.datatypes.GlobalDataTypes.CloudService; import com.cemu_UI.datatypes.SmmdbApiDataType; import com.cemu_UI.datatypes.UIROMDataType; import com.cemu_UI.uiElements.JFXEditGameAlert; import com.cemu_UI.uiElements.JFXInfoAlert; import com.cemu_UI.uiElements.JFXOkayCancelAlert; import com.cemu_UI.uiElements.JFXTextAreaAlert; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXColorPicker; import com.jfoenix.controls.JFXHamburger; import com.jfoenix.controls.JFXSpinner; import com.jfoenix.controls.JFXTextField; import com.jfoenix.controls.JFXToggleButton; import com.jfoenix.controls.JFXTreeTableColumn; import com.jfoenix.controls.JFXTreeTableView; import com.jfoenix.transitions.hamburger.HamburgerBackArrowBasicTransition; import javafx.animation.TranslateTransition; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.ChoiceBox; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.control.ScrollPane; import javafx.scene.control.TreeItem; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; import javafx.util.Duration; import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.exception.ZipException; public class MainWindowController { @FXML private JFXButton aboutBtn; @FXML private JFXButton settingsBtn; @FXML private JFXButton addBtn; @FXML private JFXButton reloadRomsBtn; @FXML private JFXButton smmdbBtn; @FXML private JFXButton cemuTFBtn; @FXML private JFXButton romTFBtn; @FXML private JFXButton updateBtn; @FXML private JFXButton smmdbDownloadBtn; @FXML private JFXButton playBtn; @FXML private JFXButton lastTimePlayedBtn; @FXML JFXButton totalPlaytimeBtn; @FXML private JFXHamburger menuHam; @FXML private JFXTextField cemuTextField; @FXML private JFXTextField romTextField; @FXML private JFXTextField courseSearchTextFiled; @FXML private JFXTextField executeCommandTextFiled; @FXML private TextFlow smmdbTextFlow; @FXML private JFXColorPicker colorPicker; @FXML private JFXToggleButton cloudSyncToggleBtn; @FXML private JFXToggleButton autoUpdateToggleBtn; @FXML private JFXToggleButton fullscreenToggleBtn; @FXML private ChoiceBox languageChoisBox; @FXML private ChoiceBox branchChoisBox; @FXML private ScrollPane mainScrollPane; @FXML private ScrollPane settingsScrollPane; @FXML private ScrollPane smmdbScrollPane; @FXML private ScrollPane smmdbImageViewScrollPane; @FXML private AnchorPane mainAnchorPane; @FXML private AnchorPane settingsAnchorPane; @FXML private AnchorPane smmdbAnchorPane; @FXML private FlowPane gamesFlowPane; @FXML private VBox sideMenuVBox; @FXML private HBox topHBox; @FXML private HBox bottomHBox; @FXML private ImageView smmdbImageView; @FXML private Label helpLbl; @FXML private Label cemu_UISettingsLbl; @FXML private Label cemuDirectoryLbl; @FXML private Label romDirectoryLbl; @FXML private Label mainColorLbl; @FXML private Label languageLbl; @FXML private Label updateLbl; @FXML private Label branchLbl; @FXML private Label cemuSettingsLbl; @FXML private Label licensesLbl; @FXML private JFXTreeTableView courseTreeTable = new JFXTreeTableView(); @FXML private TreeItem root = new TreeItem<>(new CourseTableDataType("", "", 0, 0)); @FXML private JFXTreeTableColumn titleColumn = new JFXTreeTableColumn<>("title"); @FXML private JFXTreeTableColumn idColumn = new JFXTreeTableColumn<>("id"); @FXML private JFXTreeTableColumn starsColumn = new JFXTreeTableColumn<>("stars"); @FXML private JFXTreeTableColumn timeColumn = new JFXTreeTableColumn<>("time"); private static MainWindowController MWC; private XMLController xmlController; private DBController dbController; private playGame playGame; private SmmdbAPIController smmdbAPIController; private Stage primaryStage; private boolean menuTrue = false; private boolean playTrue = false; private String gameExecutePath; private String btnStyle; private String selectedGameTitleID; private String selectedGameTitle; private String id; private String version = "0.3.3"; private String buildNumber = "089"; private String versionName = "Purple Comet"; private int selectedUIDataIndex; private int selected; private DirectoryChooser directoryChooser = new DirectoryChooser(); private ObservableList branches = FXCollections.observableArrayList("stable", "beta"); private ObservableList languages = FXCollections.observableArrayList("English (en_US)", "Deutsch (de_DE)"); private ObservableList smmIDs = FXCollections.observableArrayList("fe31b7f2", "44fc5929"); private ObservableList games = FXCollections.observableArrayList(); ObservableList courses = FXCollections.observableArrayList(); ObservableList filteredCourses = FXCollections.observableArrayList(); ArrayList courseText = new ArrayList(); ArrayList nameText = new ArrayList(); Properties props = new Properties(); Properties gameProps = new Properties(); private static final Logger LOGGER = LogManager.getLogger(MainWindowController.class.getName()); private HamburgerBackArrowBasicTransition burgerTask; private MenuItem edit = new MenuItem("edit"); private MenuItem remove = new MenuItem("remove"); private MenuItem addUpdate = new MenuItem("update"); private MenuItem addDLC = new MenuItem("add DLC"); private ContextMenu gameContextMenu = new ContextMenu(edit, remove, addUpdate, addDLC); private Label lastGameLabel = new Label(); // language support private String lastPlayed; private String today; private String yesterday; private String never; public MainWindowController() { xmlController = new XMLController(); dbController = DBController.getInstance(); smmdbAPIController = new SmmdbAPIController(); } /** * initialize the MainWindowController * loadSettings, checkAutoUpdate, initUI and initActions */ public void init() { xmlController.loadSettings(); // load settings checkAutoUpdate(); initUI(); initActions(); dbController.init(); dbController.refreshDataBase(); games = dbController.loadAllGames(); addAllGames(); } /** * initialize all variable UI parameters and elements */ private void initUI() { LOGGER.info("initializing UI ..."); primaryStage = (Stage) mainAnchorPane.getScene().getWindow(); // set primary stage for dialogs // TODO this is broken if (XMLController.getWindowWidth() > 100 && XMLController.getWindowHeight() > 100) { mainAnchorPane.setPrefSize(XMLController.getWindowWidth(), XMLController.getWindowHeight()); } cemuTextField.setText(XMLController.getCemuPath()); romTextField.setText(XMLController.getRomDirectoryPath()); colorPicker.setValue(Color.valueOf(XMLController.getColor())); fullscreenToggleBtn.setSelected(XMLController.isFullscreen()); cloudSyncToggleBtn.setSelected(XMLController.isCloudSync()); autoUpdateToggleBtn.setSelected(XMLController.isAutoUpdate()); branchChoisBox.setItems(branches); languageChoisBox.setItems(languages); bottomHBox.setPickOnBounds(false); if (XMLController.isUseBeta()) { branchChoisBox.getSelectionModel().select(1); } else { branchChoisBox.getSelectionModel().select(0); } applyColor(); // initialize courseTable titleColumn.setPrefWidth(185); timeColumn.setPrefWidth(112); starsColumn.setPrefWidth(90); courseTreeTable.setRoot(root); courseTreeTable.setShowRoot(false); courseTreeTable.setEditable(false); titleColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().title); idColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().id); starsColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().stars.asObject()); timeColumn.setCellValueFactory(cellData -> cellData.getValue().getValue().time.asObject()); courseTreeTable.getColumns().add(titleColumn); courseTreeTable.getColumns().add(timeColumn); courseTreeTable.getColumns().add(starsColumn); courseTreeTable.getColumns().add(idColumn); courseTreeTable.getColumns().get(3).setVisible(false); // the idColumn should not bee displayed setUILanguage(); LOGGER.info("initializing UI done"); } /** * initialize all actions not initialized by a own method */ private void initActions() { LOGGER.info("initializing Actions ..."); MWC = this; burgerTask = new HamburgerBackArrowBasicTransition(menuHam); menuHam.addEventHandler(MouseEvent.MOUSE_PRESSED, (e)->{ if (playTrue) { playBtnSlideOut(); } if (menuTrue) { sideMenuSlideOut(); burgerTask.setRate(-1.0); burgerTask.play(); menuTrue = false; } else { sideMenuSlideIn(); burgerTask.setRate(1.0); burgerTask.play(); menuTrue = true; } if (settingsScrollPane.isVisible()) { settingsScrollPane.setVisible(false); xmlController.saveSettings(); } if (smmdbAnchorPane.isVisible()) { smmdbAnchorPane.setVisible(false); } }); edit.setOnAction(event -> { try { LOGGER.info("edit " + selectedGameTitleID); String[] gameInfo = dbController.getGameInfo(selectedGameTitleID); // new edit dialog String headingText = XMLController.getLocalBundle().getString("editHeadingText") + " \"" + selectedGameTitle + "\""; JFXEditGameAlert editGameAlert = new JFXEditGameAlert(headingText, XMLController.getLocalBundle().getString("editBodyText"), btnStyle, 1, primaryStage, MWC); editGameAlert.setTitle(gameInfo[0]); editGameAlert.setCoverPath(gameInfo[1]); editGameAlert.setRomPath(gameInfo[2]); editGameAlert.setTitleID(gameInfo[3]); editGameAlert.showAndWait(); } catch (Exception e) { LOGGER.warn("trying to edit " + selectedGameTitleID + ",which is not a valid type!", e); } }); remove.setOnAction(event -> { try { LOGGER.info("remove " + selectedGameTitle + "(" + selectedGameTitleID + ")"); String headingText = XMLController.getLocalBundle().getString("removeHeadingText") + " \"" + selectedGameTitle + "\""; String bodyText = XMLController.getLocalBundle().getString("removeBodyText") + " " + selectedGameTitle + " ?"; JFXOkayCancelAlert removeGameAlert = new JFXOkayCancelAlert(headingText, bodyText, btnStyle, primaryStage); removeGameAlert.setOkayAction(e -> { try { games.remove(selectedUIDataIndex); // remove game form games-list dbController.removeGame(selectedGameTitleID); // remove game from database refreshUIData(); // refresh all games at gamesAnchorPane (UI) } catch (Exception ex) { LOGGER.error("error while removing ROM from database!", ex); } }); removeGameAlert.setCancelAction(e -> LOGGER.info("Action canceld by user!")); removeGameAlert.showAndWait(); } catch (Exception e) { LOGGER.error("error while removing " + selectedGameTitle + "(" + selectedGameTitleID + ")", e); } }); addUpdate.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { try { LOGGER.info("update: " + selectedGameTitleID); EventHandler okayAction = new EventHandler() { @Override public void handle(ActionEvent event) { DirectoryChooser directoryChooser = new DirectoryChooser(); File selectedDirecroty = directoryChooser.showDialog(primaryStage); String updatePath = selectedDirecroty.getAbsolutePath(); String[] parts = selectedGameTitleID.split("-"); // split string into 2 parts at "-" File srcDir = new File(updatePath); File destDir = new File(XMLController.getCemuPath() + "/mlc01/usr/title/" + parts[0] + "/" + parts[1]); // if directory doesn't exist create it if (destDir.exists() != true) { destDir.mkdir(); } try { LOGGER.info("copying the content of " + updatePath + " to " + destDir.toString()); playBtn.setText(XMLController.getLocalBundle().getString("playBtnUpdating")); playBtn.setDisable(true); FileUtils.copyDirectory(srcDir, destDir); playBtn.setText(XMLController.getLocalBundle().getString("playBtnPlay")); playBtn.setDisable(false); LOGGER.info("copying files done!"); } catch (IOException e) { e.printStackTrace(); } } }; EventHandler cancelAction = new EventHandler() { @Override public void handle(ActionEvent event) { LOGGER.info("Action canceld by user!"); } }; String headingText = XMLController.getLocalBundle().getString("addUpdateHeadingText") + " \"" + selectedGameTitle + "\""; JFXOkayCancelAlert updateGameAlert = new JFXOkayCancelAlert(headingText, XMLController.getLocalBundle().getString("addUpdateBodyText"), btnStyle, primaryStage); updateGameAlert.setOkayAction(okayAction); updateGameAlert.setCancelAction(cancelAction); updateGameAlert.showAndWait(); } catch (Exception e) { LOGGER.warn("trying to update " + selectedGameTitleID + ",which is not a valid type!", e); } } }); addDLC.setOnAction(new EventHandler() { @Override public void handle(ActionEvent event) { try { LOGGER.info("add DLC: " + selectedGameTitleID); String headingText = XMLController.getLocalBundle().getString("addDLCHeadingText") + " \"" + selectedGameTitle + "\""; EventHandler okayAction = new EventHandler() { @Override public void handle(ActionEvent event) { DirectoryChooser directoryChooser = new DirectoryChooser(); File selectedDirecroty = directoryChooser.showDialog(primaryStage); String dlcPath = selectedDirecroty.getAbsolutePath(); String[] parts = selectedGameTitleID.split("-"); // split string into 2 parts at "-" File srcDir = new File(dlcPath); File destDir = new File(XMLController.getCemuPath() + "/mlc01/usr/title/" + parts[0] + "/" + parts[1] + "/aoc"); // if directory doesn't exist create it if (destDir.exists() != true) { destDir.mkdir(); } try { LOGGER.info("copying the content of " + dlcPath + " to " + destDir.toString()); playBtn.setText(XMLController.getLocalBundle().getString("playBtnCopyingFiles")); playBtn.setDisable(true); FileUtils.copyDirectory(srcDir, destDir); playBtn.setText(XMLController.getLocalBundle().getString("playBtnPlay")); playBtn.setDisable(false); LOGGER.info("copying files done!"); } catch (IOException e) { e.printStackTrace(); } } }; EventHandler cancelAction = new EventHandler() { @Override public void handle(ActionEvent event) { LOGGER.info("Action canceld by user!"); } }; JFXOkayCancelAlert addDLCAlert = new JFXOkayCancelAlert(headingText, XMLController.getLocalBundle().getString("addDLCBodyText"), btnStyle, primaryStage); addDLCAlert.setOkayAction(okayAction); addDLCAlert.setCancelAction(cancelAction); addDLCAlert.showAndWait(); } catch (Exception e) { LOGGER.warn("trying to add a dlc to " + selectedGameTitleID + ",which is not a valid type!", e); } } }); gamesFlowPane.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { @Override public void handle(MouseEvent event) { if (playTrue) { playBtnSlideOut(); } } }); topHBox.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { @Override public void handle(MouseEvent event) { if (playTrue) { playBtnSlideOut(); } } }); courseSearchTextFiled.textProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, String oldValue, String newValue) { filteredCourses.removeAll(filteredCourses); root.getChildren().remove(0, root.getChildren().size()); for (int i = 0; i < courses.size(); i++) { if (courses.get(i).getTitle().toLowerCase().contains(courseSearchTextFiled.getText().toLowerCase())) { // add data from courses to filteredCourses where title contains search input filteredCourses.add(courses.get(i)); } } for (int i = 0; i < filteredCourses.size(); i++) { CourseTableDataType helpCourse = new CourseTableDataType(filteredCourses.get(i).getTitle(), filteredCourses.get(i).getId(), filteredCourses.get(i).getTime(), filteredCourses.get(i).getStars()); root.getChildren().add(new TreeItem(helpCourse)); // add data to root-node } } }); // Change-listener for TreeTable courseTreeTable.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Object oldVal, Object newVal) { selected = courseTreeTable.getSelectionModel().getSelectedIndex(); // get selected item id = idColumn.getCellData(selected); // get name of selected item for (int i = 0; i < courses.size(); i++) { if (courses.get(i).getId() == id) { try { URL url = new URL("https://smmdb.ddns.net/courseimg/" + id + "_full?v=1"); Image sourceImage = new Image(url.toURI().toString()); // scale image to 142px double scalefactor = 142 / sourceImage.getHeight(); // calculate scaling factor int nWidth = (int) Math.rint(scalefactor * sourceImage.getWidth()); int nHeight = (int) Math.rint(scalefactor * sourceImage.getHeight()); Image scaledImage = new Image(url.toURI().toString(), nWidth, nHeight, false, true); // generate a scaled image smmdbImageView.setFitWidth(scaledImage.getWidth()); // set ImageView width to the image width smmdbImageView.setImage(scaledImage); // set imageview to image } catch (MalformedURLException | URISyntaxException e) { LOGGER.warn("There was either a problem or no image!", e); smmdbImageView.setImage(new Image("icons/close_black_2048x2048.png")); } addCourseDescription(courses.get(i)); } } } }); helpLbl.setOnMouseClicked(event -> { if (event.getButton().equals(MouseButton.PRIMARY)) { Main.getMain().getHostServices().showDocument("https://git.mosad.xyz/Seil0/cemu_UI/issues/3"); } }); languageChoisBox.getSelectionModel().selectedIndexProperty().addListener((event, oldValue, newValue) -> { String language = languageChoisBox.getItems().get((int) newValue).toString(); language = language.substring(language.length() - 6, language.length() - 1); // reading only en_US from // English (en_US) XMLController.setUsrLocal(language); setUILanguage(); xmlController.saveSettings(); }); branchChoisBox.getSelectionModel().selectedIndexProperty().addListener((e, oldValue, newValue) -> { if (branchChoisBox.getItems().get((int) newValue).toString() == "beta") { XMLController.setUseBeta(true); } else { XMLController.setUseBeta(false); } xmlController.saveSettings(); }); licensesLbl.setOnMouseClicked(event -> { if (event.getButton().equals(MouseButton.PRIMARY)) { EventHandler cancelAction = cE -> { String textAreaText = ""; try { BufferedReader licenseBR = new BufferedReader( new InputStreamReader(getClass().getResourceAsStream("/licenses/licenses_show.txt"))); String currentLine; while ((currentLine = licenseBR.readLine()) != null) { textAreaText = textAreaText + currentLine + "\n"; } licenseBR.close(); } catch (IOException ex) { LOGGER.error("Cloud not read the license file!", ex); } JFXTextAreaAlert licenseAlert = new JFXTextAreaAlert("cemu_UI", textAreaText, btnStyle, primaryStage); licenseAlert.showAndWait(); }; JFXOkayCancelAlert licenseOverviewAlert = new JFXOkayCancelAlert( XMLController.getLocalBundle().getString("licensesLblHeadingText"), XMLController.getLocalBundle().getString("licensesLblBodyText"), btnStyle, primaryStage); licenseOverviewAlert.setOkayAction(oE -> {}); licenseOverviewAlert.setCancelAction(cancelAction); licenseOverviewAlert.setCancelText(XMLController.getLocalBundle().getString("showLicenses")); licenseOverviewAlert.showAndWait(); } }); cemuTextField.textProperty().addListener((e, oldValue, newValue) -> { if (new File(newValue).exists()) { XMLController.setCemuPath(newValue); xmlController.saveSettings(); } else { String bodyText = newValue + ": No such file or directory"; JFXInfoAlert fileErrorDialog = new JFXInfoAlert("Waring!", bodyText, btnStyle, primaryStage); fileErrorDialog.showAndWait(); LOGGER.warn(newValue + ": No such file or directory"); } }); romTextField.textProperty().addListener((e, oldValue, newValue) -> { if (new File(newValue).exists()) { XMLController.setRomDirectoryPath(newValue); xmlController.saveSettings(); reloadRoms(); } else { String bodyText = newValue + ": No such file or directory"; JFXInfoAlert fileErrorDialog = new JFXInfoAlert("Waring!", bodyText, btnStyle, primaryStage); fileErrorDialog.showAndWait(); LOGGER.warn(newValue + ": No such file or directory"); } }); LOGGER.info("initializing Actions done!"); } @FXML private void detailsSlideoutBtnAction() { playBtnSlideOut(); } @FXML private void aboutBtnAction() { String bodyText = "cemu_UI by @Seil0 \nVersion: " + version + " (" + buildNumber + ") \"" + versionName + "\" \n" + XMLController.getLocalBundle().getString("aboutBtnBodyText"); JFXInfoAlert infoAlert = new JFXInfoAlert(XMLController.getLocalBundle().getString("aboutBtnHeadingText"), bodyText, btnStyle, primaryStage); infoAlert.showAndWait(); } @FXML private void settingsBtnAction() { if (smmdbAnchorPane.isVisible()) { smmdbAnchorPane.setVisible(false); } settingsScrollPane.setVisible(!settingsScrollPane.isVisible()); xmlController.saveSettings(); // saving settings to be sureMouseEvent.MOUSE_CLICKED, (event) -> { } @FXML private void reloadRomsBtnAction() throws IOException { reloadRoms(); } @FXML private void smmdbBtnAction() { // show smmdbAnchorPane smmdbAnchorPane.setVisible(!smmdbAnchorPane.isVisible()); if (smmdbAnchorPane.isVisible()) { // start query in new thread Thread thread = new Thread(new Runnable() { @Override public void run() { Platform.runLater(() -> { smmdbDownloadBtn.setText(XMLController.getLocalBundle().getString("smmdbDownloadBtnLoading")); smmdbDownloadBtn.setDisable(true); root.getChildren().remove(0,root.getChildren().size()); }); courses.removeAll(courses); // remove existing courses courses.addAll(smmdbAPIController.startQuery()); // start query // add query response to courseTreeTable for (int i = 0; i < courses.size(); i++) { CourseTableDataType helpCourse = new CourseTableDataType(courses.get(i).getTitle(), courses.get(i).getId(), courses.get(i).getTime(), courses.get(i).getStars()); Platform.runLater(() -> { root.getChildren().add(new TreeItem(helpCourse)); // add data to root-node smmdbDownloadBtn.setText(XMLController.getLocalBundle().getString("smmdbDownloadBtnDownload")); smmdbDownloadBtn.setDisable(false); }); } } }); thread.start(); } } @FXML private void playBtnAction() throws InterruptedException, IOException { dbController.setLastPlayed(selectedGameTitleID); playGame = new playGame(this, dbController); playGame.start(); } @FXML private void totalPlaytimeBtnAction() { } @FXML private void lastTimePlayedBtnAction() { } @FXML private void cemuTFBtnAction() { File cemuDirectory = directoryChooser.showDialog(primaryStage); if (cemuDirectory != null) { cemuTextField.setText(cemuDirectory.getAbsolutePath()); } } @FXML private void romTFBtnAction() { File romDirectory = directoryChooser.showDialog(primaryStage); if (romDirectory != null) { romTextField.setText(romDirectory.getAbsolutePath()); } } @FXML private void updateBtnAction() { UpdateController updateController = new UpdateController(this, buildNumber, XMLController.isUseBeta()); Thread updateThread = new Thread(updateController); updateThread.setName("Updater"); updateThread.start(); } @FXML private void autoUpdateToggleBtnAction() { XMLController.setAutoUpdate(!XMLController.isAutoUpdate()); xmlController.saveSettings(); } @FXML private void courseSearchTextFiledAction() { // not in use } @FXML private void smmdbDownloadBtnAction() { String downloadUrl = "http://smmdb.ddns.net/api/downloadcourse?id=" + id + "&type=zip"; String downloadFileURL = XMLController.getCemuPath() + "/" + id + ".zip"; // getCemuPath() + "/" + smmID + "/" + id + ".rar" String outputFile = XMLController.getCemuPath() + "/"; try { LOGGER.info("beginning download ..."); HttpURLConnection conn = (HttpURLConnection) new URL(downloadUrl).openConnection(); ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(null, "Downloading...", conn.getInputStream()); ProgressMonitor pm = pmis.getProgressMonitor(); pm.setMillisToDecideToPopup(0); pm.setMillisToPopup(0); pm.setMinimum(0); // tell the progress bar that we start at the beginning of the stream pm.setMaximum(conn.getContentLength()); // tell the progress bar the total number of bytes we are going to read. FileUtils.copyInputStreamToFile(pmis, new File(downloadFileURL)); // download file + "/mlc01/emulatorSave" pmis.close(); LOGGER.info("downloaded successfull"); File downloadFile = new File(downloadFileURL); String source = downloadFileURL; String destination = null; int highestCourseNumber = 0; String courseName = null; for (int i = 0; i < smmIDs.size(); i++) { File smmDirectory = new File(outputFile + "mlc01/emulatorSave/" + smmIDs.get(i)); if (smmDirectory.exists()) { LOGGER.info("found smm directory: " + smmDirectory.getAbsolutePath()); File[] courses = smmDirectory.listFiles(File::isDirectory); // get all existing courses in smm directory, new name is highest number +1 for (int j = 0; j < courses.length; j++) { int courseNumber = Integer.parseInt(courses[j].getName().substring(6)); if (courseNumber > highestCourseNumber) { highestCourseNumber = courseNumber; } } String number = "000" + (highestCourseNumber +1); courseName = "course" + number.substring(number.length() -3, number.length()); File courseDirectory = new File(outputFile + "mlc01/emulatorSave/" + smmIDs.get(i) + "/"); destination = courseDirectory.getPath(); } } if (destination != null) { try { ZipFile zipFile = new ZipFile(source); zipFile.extractAll(destination); // rename zip-file File course = new File(destination + "/course000"); course.renameTo( new File(destination + "/" + courseName)); LOGGER.info("Added new course: " + courseName + ", full path is: " + destination + "/" + courseName); } catch (ZipException e) { LOGGER.error("an error occurred during unziping the file!", e); } } downloadFile.delete(); } catch (IOException e) { LOGGER.error("something went wrong during downloading the course", e); } } @FXML private void fullscreenToggleBtnAction() { XMLController.setFullscreen(!XMLController.isFullscreen()); xmlController.saveSettings(); } @FXML private void cloudSyncToggleBtnAction() { XMLController.setCloudSync(!XMLController.isCloudSync()); if (XMLController.isCloudSync()) { EventHandler okayAction = e -> { // TODO rework for other cloud services // CloudService service = CloudService.GoogleDrive; XMLController.setCloudService(CloudService.GoogleDrive); // start cloud sync in new thread Runnable task = () -> { if (CloudController.getInstance(MWC).initializeConnection(XMLController.getCloudService(), XMLController.getCemuPath())) { CloudController.getInstance(MWC).sync(XMLController.getCloudService(), XMLController.getCemuPath(), XMLController.getDirCemuUIPath()); xmlController.saveSettings(); } else { cloudSyncToggleBtn.setSelected(false); // cloud sync init error dialog JFXInfoAlert cloudSyncErrorDialog = new JFXInfoAlert( XMLController.getLocalBundle().getString("cloudSyncErrorHeadingText"), XMLController.getLocalBundle().getString("cloudSyncErrorBodyText"), btnStyle, primaryStage); cloudSyncErrorDialog.showAndWait(); } }; new Thread(task).start(); }; JFXOkayCancelAlert cloudSyncWarningAlert = new JFXOkayCancelAlert( XMLController.getLocalBundle().getString("cloudSyncWaringHeadingText"), XMLController.getLocalBundle().getString("cloudSyncWaringHeadingText"), btnStyle, primaryStage); cloudSyncWarningAlert.setOkayAction(okayAction); cloudSyncWarningAlert.setCancelAction(e -> cloudSyncToggleBtn.setSelected(false)); cloudSyncWarningAlert.showAndWait(); } } @FXML private void colorPickerAction() { editColor(colorPicker.getValue().toString()); applyColor(); } @FXML private void addBtnAction() { JFXEditGameAlert addGameAlert = new JFXEditGameAlert( XMLController.getLocalBundle().getString("addGameBtnHeadingText"), XMLController.getLocalBundle().getString("addGameBtnBodyText"), btnStyle, 0, primaryStage, MWC); addGameAlert.showAndWait(); } /** * process the returning data from the addGame dialog * and add them to the database and the UI */ public void addBtnReturn(String title, String coverPath, String romPath, String titleID) { /** * if one parameter dosen't contain any value do not add the game * else convert the cover to .png add copy it into the picture cache, * then add the rom to the local_roms database */ if (romPath.length() == 0 || coverPath.length() == 0 || title.length() == 0 || titleID.length() == 0) { LOGGER.info("No parameter set!"); // addGame error dialog JFXInfoAlert errorAlert = new JFXInfoAlert( XMLController.getLocalBundle().getString("addBtnReturnErrorHeadingText"), XMLController.getLocalBundle().getString("addBtnReturnErrorBodyText"), btnStyle, primaryStage); errorAlert.showAndWait(); } else { File pictureCache = XMLController.getPictureCache(); String coverName = new File(coverPath).getName(); try { BufferedImage originalImage = ImageIO.read(new File(coverPath)); //load cover int type = originalImage.getType() == 0 ? BufferedImage.TYPE_INT_ARGB : originalImage.getType(); BufferedImage resizeImagePNG = resizeImage(originalImage, type, 400, 600); coverPath = pictureCache + "/" + coverName; ImageIO.write(resizeImagePNG, "png", new File(coverPath)); //save image to pictureCache } catch (IOException e) { LOGGER.error("Ops something went wrong! Error while resizing cover.", e); } try { dbController.addGame(title, coverPath, romPath, titleID, "", "", "", "0"); games.add(dbController.loadSingleGame(titleID)); addGame(games.stream().filter(x -> x.getRomPath().equals(romPath)).findFirst().orElse(null)); if (menuTrue) { sideMenuSlideOut(); burgerTask.setRate(-1.0); burgerTask.play(); menuTrue = false; } refreshUIData(); } catch (SQLException e) { LOGGER.error("Oops, something went wrong! Error while adding a game.", e); } } } public void editBtnReturn(String title, String coverPath, String romPath, String titleID) { dbController.setGameInfo(title, coverPath, romPath, titleID); games.remove(selectedUIDataIndex); games.add(dbController.loadSingleGame(titleID)); addGame(games.stream().filter(x -> x.getRomPath().equals(romPath)).findFirst().orElse(null)); refreshUIData(); LOGGER.info("successfully edited " + titleID + ", new name is \"" + title + "\""); } /** * add the context menu and button action for all games */ private void addAllGames() { for (UIROMDataType game : games) { addGame(game); } refreshUIData(); // refresh the list of games displayed on screen } /** * add the context menu and button action for one game * @param game a game */ private void addGame(UIROMDataType game) { game.getButton().setContextMenu(gameContextMenu); game.getButton().addEventHandler(MouseEvent.MOUSE_CLICKED, (event) -> { LOGGER.info("selected: " + game.getLabel().getText() + "; ID: " + game.getTitleID()); // getting the selected game index by comparing event.getSource() with games.get(i).getButton() for (int i = 0; i < games.size(); i++) { if (games.get(i).getButton() == event.getSource()) { selectedUIDataIndex = i; } } gameExecutePath = game.getRomPath(); selectedGameTitleID = game.getTitleID(); selectedGameTitle = game.getName(); // underlining selected Label lastGameLabel.setStyle("-fx-underline: false;"); games.get(selectedUIDataIndex).getLabel().setStyle("-fx-underline: true;"); lastGameLabel = games.get(selectedUIDataIndex).getLabel(); // setting last played, if lastPlayed is empty game was never played before, else set correct date if (dbController.getLastPlayed(game.getTitleID()).equals("") || dbController.getLastPlayed(game.getTitleID()).equals(null)) { lastTimePlayedBtn.setText(lastPlayed + never); totalPlaytimeBtn.setText(dbController.getTotalPlaytime(game.getTitleID()) + " min"); } else { DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); int tToday = Integer.parseInt(dtf.format(LocalDate.now()).replaceAll("-", "")); int tYesterday = Integer.parseInt(dtf.format(LocalDate.now().minusDays(1)).replaceAll("-", "")); int tLastPlayedDay = Integer.parseInt(dbController.getLastPlayed(game.getTitleID()).replaceAll("-", "")); if (tLastPlayedDay == tToday) { lastTimePlayedBtn.setText(lastPlayed + today); } else if (tLastPlayedDay == tYesterday) { lastTimePlayedBtn.setText(lastPlayed + yesterday); } else { lastTimePlayedBtn.setText(lastPlayed + dbController.getLastPlayed(game.getTitleID())); } } /** * setting total playtime, if total playtime > 60 minutes, format is "x hours x * minutes" (x h x min), else only minutes are showed */ if (Integer.parseInt(dbController.getTotalPlaytime(game.getTitleID())) > 60) { int hoursPlayed = (int) Math.floor(Integer.parseInt(dbController.getTotalPlaytime(game.getTitleID())) / 60); int minutesPlayed = Integer.parseInt(dbController.getTotalPlaytime(game.getTitleID())) - 60 * hoursPlayed; totalPlaytimeBtn.setText(hoursPlayed + " h " + minutesPlayed + " min"); } else { totalPlaytimeBtn.setText(dbController.getTotalPlaytime(game.getTitleID()) + " min"); } if (!playTrue) { playBtnSlideIn(); } if (menuTrue) { sideMenuSlideOut(); burgerTask.setRate(-1.0); burgerTask.play(); menuTrue = false; } }); } /** * reload all ROMs from the ROM directory */ public void reloadRoms() { JFXSpinner spinner = new JFXSpinner(); spinner.setPrefSize(30, 30); AnchorPane.setTopAnchor(spinner, (mainAnchorPane.getPrefHeight() - spinner.getPrefHeight()) / 2); AnchorPane.setLeftAnchor(spinner, (mainAnchorPane.getPrefWidth() - spinner.getPrefWidth()) / 2); Runnable task = () -> { Platform.runLater(() -> { gamesFlowPane.getChildren().clear(); //remove all games form gamesFlowPane mainAnchorPane.getChildren().add(spinner); // add spinner to mainAnchorPane }); dbController.loadRomDirectory(XMLController.getRomDirectoryPath()); // reload the ROM directory games.clear(); // remove all games from the mwc game list games = dbController.loadAllGames(); addAllGames(); Platform.runLater(() -> { mainAnchorPane.getChildren().remove(spinner); }); }; new Thread(task).start(); } // remove all games from gamesFlowPane and add them afterwards public void refreshUIData() { gamesFlowPane.getChildren().clear(); gamesFlowPane.getChildren().addAll(games); } // set the selected local strings to all needed elements void setUILanguage(){ switch (XMLController.getUsrLocal()) { case "en_US": XMLController.setLocalBundle(ResourceBundle.getBundle("locals.cemu_UI-Local", Locale.US)); // us_English languageChoisBox.getSelectionModel().select(0); break; case "de_DE": XMLController.setLocalBundle(ResourceBundle.getBundle("locals.cemu_UI-Local", Locale.GERMAN)); // German languageChoisBox.getSelectionModel().select(1); break; default: XMLController.setLocalBundle(ResourceBundle.getBundle("locals.cemu_UI-Local", Locale.US)); // default local languageChoisBox.getSelectionModel().select(0); break; } // Buttons aboutBtn.setText(XMLController.getLocalBundle().getString("aboutBtn")); settingsBtn.setText(XMLController.getLocalBundle().getString("settingsBtn")); addBtn.setText(XMLController.getLocalBundle().getString("addBtn")); reloadRomsBtn.setText(XMLController.getLocalBundle().getString("reloadRomsBtn")); smmdbBtn.setText(XMLController.getLocalBundle().getString("smmdbBtn")); cemuTFBtn.setText(XMLController.getLocalBundle().getString("cemuTFBtn")); romTFBtn.setText(XMLController.getLocalBundle().getString("romTFBtn")); updateBtn.setText(XMLController.getLocalBundle().getString("updateBtnCheckNow")); smmdbDownloadBtn.setText(XMLController.getLocalBundle().getString("smmdbDownloadBtn")); playBtn.setText(XMLController.getLocalBundle().getString("playBtn")); cloudSyncToggleBtn.setText(XMLController.getLocalBundle().getString("cloudSyncToggleBtn")); autoUpdateToggleBtn.setText(XMLController.getLocalBundle().getString("autoUpdateToggleBtn")); fullscreenToggleBtn.setText(XMLController.getLocalBundle().getString("fullscreenToggleBtn")); // Labels cemu_UISettingsLbl.setText(XMLController.getLocalBundle().getString("cemu_UISettingsLbl")); cemuDirectoryLbl.setText(XMLController.getLocalBundle().getString("cemuDirectoryLbl")); romDirectoryLbl.setText(XMLController.getLocalBundle().getString("romDirectoryLbl")); mainColorLbl.setText(XMLController.getLocalBundle().getString("mainColorLbl")); languageLbl.setText(XMLController.getLocalBundle().getString("languageLbl")); updateLbl.setText(XMLController.getLocalBundle().getString("updateLbl")); branchLbl.setText(XMLController.getLocalBundle().getString("branchLbl")); cemuSettingsLbl.setText(XMLController.getLocalBundle().getString("cemuSettingsLbl")); licensesLbl.setText(XMLController.getLocalBundle().getString("licensesLbl")); // Columns titleColumn.setText(XMLController.getLocalBundle().getString("titleColumn")); idColumn.setText(XMLController.getLocalBundle().getString("idColumn")); starsColumn.setText(XMLController.getLocalBundle().getString("starsColumn")); timeColumn.setText(XMLController.getLocalBundle().getString("timeColumn")); // Strings lastPlayed = XMLController.getLocalBundle().getString("lastPlayed"); today = XMLController.getLocalBundle().getString("today"); yesterday = XMLController.getLocalBundle().getString("yesterday"); never = XMLController.getLocalBundle().getString("never"); } // if AutoUpdate, then check for updates private void checkAutoUpdate() { if (XMLController.isAutoUpdate()) { try { LOGGER.info("AutoUpdate: looking for updates on startup ..."); UpdateController updateController = new UpdateController(this, buildNumber, XMLController.isUseBeta()); Thread updateThread = new Thread(updateController); updateThread.setName("Updater"); updateThread.start(); updateThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void addCourseDescription(SmmdbApiDataType course) { String courseTheme; String gameStyle; String difficulty; String autoscroll; smmdbTextFlow.getChildren().remove(0, smmdbTextFlow.getChildren().size()); nameText.clear(); courseText.clear(); switch (course.getCourseTheme()) { case 0: courseTheme = "Ground"; break; case 1: courseTheme = "Underground"; break; case 2: courseTheme = "Castle"; break; case 3: courseTheme = "Airship"; break; case 4: courseTheme = "Underwater"; break; case 5: courseTheme = "Ghost House"; break; default: courseTheme = "notset"; break; } switch (course.getGameStyle()) { case 0: gameStyle = "SMB"; break; case 1: gameStyle = "SMB3"; break; case 2: gameStyle = "SMW"; break; case 3: gameStyle = "NSMBU"; break; default: gameStyle = "notset"; break; } switch (course.getDifficulty()) { case 0: difficulty = "Easy"; break; case 1: difficulty = "Normal"; break; case 2: difficulty = "Expert"; break; case 3: difficulty = "Super Expert"; break; case 4: difficulty = "Mixed"; break; default: difficulty = "notset"; break; } switch (course.getAutoScroll()) { case 0: autoscroll = "disabled"; break; case 1: autoscroll = "slow"; break; case 2: autoscroll = "medium"; break; case 3: autoscroll = "fast"; break; default: autoscroll = "notset"; break; } nameText.add(0, new Text("title" + ": ")); nameText.add(1, new Text("owner" + ": ")); nameText.add(2, new Text("Course-Theme" + ": ")); nameText.add(3, new Text("Game-Style" + ": ")); nameText.add(4, new Text("difficulty" + ": ")); nameText.add(5, new Text("Auto-Scroll" + ": ")); nameText.add(6, new Text("Time" + ": ")); nameText.add(7, new Text("lastmodified" + ": ")); nameText.add(8, new Text("uploaded" + ": ")); nameText.add(9, new Text("nintendoid" + ": ")); courseText.add(0, new Text(course.getTitle() + "\n")); courseText.add(1, new Text(course.getOwner() + "\n")); courseText.add(2, new Text(courseTheme + "\n")); courseText.add(3, new Text(gameStyle + "\n")); courseText.add(4, new Text(difficulty + "\n")); courseText.add(5, new Text(autoscroll + "\n")); courseText.add(6, new Text(course.getTime() + "\n")); courseText.add(7, new Text(new java.util.Date((long) course.getLastmodified() * 1000) + "\n")); courseText.add(8, new Text(new java.util.Date((long) course.getUploaded() * 1000) + "\n")); courseText.add(9, new Text(course.getNintendoid() + "\n")); for (int i = 0; i < nameText.size(); i++) { nameText.get(i).setFont(Font.font("System", FontWeight.BOLD, 14)); courseText.get(i).setFont(Font.font("System", 14)); smmdbTextFlow.getChildren().addAll(nameText.get(i), courseText.get(i)); } } // change the color of all needed GUI elements private void applyColor() { String boxStyle = "-fx-background-color: #" + XMLController.getColor() + ";"; String textFill = ""; BigInteger icolor = new BigInteger(XMLController.getColor(),16); BigInteger ccolor = new BigInteger("78909cff",16); sideMenuVBox.setStyle(boxStyle); topHBox.setStyle(boxStyle); cemuTextField.setFocusColor(Color.valueOf(XMLController.getColor())); romTextField.setFocusColor(Color.valueOf(XMLController.getColor())); if (icolor.compareTo(ccolor) == -1) { textFill = "-fx-text-fill: WHITE;"; aboutBtn.setGraphic(new ImageView(new Image("icons/ic_info_white_24dp_1x.png"))); settingsBtn.setGraphic(new ImageView(new Image("icons/ic_settings_white_24dp_1x.png"))); addBtn.setGraphic(new ImageView(new Image("icons/ic_add_circle_white_24dp_1x.png"))); reloadRomsBtn.setGraphic(new ImageView(new Image("icons/ic_cached_white_24dp_1x.png"))); smmdbBtn.setGraphic(new ImageView(new Image("icons/ic_get_app_white_24dp_1x.png"))); menuHam.getStyleClass().clear(); menuHam.getStyleClass().add("jfx-hamburgerW"); } else { textFill = "-fx-text-fill: BLACK;"; aboutBtn.setGraphic(new ImageView(new Image("icons/ic_info_black_24dp_1x.png"))); settingsBtn.setGraphic(new ImageView(new Image("icons/ic_settings_black_24dp_1x.png"))); addBtn.setGraphic(new ImageView(new Image("icons/ic_add_circle_black_24dp_1x.png"))); reloadRomsBtn.setGraphic(new ImageView(new Image("icons/ic_cached_black_24dp_1x.png"))); smmdbBtn.setGraphic(new ImageView(new Image("icons/ic_get_app_black_24dp_1x.png"))); menuHam.getStyleClass().clear(); menuHam.getStyleClass().add("jfx-hamburgerB"); } btnStyle = "-fx-button-type: RAISED; -fx-background-color: #" + XMLController.getColor() + "; " + textFill; aboutBtn.setStyle(textFill); settingsBtn.setStyle(textFill); addBtn.setStyle(textFill); reloadRomsBtn.setStyle(textFill); smmdbBtn.setStyle(textFill); playBtn.setStyle(textFill); cemuTFBtn.setStyle(btnStyle); romTFBtn.setStyle(btnStyle); updateBtn.setStyle(btnStyle); smmdbDownloadBtn.setStyle(btnStyle); playBtn.setStyle(btnStyle); for(UIROMDataType game : games) { game.getButton().setRipplerFill(Paint.valueOf(XMLController.getColor())); } } private void sideMenuSlideIn(){ sideMenuVBox.setVisible(true); //slide in in 400ms TranslateTransition translateTransition = new TranslateTransition(Duration.millis(400), sideMenuVBox); translateTransition.setFromX(-175); translateTransition.setToX(0); translateTransition.play(); } private void sideMenuSlideOut(){ //slide out in 400ms TranslateTransition translateTransition = new TranslateTransition(Duration.millis(400), sideMenuVBox); translateTransition.setFromX(0); translateTransition.setToX(-175); translateTransition.play(); } private void playBtnSlideIn(){ bottomHBox.setVisible(true); playTrue = true; TranslateTransition playBtnTransition = new TranslateTransition(Duration.millis(300), bottomHBox); playBtnTransition.setFromY(56); playBtnTransition.setToY(0); playBtnTransition.play(); } private void playBtnSlideOut(){ playTrue = false; TranslateTransition playBtnTransition = new TranslateTransition(Duration.millis(300), bottomHBox); playBtnTransition.setFromY(0); playBtnTransition.setToY(56); playBtnTransition.play(); } private void editColor(String input){ StringBuilder sb = new StringBuilder(input); sb.delete(0, 2); XMLController.setColor(sb.toString()); xmlController.saveSettings(); } @SuppressWarnings("unused") /** * @return the main color in hexadecimal format */ private String hexToRgb() { LOGGER.info(XMLController.getColor()); int hex = Integer.parseInt(XMLController.getColor().substring(0, 5), 16); int r = (hex & 0xFF0000) >> 16; int g = (hex & 0xFF00) >> 8; int b = (hex & 0xFF); return r + ", " + g + ", " + b; } /** * * @param originalImage original image which size is changed * @param type type of the original image (PNG,JPEG,...) * @param imgWidth wanted width * @param imgHeigth wanted height * @return the rezised image */ private static BufferedImage resizeImage(BufferedImage originalImage, int type, int imgWidth, int imgHeigth) { BufferedImage resizedImage = new BufferedImage(imgWidth, imgHeigth, type); Graphics2D g = resizedImage.createGraphics(); g.drawImage(originalImage, 0, 0, imgWidth, imgHeigth, null); g.dispose(); return resizedImage; } public Stage getPrimaryStage() { return primaryStage; } public String getGameExecutePath() { return gameExecutePath; } public void setGameExecutePath(String gameExecutePath) { this.gameExecutePath = gameExecutePath; } public String getSelectedGameTitleID() { return selectedGameTitleID; } public void setSelectedGameTitleID(String selectedGameTitleID) { this.selectedGameTitleID = selectedGameTitleID; } public JFXButton getPlayBtn() { return playBtn; } public void setPlayBtn(JFXButton playBtn) { this.playBtn = playBtn; } public AnchorPane getMainAnchorPane() { return mainAnchorPane; } public JFXButton getUpdateBtn() { return updateBtn; } }