commit
						3565cd0fc0
					
				
					 25 changed files with 2020 additions and 587 deletions
				
			
		
							
								
								
									
										27
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								README.md
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1,12 +1,14 @@
 | 
			
		|||
# NS-USBloader
 | 
			
		||||
 | 
			
		||||
NS-USBloader is a PC-side tinfoil NSP USB uploader. Replacement for default usb_install_pc.py
 | 
			
		||||
NS-USBloader is a PC-side TinFoil/GoldLeaf NSP USB uploader. Replacement for default usb_install_pc.py and GoldTree.
 | 
			
		||||
 | 
			
		||||
With GUI and cookies.
 | 
			
		||||
 | 
			
		||||
Read more: https://developersu.blogspot.com/2019/02/ns-usbloader-en.html
 | 
			
		||||
 | 
			
		||||
Here is the version of 'not perfect byt anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR).
 | 
			
		||||
Here is the version of 'not perfect but anyway' [tinfoil I use](https://cloud.mail.ru/public/DwbX/H8d2p3aYR). 
 | 
			
		||||
Ok, I'm almost sure that this version has bugs. I don't remember where I downloaded it. But it works for me somehow.. 
 | 
			
		||||
Let's rephrase, if you have working version of TinFoil DO NOT use this one.  
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +34,7 @@ Install JRE/JDK 8 or higher (openJDK is good. Oracle's one is also good). JavaFX
 | 
			
		|||
 | 
			
		||||
See 'Linux' section.
 | 
			
		||||
 | 
			
		||||
Set 'Security & Privacy' if needed.
 | 
			
		||||
Set 'Security & Privacy' settings if needed.
 | 
			
		||||
 | 
			
		||||
### Windows: 
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +58,22 @@ Set 'Security & Privacy' if needed.
 | 
			
		|||
## Known bugs
 | 
			
		||||
* Unable to interrupt transmission when libusb awaiting for read event (when user sent NSP list but didn't selected anything on NS).
 | 
			
		||||
 | 
			
		||||
## NOTES
 | 
			
		||||
Table 'Status' = 'Uploaded' does not means that file installed. It means that it has been sent to NS without any issues! That's what this app about. 
 | 
			
		||||
Handling successful/failed installation is a purpose of the other side application (TinFoil/GoldLeaf). (And they don't provide any feedback interfaces so I can't detect success/failure.)
 | 
			
		||||
 | 
			
		||||
## TODO:
 | 
			
		||||
- [x] macOS QA by [Konstanin Kelemen](https://github.com/konstantin-kelemen). Appreciate assistance of [Vitaliy Natarov](https://github.com/SebastianUA). 
 | 
			
		||||
- [x] macOS QA v0.1
 | 
			
		||||
- [ ] macOS QA v0.2 (partly)
 | 
			
		||||
- [x] Windows support
 | 
			
		||||
- [ ] code refactoring
 | 
			
		||||
- [ ] code refactoring (almost. todo: printLog() )
 | 
			
		||||
- [x] GoldLeaf support
 | 
			
		||||
- [ ] XCI support
 | 
			
		||||
- [ ] File order sort (non-critical)
 | 
			
		||||
 | 
			
		||||
## Thanks
 | 
			
		||||
Appreciate assistance and support of both Vitaliy and Konstantin. Without you all this magic would not have happened.
 | 
			
		||||
 | 
			
		||||
[Konstanin Kelemen](https://github.com/konstantin-kelemen)
 | 
			
		||||
 
 | 
			
		||||
[Vitaliy Natarov](https://github.com/SebastianUA) 
 | 
			
		||||
							
								
								
									
										42
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								pom.xml
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -5,12 +5,50 @@
 | 
			
		|||
    <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
 | 
			
		||||
    <groupId>loper</groupId>
 | 
			
		||||
    <artifactId>NS-USBloader</artifactId>
 | 
			
		||||
    <version>0.1-SNAPSHOT</version>
 | 
			
		||||
    <name>NS-USBloader</name>
 | 
			
		||||
 | 
			
		||||
    <artifactId>ns-usbloader</artifactId>
 | 
			
		||||
    <version>0.2-SNAPSHOT</version>
 | 
			
		||||
 | 
			
		||||
    <url>https://github.com/developersu/ns-usbloader/</url>
 | 
			
		||||
    <description>
 | 
			
		||||
        NSP USB loader for TinFoil and GoldLeaf
 | 
			
		||||
    </description>
 | 
			
		||||
    <inceptionYear>2019</inceptionYear>
 | 
			
		||||
    <organization>
 | 
			
		||||
        <name>Dmitry Isaenko</name>
 | 
			
		||||
        <url>https://developersu.blogspot.com/</url>
 | 
			
		||||
    </organization>
 | 
			
		||||
 | 
			
		||||
    <licenses>
 | 
			
		||||
        <license>
 | 
			
		||||
            <name>GPLv3</name>
 | 
			
		||||
            <url>LICENSE</url>
 | 
			
		||||
            <distribution>manual</distribution>
 | 
			
		||||
        </license>
 | 
			
		||||
    </licenses>
 | 
			
		||||
 | 
			
		||||
    <developers>
 | 
			
		||||
        <developer>
 | 
			
		||||
            <id>developer.su</id>
 | 
			
		||||
            <name>Dmitry Isaenko</name>
 | 
			
		||||
            <roles>
 | 
			
		||||
                <role>Developer</role>
 | 
			
		||||
            </roles>
 | 
			
		||||
            <timezone>+3</timezone>
 | 
			
		||||
            <url>https://developersu.blogspot.com/</url>
 | 
			
		||||
        </developer>
 | 
			
		||||
    </developers>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <issueManagement>
 | 
			
		||||
        <system>GitHub</system>
 | 
			
		||||
        <url>https://github.com/developer_su/${project.artifactId}/issues</url>
 | 
			
		||||
    </issueManagement>
 | 
			
		||||
 | 
			
		||||
    <dependencies>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>org.openjfx</groupId>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										23
									
								
								src/main/java/nsusbloader/AppPreferences.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/main/java/nsusbloader/AppPreferences.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
package nsusbloader;
 | 
			
		||||
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
public class AppPreferences {
 | 
			
		||||
    private static final AppPreferences INSTANCE = new AppPreferences();
 | 
			
		||||
    public static AppPreferences getInstance() { return INSTANCE; }
 | 
			
		||||
 | 
			
		||||
    private Preferences preferences;
 | 
			
		||||
 | 
			
		||||
    private AppPreferences(){ preferences = Preferences.userRoot().node("NS-USBloader"); }
 | 
			
		||||
 | 
			
		||||
    public String getTheme(){
 | 
			
		||||
        String theme = preferences.get("THEME", "/res/app_dark.css");           // Don't let user to change settings manually
 | 
			
		||||
        if (!theme.matches("(^/res/app_dark.css$)|(^/res/app_light.css$)"))
 | 
			
		||||
            theme = "/res/app_dark.css";
 | 
			
		||||
        return theme;
 | 
			
		||||
    }
 | 
			
		||||
    public void setTheme(String theme){ preferences.put("THEME", theme); }
 | 
			
		||||
 | 
			
		||||
    public String getRecent(){ return preferences.get("RECENT", System.getProperty("user.home")); }
 | 
			
		||||
    public void setRecent(String path){ preferences.put("RECENT", path); }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										195
									
								
								src/main/java/nsusbloader/Controllers/NSLMainController.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								src/main/java/nsusbloader/Controllers/NSLMainController.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,195 @@
 | 
			
		|||
package nsusbloader.Controllers;
 | 
			
		||||
 | 
			
		||||
import javafx.collections.FXCollections;
 | 
			
		||||
import javafx.collections.ObservableList;
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.fxml.Initializable;
 | 
			
		||||
import javafx.scene.control.*;
 | 
			
		||||
import javafx.scene.layout.Pane;
 | 
			
		||||
import javafx.scene.layout.Region;
 | 
			
		||||
import javafx.stage.FileChooser;
 | 
			
		||||
import nsusbloader.AppPreferences;
 | 
			
		||||
import nsusbloader.MediatorControl;
 | 
			
		||||
import nsusbloader.NSLMain;
 | 
			
		||||
import nsusbloader.UsbCommunications;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.ResourceBundle;
 | 
			
		||||
 | 
			
		||||
public class NSLMainController implements Initializable {
 | 
			
		||||
 | 
			
		||||
    private ResourceBundle resourceBundle;
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public TextArea logArea;            // Accessible from Mediator
 | 
			
		||||
    @FXML
 | 
			
		||||
    private Button selectNspBtn;
 | 
			
		||||
    @FXML
 | 
			
		||||
    private Button uploadStopBtn;
 | 
			
		||||
    private Region btnUpStopImage;
 | 
			
		||||
    @FXML
 | 
			
		||||
    public ProgressBar progressBar;            // Accessible from Mediator
 | 
			
		||||
    @FXML
 | 
			
		||||
    private ChoiceBox<String> choiceProtocol;
 | 
			
		||||
    @FXML
 | 
			
		||||
    private Button switchThemeBtn;
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    private Pane specialPane;
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    public NSTableViewController tableFilesListController;            // Accessible from Mediator
 | 
			
		||||
 | 
			
		||||
    private Thread usbThread;
 | 
			
		||||
 | 
			
		||||
    private String previouslyOpenedPath;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(URL url, ResourceBundle rb) {
 | 
			
		||||
        this.resourceBundle = rb;
 | 
			
		||||
        logArea.setText(rb.getString("logsGreetingsMessage")+" "+ NSLMain.appVersion+"!\n");
 | 
			
		||||
        if (System.getProperty("os.name").toLowerCase().startsWith("lin"))
 | 
			
		||||
            if (!System.getProperty("user.name").equals("root"))
 | 
			
		||||
                logArea.appendText(rb.getString("logsEnteredAsMsg1")+System.getProperty("user.name")+"\n"+rb.getString("logsEnteredAsMsg2") + "\n");
 | 
			
		||||
 | 
			
		||||
        logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
 | 
			
		||||
 | 
			
		||||
        MediatorControl.getInstance().setController(this);
 | 
			
		||||
 | 
			
		||||
        specialPane.getStyleClass().add("special-pane-as-border");  // UI hacks
 | 
			
		||||
 | 
			
		||||
        uploadStopBtn.setDisable(true);
 | 
			
		||||
        selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
 | 
			
		||||
        uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
 | 
			
		||||
 | 
			
		||||
        selectNspBtn.getStyleClass().add("buttonSelect");
 | 
			
		||||
 | 
			
		||||
        this.btnUpStopImage = new Region();
 | 
			
		||||
        btnUpStopImage.getStyleClass().add("regionUpload");
 | 
			
		||||
        //uploadStopBtn.getStyleClass().remove("button");
 | 
			
		||||
        uploadStopBtn.getStyleClass().add("buttonUp");
 | 
			
		||||
        uploadStopBtn.setGraphic(btnUpStopImage);
 | 
			
		||||
 | 
			
		||||
        ObservableList<String> choiceProtocolList = FXCollections.observableArrayList("TinFoil", "GoldLeaf");
 | 
			
		||||
        choiceProtocol.setItems(choiceProtocolList);
 | 
			
		||||
        choiceProtocol.getSelectionModel().select(0);                               // TODO: shared settings
 | 
			
		||||
        choiceProtocol.setOnAction(e->tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem()));  // Add listener to notify tableView controller
 | 
			
		||||
        tableFilesListController.setNewProtocol(choiceProtocol.getSelectionModel().getSelectedItem());   // Notify tableView controller
 | 
			
		||||
 | 
			
		||||
        this.previouslyOpenedPath = null;
 | 
			
		||||
 | 
			
		||||
        Region btnSwitchImage = new Region();
 | 
			
		||||
        btnSwitchImage.getStyleClass().add("regionLamp");
 | 
			
		||||
        switchThemeBtn.setGraphic(btnSwitchImage);
 | 
			
		||||
        this.switchThemeBtn.setOnAction(e->switchTheme());
 | 
			
		||||
 | 
			
		||||
        previouslyOpenedPath = AppPreferences.getInstance().getRecent();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Changes UI theme on the go
 | 
			
		||||
     * */
 | 
			
		||||
    private void switchTheme(){
 | 
			
		||||
        if (switchThemeBtn.getScene().getStylesheets().get(0).equals("/res/app_dark.css")) {
 | 
			
		||||
            switchThemeBtn.getScene().getStylesheets().remove("/res/app_dark.css");
 | 
			
		||||
            switchThemeBtn.getScene().getStylesheets().add("/res/app_light.css");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            switchThemeBtn.getScene().getStylesheets().remove("/res/app_light.css");
 | 
			
		||||
            switchThemeBtn.getScene().getStylesheets().add("/res/app_dark.css");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Functionality for selecting NSP button.
 | 
			
		||||
     * Uses setReady and setNotReady to simplify code readability.
 | 
			
		||||
     * */
 | 
			
		||||
    private void selectFilesBtnAction(){
 | 
			
		||||
        List<File> filesList;
 | 
			
		||||
        FileChooser fileChooser = new FileChooser();
 | 
			
		||||
        fileChooser.setTitle(resourceBundle.getString("btnFileOpen"));
 | 
			
		||||
 | 
			
		||||
        File validator = new File(previouslyOpenedPath);
 | 
			
		||||
        if (validator.exists())
 | 
			
		||||
            fileChooser.setInitialDirectory(validator);         // TODO: read from prefs
 | 
			
		||||
        else
 | 
			
		||||
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));         // TODO: read from prefs
 | 
			
		||||
 | 
			
		||||
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NSP ROM", "*.nsp"));
 | 
			
		||||
 | 
			
		||||
        filesList = fileChooser.showOpenMultipleDialog(logArea.getScene().getWindow());
 | 
			
		||||
        if (filesList != null && !filesList.isEmpty()) {
 | 
			
		||||
            tableFilesListController.setFiles(filesList);
 | 
			
		||||
            uploadStopBtn.setDisable(false);
 | 
			
		||||
            previouslyOpenedPath = filesList.get(0).getParent();
 | 
			
		||||
        }
 | 
			
		||||
        else{
 | 
			
		||||
            tableFilesListController.setFiles(null);
 | 
			
		||||
            uploadStopBtn.setDisable(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * It's button listener when no transmission executes
 | 
			
		||||
     * */
 | 
			
		||||
    private void uploadBtnAction(){
 | 
			
		||||
        if (usbThread == null || !usbThread.isAlive()){
 | 
			
		||||
            List<File> nspToUpload;
 | 
			
		||||
            if ((nspToUpload = tableFilesListController.getFiles()) == null) {
 | 
			
		||||
                resourceBundle.getString("logsNoFolderFileSelected");
 | 
			
		||||
                return;
 | 
			
		||||
            }else {
 | 
			
		||||
                logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n");
 | 
			
		||||
                for (File item: nspToUpload)
 | 
			
		||||
                    logArea.appendText("  "+item.getAbsolutePath()+"\n");
 | 
			
		||||
            }
 | 
			
		||||
            UsbCommunications usbCommunications = new UsbCommunications(nspToUpload, choiceProtocol.getSelectionModel().getSelectedItem());
 | 
			
		||||
            usbThread = new Thread(usbCommunications);
 | 
			
		||||
            usbThread.start();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * It's button listener when transmission in progress
 | 
			
		||||
     * */
 | 
			
		||||
    private void stopBtnAction(){
 | 
			
		||||
        if (usbThread != null && usbThread.isAlive()){
 | 
			
		||||
            usbThread.interrupt();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission"
 | 
			
		||||
     * Called from mediator
 | 
			
		||||
     * */
 | 
			
		||||
    public void notifyTransmissionStarted(boolean isTransmissionStarted){
 | 
			
		||||
        if (isTransmissionStarted) {
 | 
			
		||||
            selectNspBtn.setDisable(true);
 | 
			
		||||
            uploadStopBtn.setOnAction(e->{ stopBtnAction(); });
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.setText(resourceBundle.getString("btnStop"));
 | 
			
		||||
 | 
			
		||||
            btnUpStopImage.getStyleClass().remove("regionUpload");
 | 
			
		||||
            btnUpStopImage.getStyleClass().add("regionStop");
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.getStyleClass().remove("buttonUp");
 | 
			
		||||
            uploadStopBtn.getStyleClass().add("buttonStop");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            selectNspBtn.setDisable(false);
 | 
			
		||||
            uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.setText(resourceBundle.getString("btnUpload"));
 | 
			
		||||
 | 
			
		||||
            btnUpStopImage.getStyleClass().remove("regionStop");
 | 
			
		||||
            btnUpStopImage.getStyleClass().add("regionUpload");
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.getStyleClass().remove("buttonStop");
 | 
			
		||||
            uploadStopBtn.getStyleClass().add("buttonUp");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Save preferences before exit
 | 
			
		||||
     * */
 | 
			
		||||
    public void exit(){
 | 
			
		||||
        AppPreferences.getInstance().setTheme(switchThemeBtn.getScene().getStylesheets().get(0));
 | 
			
		||||
        AppPreferences.getInstance().setRecent(previouslyOpenedPath);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/main/java/nsusbloader/Controllers/NSLRowModel.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/main/java/nsusbloader/Controllers/NSLRowModel.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
package nsusbloader.Controllers;
 | 
			
		||||
 | 
			
		||||
import nsusbloader.NSLDataTypes.EFileStatus;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
public class NSLRowModel {
 | 
			
		||||
 | 
			
		||||
    private String status;
 | 
			
		||||
    private File nspFile;
 | 
			
		||||
    private String nspFileName;
 | 
			
		||||
    private String nspFileSize;
 | 
			
		||||
    private boolean markForUpload;
 | 
			
		||||
 | 
			
		||||
    NSLRowModel(File nspFile, boolean checkBoxValue){
 | 
			
		||||
        this.nspFile = nspFile;
 | 
			
		||||
        this.markForUpload = checkBoxValue;
 | 
			
		||||
        this.nspFileName = nspFile.getName();
 | 
			
		||||
        this.nspFileSize = String.format("%.2f", nspFile.length()/1024.0/1024.0);
 | 
			
		||||
        this.status = "";
 | 
			
		||||
    }
 | 
			
		||||
    // Model methods start
 | 
			
		||||
    public String getStatus(){
 | 
			
		||||
        return status;
 | 
			
		||||
    }
 | 
			
		||||
    public String getNspFileName(){
 | 
			
		||||
        return nspFileName;
 | 
			
		||||
    }
 | 
			
		||||
    public String getNspFileSize() { return nspFileSize; }
 | 
			
		||||
    public boolean isMarkForUpload() {
 | 
			
		||||
        return markForUpload;
 | 
			
		||||
    }
 | 
			
		||||
    // Model methods end
 | 
			
		||||
 | 
			
		||||
    public void setMarkForUpload(boolean value){
 | 
			
		||||
        markForUpload = value;
 | 
			
		||||
    }
 | 
			
		||||
    public File getNspFile(){ return nspFile; }
 | 
			
		||||
    public void setStatus(EFileStatus status){                               // TODO: Localization
 | 
			
		||||
        switch (status){
 | 
			
		||||
            case UPLOADED:
 | 
			
		||||
                this.status = "Success";
 | 
			
		||||
                markForUpload = false;
 | 
			
		||||
                break;
 | 
			
		||||
            case FAILED:
 | 
			
		||||
                this.status = "Failed";
 | 
			
		||||
                break;
 | 
			
		||||
            case INCORRECT_FILE_FAILED:
 | 
			
		||||
                this.status = "Failed: Incorrect file";
 | 
			
		||||
                markForUpload = false;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										177
									
								
								src/main/java/nsusbloader/Controllers/NSTableViewController.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/main/java/nsusbloader/Controllers/NSTableViewController.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,177 @@
 | 
			
		|||
package nsusbloader.Controllers;
 | 
			
		||||
 | 
			
		||||
import javafx.beans.property.SimpleBooleanProperty;
 | 
			
		||||
import javafx.beans.value.ChangeListener;
 | 
			
		||||
import javafx.beans.value.ObservableValue;
 | 
			
		||||
import javafx.collections.FXCollections;
 | 
			
		||||
import javafx.collections.ObservableList;
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.fxml.Initializable;
 | 
			
		||||
import javafx.scene.control.Label;
 | 
			
		||||
import javafx.scene.control.TableCell;
 | 
			
		||||
import javafx.scene.control.TableColumn;
 | 
			
		||||
import javafx.scene.control.TableView;
 | 
			
		||||
import javafx.scene.control.cell.CheckBoxTableCell;
 | 
			
		||||
import javafx.scene.control.cell.PropertyValueFactory;
 | 
			
		||||
import javafx.util.Callback;
 | 
			
		||||
import nsusbloader.NSLDataTypes.EFileStatus;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.ResourceBundle;
 | 
			
		||||
 | 
			
		||||
public class NSTableViewController implements Initializable {
 | 
			
		||||
    @FXML
 | 
			
		||||
    private TableView<NSLRowModel> table;
 | 
			
		||||
    private ObservableList<NSLRowModel> rowsObsLst;
 | 
			
		||||
 | 
			
		||||
    private String protocol;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(URL url, ResourceBundle resourceBundle) {
 | 
			
		||||
        rowsObsLst = FXCollections.observableArrayList();
 | 
			
		||||
        table.setPlaceholder(new Label());
 | 
			
		||||
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
 | 
			
		||||
 | 
			
		||||
        TableColumn<NSLRowModel, String> statusColumn = new TableColumn<>(resourceBundle.getString("tableStatusLbl"));
 | 
			
		||||
        TableColumn<NSLRowModel, String> fileNameColumn = new TableColumn<>(resourceBundle.getString("tableFileNameLbl"));
 | 
			
		||||
        TableColumn<NSLRowModel, String> fileSizeColumn = new TableColumn<>(resourceBundle.getString("tableSizeLbl"));
 | 
			
		||||
        TableColumn<NSLRowModel, Boolean> uploadColumn = new TableColumn<>(resourceBundle.getString("tableUploadLbl"));
 | 
			
		||||
        // See https://bugs.openjdk.java.net/browse/JDK-8157687
 | 
			
		||||
        statusColumn.setMinWidth(100.0);
 | 
			
		||||
        statusColumn.setPrefWidth(100.0);
 | 
			
		||||
        statusColumn.setMaxWidth(100.0);
 | 
			
		||||
        statusColumn.setResizable(false);
 | 
			
		||||
 | 
			
		||||
        fileNameColumn.setMinWidth(25.0);
 | 
			
		||||
 | 
			
		||||
        fileSizeColumn.setMinWidth(120.0);
 | 
			
		||||
        fileSizeColumn.setPrefWidth(120.0);
 | 
			
		||||
        fileSizeColumn.setMaxWidth(120.0);
 | 
			
		||||
        fileSizeColumn.setResizable(false);
 | 
			
		||||
 | 
			
		||||
        uploadColumn.setMinWidth(100.0);
 | 
			
		||||
        uploadColumn.setPrefWidth(100.0);
 | 
			
		||||
        uploadColumn.setMaxWidth(100.0);
 | 
			
		||||
        uploadColumn.setResizable(false);
 | 
			
		||||
 | 
			
		||||
        statusColumn.setCellValueFactory(new PropertyValueFactory<>("status"));
 | 
			
		||||
        fileNameColumn.setCellValueFactory(new PropertyValueFactory<>("nspFileName"));
 | 
			
		||||
        fileSizeColumn.setCellValueFactory(new PropertyValueFactory<>("nspFileSize"));
 | 
			
		||||
        // ><
 | 
			
		||||
        uploadColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<NSLRowModel, Boolean>, ObservableValue<Boolean>>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public ObservableValue<Boolean> call(TableColumn.CellDataFeatures<NSLRowModel, Boolean> paramFeatures) {
 | 
			
		||||
                NSLRowModel model = paramFeatures.getValue();
 | 
			
		||||
 | 
			
		||||
                SimpleBooleanProperty booleanProperty = new SimpleBooleanProperty(model.isMarkForUpload());
 | 
			
		||||
 | 
			
		||||
                booleanProperty.addListener(new ChangeListener<Boolean>() {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public void changed(ObservableValue<? extends Boolean> observableValue, Boolean oldValue, Boolean newValue) {
 | 
			
		||||
                        model.setMarkForUpload(newValue);
 | 
			
		||||
                        restrictSelection(model);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return booleanProperty;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        uploadColumn.setCellFactory(new Callback<TableColumn<NSLRowModel, Boolean>, TableCell<NSLRowModel, Boolean>>() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public TableCell<NSLRowModel, Boolean> call(TableColumn<NSLRowModel, Boolean> paramFeatures) {
 | 
			
		||||
                CheckBoxTableCell<NSLRowModel, Boolean> cell = new CheckBoxTableCell<>();
 | 
			
		||||
                return cell;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        table.setItems(rowsObsLst);
 | 
			
		||||
        table.getColumns().addAll(statusColumn, fileNameColumn, fileSizeColumn, uploadColumn);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * See uploadColumn callback. In case of GoldLeaf we have to restrict selection
 | 
			
		||||
     * */
 | 
			
		||||
    private void restrictSelection(NSLRowModel modelChecked){
 | 
			
		||||
        if (!protocol.equals("TinFoil") && rowsObsLst.size() > 1) {       // Tinfoil doesn't need any restrictions. If only one file in list, also useless
 | 
			
		||||
            for (NSLRowModel model: rowsObsLst){
 | 
			
		||||
                if (model != modelChecked)
 | 
			
		||||
                    model.setMarkForUpload(false);
 | 
			
		||||
            }
 | 
			
		||||
            table.refresh();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Add files when user selected them
 | 
			
		||||
     * */
 | 
			
		||||
    public void setFiles(List<File> files){
 | 
			
		||||
        rowsObsLst.clear();                 // TODO: consider table refresh
 | 
			
		||||
        if (files == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (protocol.equals("TinFoil")){
 | 
			
		||||
            for (File nspFile: files){
 | 
			
		||||
                rowsObsLst.add(new NSLRowModel(nspFile, true));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            rowsObsLst.clear();
 | 
			
		||||
            for (File nspFile: files){
 | 
			
		||||
                rowsObsLst.add(new NSLRowModel(nspFile, false));
 | 
			
		||||
            }
 | 
			
		||||
            rowsObsLst.get(0).setMarkForUpload(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return files ready for upload. Requested from NSLMainController only
 | 
			
		||||
     * @return null if no files marked for upload
 | 
			
		||||
     *         List<File> if there are files
 | 
			
		||||
     * */
 | 
			
		||||
    public List<File> getFiles(){
 | 
			
		||||
        List<File> files = new ArrayList<>();
 | 
			
		||||
        if (rowsObsLst.isEmpty())
 | 
			
		||||
            return null;
 | 
			
		||||
        else {
 | 
			
		||||
            for (NSLRowModel model: rowsObsLst){
 | 
			
		||||
                if (model.isMarkForUpload())
 | 
			
		||||
                    files.add(model.getNspFile());
 | 
			
		||||
            }
 | 
			
		||||
            if (!files.isEmpty())
 | 
			
		||||
                return files;
 | 
			
		||||
            else
 | 
			
		||||
                return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Update files in case something is wrong. Requested from UsbCommunications
 | 
			
		||||
     * */
 | 
			
		||||
    public void setFileStatus(String fileName, EFileStatus status){
 | 
			
		||||
        for (NSLRowModel model: rowsObsLst){
 | 
			
		||||
            if (model.getNspFileName().equals(fileName)){
 | 
			
		||||
                model.setStatus(status);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        table.refresh();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Called if selected different USB protocol
 | 
			
		||||
     * */
 | 
			
		||||
    public void setNewProtocol(String newProtocol){
 | 
			
		||||
        protocol = newProtocol;
 | 
			
		||||
        if (rowsObsLst.isEmpty())
 | 
			
		||||
            return;
 | 
			
		||||
        if (newProtocol.equals("TinFoil")){
 | 
			
		||||
            for (NSLRowModel model: rowsObsLst)
 | 
			
		||||
                model.setMarkForUpload(true);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            for (NSLRowModel model: rowsObsLst)
 | 
			
		||||
                model.setMarkForUpload(false);
 | 
			
		||||
            rowsObsLst.get(0).setMarkForUpload(true);
 | 
			
		||||
        }
 | 
			
		||||
        table.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +1,28 @@
 | 
			
		|||
package nsusbloader;
 | 
			
		||||
 | 
			
		||||
class MediatorControl {
 | 
			
		||||
    private boolean isTransferActive = false;
 | 
			
		||||
import nsusbloader.Controllers.NSLMainController;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
 | 
			
		||||
public class MediatorControl {
 | 
			
		||||
    private AtomicBoolean isTransferActive = new AtomicBoolean(false);  // Overcoded just for sure
 | 
			
		||||
    private NSLMainController applicationController;
 | 
			
		||||
 | 
			
		||||
    static MediatorControl getInstance(){
 | 
			
		||||
    public static MediatorControl getInstance(){
 | 
			
		||||
        return MediatorControlHold.INSTANCE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static class MediatorControlHold {
 | 
			
		||||
        private static final MediatorControl INSTANCE = new MediatorControl();
 | 
			
		||||
    }
 | 
			
		||||
    void registerController(NSLMainController controller){
 | 
			
		||||
    public void setController(NSLMainController controller){
 | 
			
		||||
        this.applicationController = controller;
 | 
			
		||||
    }
 | 
			
		||||
    NSLMainController getContoller(){ return this.applicationController; }
 | 
			
		||||
 | 
			
		||||
    synchronized void setTransferActive(boolean state) {
 | 
			
		||||
        isTransferActive = state;
 | 
			
		||||
    public synchronized void setTransferActive(boolean state) {
 | 
			
		||||
        isTransferActive.set(state);
 | 
			
		||||
        applicationController.notifyTransmissionStarted(state);
 | 
			
		||||
    }
 | 
			
		||||
    synchronized boolean getTransferActive() {
 | 
			
		||||
        return this.isTransferActive;
 | 
			
		||||
    }
 | 
			
		||||
    public synchronized boolean getTransferActive() { return this.isTransferActive.get(); }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,11 @@ package nsusbloader;
 | 
			
		|||
import javafx.animation.AnimationTimer;
 | 
			
		||||
import javafx.scene.control.ProgressBar;
 | 
			
		||||
import javafx.scene.control.TextArea;
 | 
			
		||||
import nsusbloader.Controllers.NSTableViewController;
 | 
			
		||||
import nsusbloader.NSLDataTypes.EFileStatus;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.concurrent.BlockingQueue;
 | 
			
		||||
 | 
			
		||||
public class MessagesConsumer extends AnimationTimer {
 | 
			
		||||
| 
						 | 
				
			
			@ -13,18 +16,24 @@ public class MessagesConsumer extends AnimationTimer {
 | 
			
		|||
 | 
			
		||||
    private final BlockingQueue<Double> progressQueue;
 | 
			
		||||
    private final ProgressBar progressBar;
 | 
			
		||||
    private final HashMap<String, EFileStatus> statusMap;
 | 
			
		||||
    private final NSTableViewController tableViewController;
 | 
			
		||||
 | 
			
		||||
    private boolean isInterrupted;
 | 
			
		||||
 | 
			
		||||
    MessagesConsumer(BlockingQueue<String> msgQueue, TextArea logsArea, BlockingQueue<Double> progressQueue, ProgressBar progressBar){
 | 
			
		||||
        this.msgQueue = msgQueue;
 | 
			
		||||
        this.logsArea = logsArea;
 | 
			
		||||
    MessagesConsumer(BlockingQueue<String> msgQueue, BlockingQueue<Double> progressQueue, HashMap<String, EFileStatus> statusMap){
 | 
			
		||||
        this.isInterrupted = false;
 | 
			
		||||
 | 
			
		||||
        this.msgQueue = msgQueue;
 | 
			
		||||
        this.logsArea = MediatorControl.getInstance().getContoller().logArea;
 | 
			
		||||
 | 
			
		||||
        this.progressBar = progressBar;
 | 
			
		||||
        this.progressQueue = progressQueue;
 | 
			
		||||
        this.progressBar = MediatorControl.getInstance().getContoller().progressBar;
 | 
			
		||||
 | 
			
		||||
        this.statusMap = statusMap;
 | 
			
		||||
        this.tableViewController = MediatorControl.getInstance().getContoller().tableFilesListController;
 | 
			
		||||
 | 
			
		||||
        progressBar.setProgress(0.0);
 | 
			
		||||
        this.isInterrupted = false;
 | 
			
		||||
        MediatorControl.getInstance().setTransferActive(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -40,15 +49,18 @@ public class MessagesConsumer extends AnimationTimer {
 | 
			
		|||
        if (progressRecieved > 0)
 | 
			
		||||
            progress.forEach(prg -> progressBar.setProgress(prg));
 | 
			
		||||
 | 
			
		||||
        if (isInterrupted) {
 | 
			
		||||
        if (isInterrupted) {                                                // It's safe 'cuz it's could't be interrupted while HashMap populating
 | 
			
		||||
            MediatorControl.getInstance().setTransferActive(false);
 | 
			
		||||
            progressBar.setProgress(0.0);
 | 
			
		||||
 | 
			
		||||
            if (statusMap.size() > 0)
 | 
			
		||||
                for (String key : statusMap.keySet())
 | 
			
		||||
                    tableViewController.setFileStatus(key, statusMap.get(key));
 | 
			
		||||
            this.stop();
 | 
			
		||||
        }
 | 
			
		||||
        //TODO
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void interrupt(){
 | 
			
		||||
        this.isInterrupted = true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								src/main/java/nsusbloader/NSLDataTypes/EFileStatus.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/main/java/nsusbloader/NSLDataTypes/EFileStatus.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
package nsusbloader.NSLDataTypes;
 | 
			
		||||
 | 
			
		||||
public enum EFileStatus {
 | 
			
		||||
    UPLOADED, INCORRECT_FILE_FAILED, FAILED
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								src/main/java/nsusbloader/NSLDataTypes/EMsgType.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/main/java/nsusbloader/NSLDataTypes/EMsgType.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
package nsusbloader.NSLDataTypes;
 | 
			
		||||
 | 
			
		||||
public enum EMsgType {
 | 
			
		||||
    PASS, FAIL, INFO, WARNING
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,3 @@
 | 
			
		|||
/**
 | 
			
		||||
 Name: NSL-USBFoil
 | 
			
		||||
 @author Dmitry Isaenko
 | 
			
		||||
 License: GNU GPL v.3
 | 
			
		||||
 @see https://github.com/developersu/
 | 
			
		||||
 @see https://developersu.blogspot.com/
 | 
			
		||||
 2019, Russia
 | 
			
		||||
 */
 | 
			
		||||
package nsusbloader;
 | 
			
		||||
 | 
			
		||||
import javafx.application.Application;
 | 
			
		||||
| 
						 | 
				
			
			@ -14,22 +6,24 @@ import javafx.scene.Parent;
 | 
			
		|||
import javafx.scene.Scene;
 | 
			
		||||
import javafx.scene.image.Image;
 | 
			
		||||
import javafx.stage.Stage;
 | 
			
		||||
import nsusbloader.Controllers.NSLMainController;
 | 
			
		||||
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
import java.util.ResourceBundle;
 | 
			
		||||
 | 
			
		||||
public class NSLMain extends Application {
 | 
			
		||||
    static final String appVersion = "v0.1";
 | 
			
		||||
    public static final String appVersion = "v0.2";
 | 
			
		||||
    @Override
 | 
			
		||||
    public void start(Stage primaryStage) throws Exception{
 | 
			
		||||
 | 
			
		||||
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));
 | 
			
		||||
 | 
			
		||||
        ResourceBundle rb;
 | 
			
		||||
        if (Locale.getDefault().getISO3Language().equals("rus"))
 | 
			
		||||
            rb = ResourceBundle.getBundle("locale", new Locale("ru"));
 | 
			
		||||
        else
 | 
			
		||||
            rb = ResourceBundle.getBundle("locale", new Locale("en"));
 | 
			
		||||
 | 
			
		||||
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/NSLMain.fxml"));
 | 
			
		||||
 | 
			
		||||
        loader.setResources(rb);
 | 
			
		||||
        Parent root = loader.load();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +38,9 @@ public class NSLMain extends Application {
 | 
			
		|||
        primaryStage.setMinWidth(600);
 | 
			
		||||
        primaryStage.setMinHeight(375);
 | 
			
		||||
        Scene mainScene = new Scene(root, 800, 400);
 | 
			
		||||
        mainScene.getStylesheets().add("/res/app.css");
 | 
			
		||||
 | 
			
		||||
        mainScene.getStylesheets().add(AppPreferences.getInstance().getTheme());
 | 
			
		||||
 | 
			
		||||
        primaryStage.setScene(mainScene);
 | 
			
		||||
        primaryStage.show();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,6 +49,9 @@ public class NSLMain extends Application {
 | 
			
		|||
                if(! ServiceWindow.getConfirmationWindow(rb.getString("windowTitleConfirmExit"), rb.getString("windowBodyConfirmExit")))
 | 
			
		||||
                    e.consume();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        NSLMainController controller = loader.getController();
 | 
			
		||||
        primaryStage.setOnHidden(e-> controller.exit());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,133 +0,0 @@
 | 
			
		|||
package nsusbloader;
 | 
			
		||||
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.fxml.Initializable;
 | 
			
		||||
import javafx.scene.control.Button;
 | 
			
		||||
import javafx.scene.control.ProgressBar;
 | 
			
		||||
import javafx.scene.control.TextArea;
 | 
			
		||||
import javafx.scene.layout.Region;
 | 
			
		||||
import javafx.stage.FileChooser;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.ResourceBundle;
 | 
			
		||||
 | 
			
		||||
public class NSLMainController implements Initializable {
 | 
			
		||||
 | 
			
		||||
    private ResourceBundle resourceBundle;
 | 
			
		||||
 | 
			
		||||
    private List<File> nspToUpload;
 | 
			
		||||
 | 
			
		||||
    @FXML
 | 
			
		||||
    private TextArea logArea;
 | 
			
		||||
    @FXML
 | 
			
		||||
    private Button selectNspBtn;
 | 
			
		||||
    @FXML
 | 
			
		||||
    private Button uploadStopBtn;
 | 
			
		||||
    private Region btnUpStopImage;
 | 
			
		||||
    @FXML
 | 
			
		||||
    private ProgressBar progressBar;
 | 
			
		||||
 | 
			
		||||
    private Thread usbThread;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(URL url, ResourceBundle rb) {
 | 
			
		||||
        this.resourceBundle = rb;
 | 
			
		||||
        logArea.setText(rb.getString("logsGreetingsMessage")+" "+NSLMain.appVersion+"!\n");
 | 
			
		||||
        if (System.getProperty("os.name").toLowerCase().startsWith("lin"))
 | 
			
		||||
            if (!System.getProperty("user.name").equals("root"))
 | 
			
		||||
                logArea.appendText(rb.getString("logsEnteredAsMsg1")+System.getProperty("user.name")+"\n"+rb.getString("logsEnteredAsMsg2") + "\n");
 | 
			
		||||
 | 
			
		||||
        logArea.appendText(rb.getString("logsGreetingsMessage2")+"\n");
 | 
			
		||||
 | 
			
		||||
        MediatorControl.getInstance().registerController(this);
 | 
			
		||||
 | 
			
		||||
        uploadStopBtn.setDisable(true);
 | 
			
		||||
        selectNspBtn.setOnAction(e->{ selectFilesBtnAction(); });
 | 
			
		||||
        uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
 | 
			
		||||
 | 
			
		||||
        this.btnUpStopImage = new Region();
 | 
			
		||||
        btnUpStopImage.getStyleClass().add("regionUpload");
 | 
			
		||||
        //uploadStopBtn.getStyleClass().remove("button");
 | 
			
		||||
        uploadStopBtn.getStyleClass().add("buttonUp");
 | 
			
		||||
        uploadStopBtn.setGraphic(btnUpStopImage);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Functionality for selecting NSP button.
 | 
			
		||||
     * Uses setReady and setNotReady to simplify code readability.
 | 
			
		||||
     * */
 | 
			
		||||
    private void selectFilesBtnAction(){
 | 
			
		||||
        List<File> filesList;
 | 
			
		||||
        FileChooser fileChooser = new FileChooser();
 | 
			
		||||
        fileChooser.setTitle(resourceBundle.getString("btnFileOpen"));
 | 
			
		||||
        fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));         // TODO: read from prefs
 | 
			
		||||
        fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("NS ROM", "*.nsp"));
 | 
			
		||||
 | 
			
		||||
        filesList = fileChooser.showOpenMultipleDialog(logArea.getScene().getWindow());
 | 
			
		||||
        if (filesList != null && !filesList.isEmpty())
 | 
			
		||||
            setReady(filesList);
 | 
			
		||||
        else
 | 
			
		||||
            setNotReady(resourceBundle.getString("logsNoFolderFileSelected"));
 | 
			
		||||
    }
 | 
			
		||||
    private void setReady(List<File> filesList){
 | 
			
		||||
        logArea.setText(resourceBundle.getString("logsFilesToUploadTitle")+"\n");
 | 
			
		||||
        for (File item: filesList)
 | 
			
		||||
            logArea.appendText("  "+item.getAbsolutePath()+"\n");
 | 
			
		||||
        nspToUpload = filesList;
 | 
			
		||||
        uploadStopBtn.setDisable(false);
 | 
			
		||||
    }
 | 
			
		||||
    private void setNotReady(String whyNotReady){
 | 
			
		||||
        logArea.setText(whyNotReady);
 | 
			
		||||
        nspToUpload = null;
 | 
			
		||||
        uploadStopBtn.setDisable(true);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * It's button listener when no transmission executes
 | 
			
		||||
     * */
 | 
			
		||||
    private void uploadBtnAction(){
 | 
			
		||||
        if (usbThread == null || !usbThread.isAlive()){
 | 
			
		||||
            UsbCommunications usbCommunications = new UsbCommunications(logArea, progressBar, nspToUpload);  //todo: progress bar
 | 
			
		||||
            usbThread = new Thread(usbCommunications);
 | 
			
		||||
            usbThread.start();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * It's button listener when transmission in progress
 | 
			
		||||
     * */
 | 
			
		||||
    private void stopBtnAction(){
 | 
			
		||||
        if (usbThread != null && usbThread.isAlive()){
 | 
			
		||||
            usbThread.interrupt();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * This thing modify UI for reusing 'Upload to NS' button and make functionality set for "Stop transmission"
 | 
			
		||||
     * Called from mediator
 | 
			
		||||
     * */
 | 
			
		||||
    void notifyTransmissionStarted(boolean isTransmissionStarted){
 | 
			
		||||
        if (isTransmissionStarted) {
 | 
			
		||||
            selectNspBtn.setDisable(true);
 | 
			
		||||
            uploadStopBtn.setOnAction(e->{ stopBtnAction(); });
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.setText(resourceBundle.getString("btnStop"));
 | 
			
		||||
 | 
			
		||||
            btnUpStopImage.getStyleClass().remove("regionUpload");
 | 
			
		||||
            btnUpStopImage.getStyleClass().add("regionStop");
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.getStyleClass().remove("buttonUp");
 | 
			
		||||
            uploadStopBtn.getStyleClass().add("buttonStop");
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            selectNspBtn.setDisable(false);
 | 
			
		||||
            uploadStopBtn.setOnAction(e->{ uploadBtnAction(); });
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.setText(resourceBundle.getString("btnUpload"));
 | 
			
		||||
 | 
			
		||||
            btnUpStopImage.getStyleClass().remove("regionStop");
 | 
			
		||||
            btnUpStopImage.getStyleClass().add("regionUpload");
 | 
			
		||||
 | 
			
		||||
            uploadStopBtn.getStyleClass().remove("buttonStop");
 | 
			
		||||
            uploadStopBtn.getStyleClass().add("buttonUp");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/main/java/nsusbloader/PFS/NCAFile.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/main/java/nsusbloader/PFS/NCAFile.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
package nsusbloader.PFS;
 | 
			
		||||
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.nio.ByteOrder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data class to hold NCA, tik, xml etc. meta-information
 | 
			
		||||
 * */
 | 
			
		||||
public class NCAFile {
 | 
			
		||||
    //private int ncaNumber;
 | 
			
		||||
    private byte[] ncaFileName;
 | 
			
		||||
    private long ncaOffset;
 | 
			
		||||
    private long ncaSize;
 | 
			
		||||
 | 
			
		||||
    //public void setNcaNumber(int ncaNumber){ this.ncaNumber = ncaNumber; }
 | 
			
		||||
    public void setNcaFileName(byte[] ncaFileName) { this.ncaFileName = ncaFileName; }
 | 
			
		||||
    public void setNcaOffset(long ncaOffset) { this.ncaOffset = ncaOffset; }
 | 
			
		||||
    public void setNcaSize(long ncaSize) { this.ncaSize = ncaSize; }
 | 
			
		||||
 | 
			
		||||
    //public int getNcaNumber() {return this.ncaNumber; }
 | 
			
		||||
    public byte[] getNcaFileName() { return ncaFileName; }
 | 
			
		||||
    public byte[] getNcaFileNameLength() { return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFileName.length).array(); }
 | 
			
		||||
    public long getNcaOffset() { return ncaOffset; }
 | 
			
		||||
    public long getNcaSize() { return ncaSize; }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										243
									
								
								src/main/java/nsusbloader/PFS/PFSProvider.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								src/main/java/nsusbloader/PFS/PFSProvider.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,243 @@
 | 
			
		|||
package nsusbloader.PFS;
 | 
			
		||||
 | 
			
		||||
import nsusbloader.NSLDataTypes.EMsgType;
 | 
			
		||||
import nsusbloader.ServiceWindow;
 | 
			
		||||
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.nio.ByteOrder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.concurrent.BlockingQueue;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Used in GoldLeaf USB protocol
 | 
			
		||||
 * */
 | 
			
		||||
public class PFSProvider {
 | 
			
		||||
    private static final byte[] PFS0 = new byte[]{(byte)0x50, (byte)0x46, (byte)0x53, (byte)0x30};  // PFS0, and what did you think?
 | 
			
		||||
 | 
			
		||||
    private BlockingQueue<String> msgQueue;
 | 
			
		||||
    private ResourceBundle rb;
 | 
			
		||||
 | 
			
		||||
    private RandomAccessFile randAccessFile;
 | 
			
		||||
    private String nspFileName;
 | 
			
		||||
    private NCAFile[] ncaFiles;
 | 
			
		||||
    private long bodySize;
 | 
			
		||||
    private int ticketID = -1;
 | 
			
		||||
 | 
			
		||||
    public PFSProvider(File nspFile, BlockingQueue msgQueue){
 | 
			
		||||
        this.msgQueue = msgQueue;
 | 
			
		||||
        try {
 | 
			
		||||
            this.randAccessFile = new RandomAccessFile(nspFile, "r");
 | 
			
		||||
            nspFileName = nspFile.getName();
 | 
			
		||||
        }
 | 
			
		||||
        catch (FileNotFoundException fnfe){
 | 
			
		||||
            printLog("PFS File not founnd: \n  "+fnfe.getMessage(), EMsgType.FAIL);
 | 
			
		||||
            nspFileName = null;
 | 
			
		||||
        }
 | 
			
		||||
        if (Locale.getDefault().getISO3Language().equals("rus"))
 | 
			
		||||
            rb = ResourceBundle.getBundle("locale", new Locale("ru"));
 | 
			
		||||
        else
 | 
			
		||||
            rb = ResourceBundle.getBundle("locale", new Locale("en"));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public boolean init() {
 | 
			
		||||
        if (nspFileName == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        int filesCount;
 | 
			
		||||
        int header;
 | 
			
		||||
 | 
			
		||||
        printLog("PFS Start NSP file analyze for ["+nspFileName+"]", EMsgType.INFO);
 | 
			
		||||
        try {
 | 
			
		||||
            byte[] fileStartingBytes = new byte[12];
 | 
			
		||||
            // Read PFS0, files count, header, padding (4 zero bytes)
 | 
			
		||||
            if (randAccessFile.read(fileStartingBytes) == 12)
 | 
			
		||||
                printLog("PFS Read file starting bytes.", EMsgType.PASS);
 | 
			
		||||
            else {
 | 
			
		||||
                printLog("PFS Read file starting bytes.", EMsgType.FAIL);
 | 
			
		||||
                randAccessFile.close();
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            // Check PFS0
 | 
			
		||||
            if (Arrays.equals(PFS0, Arrays.copyOfRange(fileStartingBytes, 0, 4)))
 | 
			
		||||
                printLog("PFS Read 'PFS0'.", EMsgType.PASS);
 | 
			
		||||
            else {
 | 
			
		||||
                printLog("PFS Read 'PFS0'.", EMsgType.WARNING);
 | 
			
		||||
                if (!ServiceWindow.getConfirmationWindow(nspFileName+"\n"+rb.getString("windowTitleConfirmWrongPFS0"), rb.getString("windowBodyConfirmWrongPFS0"))) {
 | 
			
		||||
                    randAccessFile.close();
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // Get files count
 | 
			
		||||
            filesCount = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 4, 8)).order(ByteOrder.LITTLE_ENDIAN).getInt();
 | 
			
		||||
            if (filesCount > 0 ) {
 | 
			
		||||
                printLog("PFS Read files count [" + filesCount + "]", EMsgType.PASS);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                printLog("PFS Read files count", EMsgType.FAIL);
 | 
			
		||||
                randAccessFile.close();
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            // Get header
 | 
			
		||||
            header = ByteBuffer.wrap(Arrays.copyOfRange(fileStartingBytes, 8, 12)).order(ByteOrder.LITTLE_ENDIAN).getInt();
 | 
			
		||||
            if (header > 0 )
 | 
			
		||||
                printLog("PFS Read header ["+header+"]", EMsgType.PASS);
 | 
			
		||||
            else {
 | 
			
		||||
                printLog("PFS Read header ", EMsgType.FAIL);
 | 
			
		||||
                randAccessFile.close();
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            //*********************************************************************************************
 | 
			
		||||
            // Create NCA set
 | 
			
		||||
            this.ncaFiles = new NCAFile[filesCount];
 | 
			
		||||
            // Collect files from NSP
 | 
			
		||||
            byte[] ncaInfoArr = new byte[24];   // should be unsigned long, but.. java.. u know my pain man
 | 
			
		||||
 | 
			
		||||
            HashMap<Integer, Long> ncaNameOffsets = new LinkedHashMap<>();
 | 
			
		||||
 | 
			
		||||
            int offset;
 | 
			
		||||
            long nca_offset;
 | 
			
		||||
            long nca_size;
 | 
			
		||||
            long nca_name_offset;
 | 
			
		||||
 | 
			
		||||
            for (int i=0; i<filesCount; i++){
 | 
			
		||||
                if (randAccessFile.read(ncaInfoArr) == 24) {
 | 
			
		||||
                    printLog("PFS Read NCA inside NSP: " + i, EMsgType.PASS);
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    printLog("PFS Read NCA inside NSP: "+i, EMsgType.FAIL);
 | 
			
		||||
                    randAccessFile.close();
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                offset =  ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 0, 4)).order(ByteOrder.LITTLE_ENDIAN).getInt();
 | 
			
		||||
                nca_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 4, 12)).order(ByteOrder.LITTLE_ENDIAN).getLong();
 | 
			
		||||
                nca_size = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 12, 20)).order(ByteOrder.LITTLE_ENDIAN).getLong();
 | 
			
		||||
                nca_name_offset = ByteBuffer.wrap(Arrays.copyOfRange(ncaInfoArr, 20, 24)).order(ByteOrder.LITTLE_ENDIAN).getInt(); // yes, cast from int to long.
 | 
			
		||||
 | 
			
		||||
                printLog("  Padding check", offset == 0?EMsgType.PASS:EMsgType.WARNING);
 | 
			
		||||
                printLog("  NCA offset check "+nca_offset, nca_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
 | 
			
		||||
                printLog("  NCA size check: "+nca_size, nca_size >= 0?EMsgType.PASS: EMsgType.WARNING);
 | 
			
		||||
                printLog("  NCA name offset check "+nca_name_offset, nca_name_offset >= 0?EMsgType.PASS:EMsgType.WARNING);
 | 
			
		||||
 | 
			
		||||
                NCAFile ncaFile = new NCAFile();
 | 
			
		||||
                ncaFile.setNcaOffset(nca_offset);
 | 
			
		||||
                ncaFile.setNcaSize(nca_size);
 | 
			
		||||
                this.ncaFiles[i] = ncaFile;
 | 
			
		||||
 | 
			
		||||
                ncaNameOffsets.put(i, nca_name_offset);
 | 
			
		||||
            }
 | 
			
		||||
            // Final offset
 | 
			
		||||
            byte[] bufForInt = new byte[4];
 | 
			
		||||
            if ((randAccessFile.read(bufForInt) == 4) && (Arrays.equals(bufForInt, new byte[4])))
 | 
			
		||||
                printLog("PFS Final padding check", EMsgType.PASS);
 | 
			
		||||
            else
 | 
			
		||||
                printLog("PFS Final padding check", EMsgType.WARNING);
 | 
			
		||||
 | 
			
		||||
            // Calculate position including header for body size offset
 | 
			
		||||
            bodySize = randAccessFile.getFilePointer()+header;
 | 
			
		||||
            //*********************************************************************************************
 | 
			
		||||
            // Collect file names from NCAs
 | 
			
		||||
            printLog("PFS Collecting file names", EMsgType.INFO);
 | 
			
		||||
            List<Byte> ncaFN;                 // Temporary
 | 
			
		||||
            byte[] b = new byte[1];                 // Temporary
 | 
			
		||||
            for (int i=0; i<filesCount; i++){
 | 
			
		||||
                ncaFN = new ArrayList<>();
 | 
			
		||||
                randAccessFile.seek(filesCount*24+16+ncaNameOffsets.get(i));          // Files cont * 24(bit for each meta-data) + 4 bytes goes after all of them  + 12 bit what were in the beginning
 | 
			
		||||
                while ((randAccessFile.read(b)) != -1){
 | 
			
		||||
                    if (b[0] == 0x00)
 | 
			
		||||
                        break;
 | 
			
		||||
                    else
 | 
			
		||||
                        ncaFN.add(b[0]);
 | 
			
		||||
                }
 | 
			
		||||
                byte[] exchangeTempArray = new byte[ncaFN.size()];
 | 
			
		||||
                for (int j=0; j < ncaFN.size(); j++)
 | 
			
		||||
                    exchangeTempArray[j] = ncaFN.get(j);
 | 
			
		||||
                // Find and store ticket (.tik)
 | 
			
		||||
                if (new String(exchangeTempArray, StandardCharsets.UTF_8).toLowerCase().endsWith(".tik"))
 | 
			
		||||
                    this.ticketID = i;
 | 
			
		||||
                this.ncaFiles[i].setNcaFileName(Arrays.copyOf(exchangeTempArray, exchangeTempArray.length));
 | 
			
		||||
            }
 | 
			
		||||
            randAccessFile.close();
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException ioe){
 | 
			
		||||
            printLog("PFS Failed NSP file analyze for ["+nspFileName+"]\n  "+ioe.getMessage(), EMsgType.FAIL);
 | 
			
		||||
            ioe.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
        printLog("PFS Finish NSP file analyze for ["+nspFileName+"]", EMsgType.PASS);
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return file name as byte array
 | 
			
		||||
     * */
 | 
			
		||||
    public byte[] getBytesNspFileName(){
 | 
			
		||||
        return nspFileName.getBytes(StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return file name as String
 | 
			
		||||
     * */
 | 
			
		||||
    public String getStringNspFileName(){
 | 
			
		||||
        return nspFileName;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return file name length as byte array
 | 
			
		||||
     * */
 | 
			
		||||
    public byte[] getBytesNspFileNameLength(){
 | 
			
		||||
        return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(getBytesNspFileName().length).array();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return NCA count inside of file as byte array
 | 
			
		||||
     * */
 | 
			
		||||
    public byte[] getBytesCountOfNca(){
 | 
			
		||||
        return ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ncaFiles.length).array();
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return NCA count inside of file as int
 | 
			
		||||
     * */
 | 
			
		||||
    public int getIntCountOfNca(){
 | 
			
		||||
        return ncaFiles.length;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return requested-by-number NCA file inside of file
 | 
			
		||||
     * */
 | 
			
		||||
    public NCAFile getNca(int ncaNumber){
 | 
			
		||||
        return ncaFiles[ncaNumber];
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return bodySize
 | 
			
		||||
     * */
 | 
			
		||||
    public long getBodySize(){
 | 
			
		||||
        return bodySize;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Return special NCA file: ticket
 | 
			
		||||
     * (sugar)
 | 
			
		||||
     * */
 | 
			
		||||
    public int getNcaTicketID(){
 | 
			
		||||
        return ticketID;
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * This is what will print to textArea of the application.
 | 
			
		||||
     **/
 | 
			
		||||
    private void printLog(String message, EMsgType type){
 | 
			
		||||
        try {
 | 
			
		||||
            switch (type){
 | 
			
		||||
                case PASS:
 | 
			
		||||
                    msgQueue.put("[ PASS ] "+message+"\n");
 | 
			
		||||
                    break;
 | 
			
		||||
                case FAIL:
 | 
			
		||||
                    msgQueue.put("[ FAIL ] "+message+"\n");
 | 
			
		||||
                    break;
 | 
			
		||||
                case INFO:
 | 
			
		||||
                    msgQueue.put("[ INFO ] "+message+"\n");
 | 
			
		||||
                    break;
 | 
			
		||||
                case WARNING:
 | 
			
		||||
                    msgQueue.put("[ WARN ] "+message+"\n");
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
        }catch (InterruptedException ie){
 | 
			
		||||
            ie.printStackTrace();                                                  //TODO:  ???
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/main/java/nsusbloader/RainbowHexDump.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/main/java/nsusbloader/RainbowHexDump.java
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
package nsusbloader;
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Debug tool like hexdump <3
 | 
			
		||||
 */
 | 
			
		||||
public class RainbowHexDump {
 | 
			
		||||
    private static final String ANSI_RESET = "\u001B[0m";
 | 
			
		||||
    private static final String ANSI_BLACK = "\u001B[30m";
 | 
			
		||||
    private static final String ANSI_RED = "\u001B[31m";
 | 
			
		||||
    private static final String ANSI_GREEN = "\u001B[32m";
 | 
			
		||||
    private static final String ANSI_YELLOW = "\u001B[33m";
 | 
			
		||||
    private static final String ANSI_BLUE = "\u001B[34m";
 | 
			
		||||
    private static final String ANSI_PURPLE = "\u001B[35m";
 | 
			
		||||
    private static final String ANSI_CYAN = "\u001B[36m";
 | 
			
		||||
    private static final String ANSI_WHITE = "\u001B[37m";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static void hexDumpUTF8(byte[] byteArray){
 | 
			
		||||
        System.out.print(ANSI_BLUE);
 | 
			
		||||
        for (int i=0; i < byteArray.length; i++)
 | 
			
		||||
            System.out.print(String.format("%02d-", i%100));
 | 
			
		||||
        System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
 | 
			
		||||
        for (byte b: byteArray)
 | 
			
		||||
            System.out.print(String.format("%02x ", b));
 | 
			
		||||
        System.out.print("\t\t\t"
 | 
			
		||||
                + new String(byteArray, StandardCharsets.UTF_8)
 | 
			
		||||
                + "\n");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ public class ServiceWindow   {
 | 
			
		|||
     * Create window with notification
 | 
			
		||||
     * */
 | 
			
		||||
    /* // not used
 | 
			
		||||
    static void getErrorNotification(String title, String body){
 | 
			
		||||
    public static void getErrorNotification(String title, String body){
 | 
			
		||||
        Alert alertBox = new Alert(Alert.AlertType.ERROR);
 | 
			
		||||
        alertBox.setTitle(title);
 | 
			
		||||
        alertBox.setHeaderText(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ public class ServiceWindow   {
 | 
			
		|||
    /**
 | 
			
		||||
     * Create notification window with confirm/deny
 | 
			
		||||
     * */
 | 
			
		||||
    static boolean getConfirmationWindow(String title, String body){
 | 
			
		||||
    public static boolean getConfirmationWindow(String title, String body){
 | 
			
		||||
        Alert alertBox = new Alert(Alert.AlertType.CONFIRMATION);
 | 
			
		||||
        alertBox.setTitle(title);
 | 
			
		||||
        alertBox.setHeaderText(null);
 | 
			
		||||
| 
						 | 
				
			
			@ -35,12 +35,9 @@ public class ServiceWindow   {
 | 
			
		|||
        alertBox.getDialogPane().setMinWidth(Region.USE_PREF_SIZE);
 | 
			
		||||
        alertBox.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
 | 
			
		||||
        alertBox.setResizable(true);        // Java bug workaround for JDR11/OpenJFX. TODO: nothing. really.
 | 
			
		||||
        alertBox.getDialogPane().getStylesheets().add("/res/app.css");
 | 
			
		||||
        alertBox.getDialogPane().getStylesheets().add(AppPreferences.getInstance().getTheme());
 | 
			
		||||
        Optional<ButtonType> result = alertBox.showAndWait();
 | 
			
		||||
        if (result.get() == ButtonType.OK)
 | 
			
		||||
            return true;
 | 
			
		||||
        else
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        return (result.isPresent() && result.get() == ButtonType.OK);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -2,23 +2,80 @@
 | 
			
		|||
 | 
			
		||||
<?import javafx.geometry.Insets?>
 | 
			
		||||
<?import javafx.scene.control.Button?>
 | 
			
		||||
<?import javafx.scene.control.ChoiceBox?>
 | 
			
		||||
<?import javafx.scene.control.ProgressBar?>
 | 
			
		||||
<?import javafx.scene.control.Tab?>
 | 
			
		||||
<?import javafx.scene.control.TabPane?>
 | 
			
		||||
<?import javafx.scene.control.TextArea?>
 | 
			
		||||
<?import javafx.scene.control.ToolBar?>
 | 
			
		||||
<?import javafx.scene.layout.AnchorPane?>
 | 
			
		||||
<?import javafx.scene.layout.ColumnConstraints?>
 | 
			
		||||
<?import javafx.scene.layout.GridPane?>
 | 
			
		||||
<?import javafx.scene.layout.HBox?>
 | 
			
		||||
<?import javafx.scene.layout.Pane?>
 | 
			
		||||
<?import javafx.scene.layout.RowConstraints?>
 | 
			
		||||
<?import javafx.scene.layout.VBox?>
 | 
			
		||||
<?import javafx.scene.shape.SVGPath?>
 | 
			
		||||
 | 
			
		||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.NSLMainController">
 | 
			
		||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSLMainController">
 | 
			
		||||
   <children>
 | 
			
		||||
      <VBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
 | 
			
		||||
         <children>
 | 
			
		||||
            <TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS">
 | 
			
		||||
               <VBox.margin>
 | 
			
		||||
                  <Insets left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
               </VBox.margin>
 | 
			
		||||
            </TextArea>
 | 
			
		||||
            <TabPane prefHeight="200.0" prefWidth="200.0" side="LEFT" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
 | 
			
		||||
              <tabs>
 | 
			
		||||
                <Tab closable="false">
 | 
			
		||||
                     <content>
 | 
			
		||||
                        <VBox prefHeight="200.0" prefWidth="100.0">
 | 
			
		||||
                           <children>
 | 
			
		||||
                              <ToolBar>
 | 
			
		||||
                                <items>
 | 
			
		||||
                                    <ChoiceBox fx:id="choiceProtocol" prefWidth="120.0" />
 | 
			
		||||
                                    <Pane HBox.hgrow="ALWAYS" />
 | 
			
		||||
                                    <Button fx:id="switchThemeBtn" mnemonicParsing="false" />
 | 
			
		||||
                                </items>
 | 
			
		||||
                              </ToolBar>
 | 
			
		||||
                              <GridPane>
 | 
			
		||||
                                <columnConstraints>
 | 
			
		||||
                                  <ColumnConstraints hgrow="SOMETIMES" />
 | 
			
		||||
                                    <ColumnConstraints hgrow="SOMETIMES" percentWidth="90.0" />
 | 
			
		||||
                                  <ColumnConstraints hgrow="SOMETIMES" />
 | 
			
		||||
                                </columnConstraints>
 | 
			
		||||
                                <rowConstraints>
 | 
			
		||||
                                  <RowConstraints vgrow="SOMETIMES" />
 | 
			
		||||
                                </rowConstraints>
 | 
			
		||||
                                 <children>
 | 
			
		||||
                                    <Pane fx:id="specialPane" GridPane.columnIndex="1" />
 | 
			
		||||
                                 </children>
 | 
			
		||||
                                 <VBox.margin>
 | 
			
		||||
                                    <Insets bottom="2.0" />
 | 
			
		||||
                                 </VBox.margin>
 | 
			
		||||
                              </GridPane>
 | 
			
		||||
                               <fx:include fx:id="tableFilesList" source="TableView.fxml" VBox.vgrow="ALWAYS" />
 | 
			
		||||
                           </children>
 | 
			
		||||
                        </VBox>
 | 
			
		||||
                     </content>
 | 
			
		||||
                     <graphic>
 | 
			
		||||
                        <SVGPath content="M21,19V17H8V19H21M21,13V11H8V13H21M8,7H21V5H8V7M4,5V7H6V5H4M3,5A1,1 0 0,1 4,4H6A1,1 0 0,1 7,5V7A1,1 0 0,1 6,8H4A1,1 0 0,1 3,7V5M4,11V13H6V11H4M3,11A1,1 0 0,1 4,10H6A1,1 0 0,1 7,11V13A1,1 0 0,1 6,14H4A1,1 0 0,1 3,13V11M4,17V19H6V17H4M3,17A1,1 0 0,1 4,16H6A1,1 0 0,1 7,17V19A1,1 0 0,1 6,20H4A1,1 0 0,1 3,19V17Z" />
 | 
			
		||||
                     </graphic>
 | 
			
		||||
                </Tab>
 | 
			
		||||
                <Tab closable="false">
 | 
			
		||||
                     <content>
 | 
			
		||||
                        <VBox prefHeight="200.0" prefWidth="100.0">
 | 
			
		||||
                           <children>
 | 
			
		||||
                              <TextArea fx:id="logArea" editable="false" VBox.vgrow="ALWAYS">
 | 
			
		||||
                                 <VBox.margin>
 | 
			
		||||
                                    <Insets bottom="2.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
                                 </VBox.margin>
 | 
			
		||||
                              </TextArea>
 | 
			
		||||
                           </children>
 | 
			
		||||
                        </VBox>
 | 
			
		||||
                     </content>
 | 
			
		||||
                     <graphic>
 | 
			
		||||
                        <SVGPath content="M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M7,7H17V5H19V19H5V5H7V7Z" />
 | 
			
		||||
                     </graphic>
 | 
			
		||||
                </Tab>
 | 
			
		||||
              </tabs>
 | 
			
		||||
            </TabPane>
 | 
			
		||||
            <ProgressBar fx:id="progressBar" prefWidth="Infinity" progress="0.0">
 | 
			
		||||
               <VBox.margin>
 | 
			
		||||
                  <Insets left="5.0" right="5.0" top="2.0" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								src/main/resources/TableView.fxml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/main/resources/TableView.fxml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
 | 
			
		||||
<?import javafx.geometry.Insets?>
 | 
			
		||||
<?import javafx.scene.control.TableView?>
 | 
			
		||||
<?import javafx.scene.layout.HBox?>
 | 
			
		||||
 | 
			
		||||
<HBox xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nsusbloader.Controllers.NSTableViewController">
 | 
			
		||||
   <children>
 | 
			
		||||
      <TableView fx:id="table" editable="true" HBox.hgrow="ALWAYS" />
 | 
			
		||||
   </children>
 | 
			
		||||
   <padding>
 | 
			
		||||
      <Insets bottom="2.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
   </padding>
 | 
			
		||||
</HBox>
 | 
			
		||||
| 
						 | 
				
			
			@ -12,3 +12,10 @@ logsGreetingsMessage2=--\n\
 | 
			
		|||
Source: https://github.com/developersu/ns-usbloader/\n\
 | 
			
		||||
Site: https://developersu.blogspot.com/search/label/NS-USBloader\n\
 | 
			
		||||
Dmitry Isaenko [developer.su]
 | 
			
		||||
windowTitleConfirmWrongPFS0=Incorrect file type
 | 
			
		||||
windowBodyConfirmWrongPFS0=Selected NSP file has incrrect starting symbols. Most likely it's corrupted.\n\
 | 
			
		||||
It's better to interrupt proccess now. Continue process anyway?
 | 
			
		||||
tableStatusLbl=Status
 | 
			
		||||
tableFileNameLbl=File name
 | 
			
		||||
tableSizeLbl=Size (~Mb)
 | 
			
		||||
tableUploadLbl=Upload?
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,4 +12,12 @@ logsGreetingsMessage2=--\n\
 | 
			
		|||
\u0418\u0441\u0445\u043E\u0434\u043D\u044B\u0439 \u043A\u043E\u0434: https://github.com/developersu/ns-usbloader/\n\
 | 
			
		||||
\u0421\u0430\u0439\u0442: https://developersu.blogspot.com/search/label/NS-USBloader\n\
 | 
			
		||||
\u0418\u0441\u0430\u0435\u043D\u043A\u043E \u0414\u043C\u0438\u0442\u0440\u0438\u0439 [developer.su]
 | 
			
		||||
windowTitleConfirmWrongPFS0=\u041D\u0435\u043F\u0440\u0438\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0439 \u0442\u0438\u043F \u0444\u0430\u0439\u043B\u0430 
 | 
			
		||||
windowBodyConfirmWrongPFS0=\u0412\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u0444\u0430\u0439\u043B NSP \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u0442 \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0435 \u0441\u0438\u043C\u0432\u043E\u043B\u044B. \u041E\u043D \u043D\u0430\u0432\u0435\u0440\u043D\u044F\u043A\u0430 \u043F\u043E\u0432\u0440\u0435\u0436\u0434\u0451\u043D.\n\
 | 
			
		||||
\u041B\u0443\u0447\u0448\u0435 \u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C\u0441\u044F \u043F\u0440\u044F\u043C\u043E \u0441\u0435\u0439\u0447\u0430\u0441. \u0425\u043E\u0447\u0435\u0448\u044C \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442\u044C \u043D\u0438 \u0441\u043C\u043E\u0442\u0440\u044F \u043D\u0438 \u043D\u0430 \u0447\u0442\u043E?\
 | 
			
		||||
 | 
			
		||||
tableUploadLbl=\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0442\u044C?
 | 
			
		||||
tableSizeLbl=\u0420\u0430\u0437\u043C\u0435\u0440 (~\u041C\u0431)
 | 
			
		||||
tableFileNameLbl=\u0418\u043C\u044F \u0444\u0430\u0439\u043B\u0430
 | 
			
		||||
tableStatusLbl=\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,100 +0,0 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
    src: url("NotoMono-Regular.ttf");
 | 
			
		||||
}
 | 
			
		||||
.root{
 | 
			
		||||
    -fx-background: #2d2d2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button, .buttonUp, .buttonStop{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #4f4f4f;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
.button:hover, .buttonStop:hover, .buttonUp:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #a4ffff;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.button:focused, .buttonStop:focused, .buttonUp:focused{
 | 
			
		||||
    -fx-background-color: #6a6a6a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button:pressed{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #289de8;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.buttonUp:pressed{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #a2e019;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.buttonStop:pressed{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #fb582c;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-area{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-control-inner-background: #4f4f4f;
 | 
			
		||||
    -fx-font-family: "Noto Mono";
 | 
			
		||||
    -fx-border-color: #00ffc9;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress-bar {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-box-border: transparent;
 | 
			
		||||
}
 | 
			
		||||
.progress-bar > .track {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-box-border: transparent;
 | 
			
		||||
}
 | 
			
		||||
.progress-bar > .bar {
 | 
			
		||||
    -fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
 | 
			
		||||
    -fx-background-radius: 2;
 | 
			
		||||
    -fx-background-insets: 1 1 2 1;
 | 
			
		||||
    -fx-padding: 0.23em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog-pane {
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
}
 | 
			
		||||
.dialog-pane > .button-bar > .container{
 | 
			
		||||
    -fx-background-color: #2d2d2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog-pane > .label{
 | 
			
		||||
    -fx-padding: 10 5 10 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.regionUpload{
 | 
			
		||||
    -fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
 | 
			
		||||
    -fx-background-color: #a2e019;
 | 
			
		||||
    -size: 24;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: 20;
 | 
			
		||||
}
 | 
			
		||||
.regionStop{
 | 
			
		||||
    -fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
 | 
			
		||||
    -fx-background-color: #fb582c;
 | 
			
		||||
    -size: 24;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: -size;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										255
									
								
								src/main/resources/res/app_dark.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								src/main/resources/res/app_dark.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,255 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
    src: url("NotoMono-Regular.ttf");
 | 
			
		||||
}
 | 
			
		||||
.root{
 | 
			
		||||
    -fx-background: #2d2d2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button, .buttonUp, .buttonStop, .buttonSelect{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #4f4f4f;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
    -fx-effect: none;
 | 
			
		||||
}
 | 
			
		||||
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .buttonSelect:focused:hover .choice-box:focused:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #a4ffff;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
 | 
			
		||||
    -fx-background-color: #6a6a6a;
 | 
			
		||||
    -fx-border-color: #6a6a6a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button:pressed, .button:pressed:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #e82382;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.buttonSelect:pressed, .buttonSelect:pressed:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #289de8;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.buttonUp:pressed, .buttonUp:pressed:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #a2e019;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.buttonStop:pressed, .buttonStop:pressed:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #fb582c;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-area{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-control-inner-background: #4f4f4f;
 | 
			
		||||
    -fx-font-family: "Noto Mono";
 | 
			
		||||
    -fx-border-color: #00ffc9;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress-bar {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-box-border: transparent;
 | 
			
		||||
}
 | 
			
		||||
.progress-bar > .track {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-box-border: transparent;
 | 
			
		||||
}
 | 
			
		||||
.progress-bar > .bar {
 | 
			
		||||
    -fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
 | 
			
		||||
    -fx-background-radius: 2;
 | 
			
		||||
    -fx-background-insets: 1 1 2 1;
 | 
			
		||||
    -fx-padding: 0.23em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog-pane {
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
}
 | 
			
		||||
.dialog-pane > .button-bar > .container{
 | 
			
		||||
    -fx-background-color: #2d2d2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog-pane > .label{
 | 
			
		||||
    -fx-padding: 10 5 10 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-bar{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.special-pane-as-border{
 | 
			
		||||
    -fx-background-color: #f7fafa;
 | 
			
		||||
    -fx-min-height: 1;
 | 
			
		||||
}
 | 
			
		||||
// -======================== Choice box =========================-
 | 
			
		||||
.choice-box {
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #4f4f4f;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-mark-color: #eea11e;
 | 
			
		||||
    -fx-effect: none;
 | 
			
		||||
}
 | 
			
		||||
.choice-box > .label {
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.choice-box:pressed, .choice-box:pressed:hover{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-border-color: #eea11e;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Background color of the whole context menu
 | 
			
		||||
.choice-box .context-menu {
 | 
			
		||||
    -fx-background-color: #2d2d2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focused item background color in the list
 | 
			
		||||
.choice-box .context-menu .menu-item:focused {
 | 
			
		||||
    -fx-background-color: #eea11e;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.choice-box .context-menu .menu-item:focused .label {
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// -======================== TAB PANE =========================-
 | 
			
		||||
.tab-pane .tab SVGPath{
 | 
			
		||||
    -fx-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.tab-pane .tab:selected SVGPath{
 | 
			
		||||
    -fx-fill: #08f3ff;
 | 
			
		||||
}
 | 
			
		||||
.tab-pane .tab{
 | 
			
		||||
    -fx-background-color: #424242;
 | 
			
		||||
    -fx-focus-color: transparent;
 | 
			
		||||
    -fx-faint-focus-color: transparent;
 | 
			
		||||
    -fx-border-radius: 0 0 0 0;
 | 
			
		||||
    -fx-border-width: 3 0 0 0;
 | 
			
		||||
    -fx-border-color: #424242;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-pane .tab:selected{
 | 
			
		||||
    -fx-background-color: #2d2d2d;
 | 
			
		||||
    -fx-focus-color: transparent;
 | 
			
		||||
    -fx-faint-focus-color: transparent;
 | 
			
		||||
    -fx-border-radius: 0 0 0 0;
 | 
			
		||||
    -fx-border-width: 3 0 0 0;
 | 
			
		||||
    -fx-border-color: #08f3ff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-pane > .tab-header-area {
 | 
			
		||||
    -fx-background-insets: 0.0;
 | 
			
		||||
    -fx-padding: 5 5 5 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-pane > .tab-header-area > .tab-header-background
 | 
			
		||||
{
 | 
			
		||||
   -fx-background-color: #424242;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.tab-pane > .tab-header-area > .headers-region > .tab {
 | 
			
		||||
    -fx-padding: 10;
 | 
			
		||||
}
 | 
			
		||||
// -=========================== TABLE ======================-
 | 
			
		||||
.table-view {
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
    -fx-background-image: url(app_logo.png);
 | 
			
		||||
    -fx-background-position: center;
 | 
			
		||||
    -fx-background-repeat: no-repeat;
 | 
			
		||||
    -fx-border-color: #00ffc9;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
}
 | 
			
		||||
.table-view .arrow {
 | 
			
		||||
   -fx-mark-color: #08f3ff ;
 | 
			
		||||
}
 | 
			
		||||
.table-view .column-header {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-border-width: 0 1 2 0;
 | 
			
		||||
    -fx-border-color: #6d8484;
 | 
			
		||||
}
 | 
			
		||||
.table-view .column-header-background .label{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-text-fill: #08f3ff;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.table-view .column-header-background, .table-view .filler{
 | 
			
		||||
    -fx-background-color: #4f4f4f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-view .table-cell{
 | 
			
		||||
    -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{
 | 
			
		||||
    -fx-background-color: -fx-table-cell-border-color, #424242;
 | 
			
		||||
    -fx-background-insets: 0, 0 0 1 0;
 | 
			
		||||
    -fx-padding: 0.0em; /* 0 */
 | 
			
		||||
    -fx-table-cell-border-color: #6d8484;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-row-cell:odd, .table-row-cell:odd:filled:selected, .table-row-cell:odd:selected{
 | 
			
		||||
    -fx-background-color: -fx-table-cell-border-color, #4f4f4f;
 | 
			
		||||
    -fx-background-insets: 0, 0 0 1 0;
 | 
			
		||||
    -fx-padding: 0.0em; /* 0 */
 | 
			
		||||
    -fx-table-cell-border-color: #6d8484;
 | 
			
		||||
}
 | 
			
		||||
// -========================== Context menu =====================-
 | 
			
		||||
.context-menu {
 | 
			
		||||
  -fx-background-color: #2d2d2d;
 | 
			
		||||
  -fx-text-fill: white;
 | 
			
		||||
  -fx-cursor: hand;
 | 
			
		||||
}
 | 
			
		||||
.context-menu .menu-item .label {
 | 
			
		||||
  -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
.context-menu .menu-item:focused .label {
 | 
			
		||||
  -fx-text-fill: #f7fafa;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.regionUpload{
 | 
			
		||||
    -fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
 | 
			
		||||
    -fx-background-color: #a2e019;
 | 
			
		||||
    -size: 24;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: 20;
 | 
			
		||||
}
 | 
			
		||||
.regionStop{
 | 
			
		||||
    -fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
 | 
			
		||||
    -fx-background-color: #fb582c;
 | 
			
		||||
    -size: 24;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: -size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.regionLamp{
 | 
			
		||||
    -fx-shape: "M12,2A7,7 0 0,1 19,9C19,11.38 17.81,13.47 16,14.74V17A1,1 0 0,1 15,18H9A1,1 0 0,1 8,17V14.74C6.19,13.47 5,11.38 5,9A7,7 0 0,1 12,2M9,21V20H15V21A1,1 0 0,1 14,22H10A1,1 0 0,1 9,21M12,4A5,5 0 0,0 7,9C7,11.05 8.23,12.81 10,13.58V16H14V13.58C15.77,12.81 17,11.05 17,9A5,5 0 0,0 12,4Z";
 | 
			
		||||
    -fx-background-color: #f7fafa;
 | 
			
		||||
    -size: 17.5;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: 12.5;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										256
									
								
								src/main/resources/res/app_light.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								src/main/resources/res/app_light.css
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,256 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
    src: url("NotoMono-Regular.ttf");
 | 
			
		||||
}
 | 
			
		||||
.root{
 | 
			
		||||
    -fx-background: #ebebeb;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button, .buttonUp, .buttonStop, .buttonSelect{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #fefefe;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
    -fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0);
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
.button:hover, .buttonStop:hover, .buttonUp:hover, .choice-box:hover, .button:focused:hover, .buttonStop:focused:hover, .buttonUp:focused:hover, .buttonSelect:focused:hover .choice-box:focused:hover{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #00caca;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.button:focused, .buttonStop:focused, .buttonUp:focused, .buttonSelect:focused, .choice-box:focused{
 | 
			
		||||
    -fx-background-color: #cccccc;
 | 
			
		||||
    -fx-border-color: #cccccc;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button:pressed, .button:pressed:hover{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #e82382;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.buttonSelect:pressed, .buttonSelect:pressed:hover{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #289de8;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.buttonUp:pressed, .buttonUp:pressed:hover{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #a2e019;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.buttonStop:pressed, .buttonStop:pressed:hover{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #fb582c;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
// -========================+ TextArea =====================-
 | 
			
		||||
.text-area{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-control-inner-background: #fefefe;
 | 
			
		||||
    -fx-font-family: "Noto Mono";
 | 
			
		||||
    -fx-border-color: #06b9bb;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.progress-bar {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-box-border: transparent;
 | 
			
		||||
}
 | 
			
		||||
.progress-bar > .track {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-box-border: transparent;
 | 
			
		||||
}
 | 
			
		||||
.progress-bar > .bar {
 | 
			
		||||
    -fx-background-color: linear-gradient(to right, #00bce4, #ff5f53);
 | 
			
		||||
    -fx-background-radius: 2;
 | 
			
		||||
    -fx-background-insets: 1 1 2 1;
 | 
			
		||||
    -fx-padding: 0.23em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog-pane {
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
}
 | 
			
		||||
.dialog-pane > .button-bar > .container{
 | 
			
		||||
    -fx-background-color: #2d2d2d;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog-pane > .label{
 | 
			
		||||
    -fx-padding: 10 5 10 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tool-bar{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.special-pane-as-border{
 | 
			
		||||
    -fx-background-color: #2c2c2c;
 | 
			
		||||
    -fx-min-height: 1;
 | 
			
		||||
}
 | 
			
		||||
// -======================== Choice box =========================-
 | 
			
		||||
.choice-box {
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #fefefe;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-mark-color: #eea11e;
 | 
			
		||||
    -fx-effect: dropshadow(three-pass-box, #b4b4b4, 2, 0, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
.choice-box > .label {
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.choice-box:pressed, .choice-box:pressed:hover{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-border-color: #eea11e;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Background color of the whole context menu
 | 
			
		||||
.choice-box .context-menu {
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Focused item background color in the list
 | 
			
		||||
.choice-box .context-menu .menu-item:focused {
 | 
			
		||||
    -fx-background-color: #eea11e;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.choice-box .context-menu .menu-item:focused .label {
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// -======================== TAB PANE =========================-
 | 
			
		||||
.tab-pane .tab SVGPath{
 | 
			
		||||
    -fx-fill: #2c2c2c;                                      // OK
 | 
			
		||||
}
 | 
			
		||||
.tab-pane .tab:selected SVGPath{
 | 
			
		||||
    -fx-fill: #289de8;                                      // OK
 | 
			
		||||
}
 | 
			
		||||
.tab-pane .tab{
 | 
			
		||||
    -fx-background-color: #fefefe;                          //ok
 | 
			
		||||
    -fx-focus-color: transparent;
 | 
			
		||||
    -fx-faint-focus-color: transparent;
 | 
			
		||||
    -fx-border-radius: 0 0 0 0;
 | 
			
		||||
    -fx-border-width: 3 0 0 0;
 | 
			
		||||
    -fx-border-color: #fefefe;                               //OK
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-pane .tab:selected{
 | 
			
		||||
    -fx-background-color: #ebebeb;                          // OK
 | 
			
		||||
    -fx-focus-color: transparent;
 | 
			
		||||
    -fx-faint-focus-color: transparent;
 | 
			
		||||
    -fx-border-radius: 0 0 0 0;
 | 
			
		||||
    -fx-border-width: 3 0 0 0;
 | 
			
		||||
    -fx-border-color: #289de8;                              // OK
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-pane > .tab-header-area {
 | 
			
		||||
    -fx-background-insets: 0.0;
 | 
			
		||||
    -fx-padding: 5 5 5 5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab-pane > .tab-header-area > .tab-header-background
 | 
			
		||||
{
 | 
			
		||||
   -fx-background-color: #fefefe;                       // OK
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.tab-pane > .tab-header-area > .headers-region > .tab {
 | 
			
		||||
    -fx-padding: 10;
 | 
			
		||||
}
 | 
			
		||||
// -=========================== TABLE ======================-
 | 
			
		||||
.table-view {
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
    -fx-background-image: url(app_logo.png);
 | 
			
		||||
    -fx-background-position: center;
 | 
			
		||||
    -fx-background-repeat: no-repeat;
 | 
			
		||||
    -fx-border-color: #06b9bb;
 | 
			
		||||
    -fx-border-radius: 3;
 | 
			
		||||
    -fx-border-width: 2;
 | 
			
		||||
}
 | 
			
		||||
.table-view .arrow {
 | 
			
		||||
   -fx-mark-color: #2c2c2c ;
 | 
			
		||||
}
 | 
			
		||||
.table-view .column-header {
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-border-width: 0 1 2 0;
 | 
			
		||||
    -fx-border-color: #b0b0b0;
 | 
			
		||||
}
 | 
			
		||||
.table-view .column-header-background .label{
 | 
			
		||||
    -fx-background-color: transparent;
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.table-view .column-header-background, .table-view .filler{
 | 
			
		||||
    -fx-background-color: #fefefe;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-view .table-cell{
 | 
			
		||||
    -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.table-row-cell, .table-row-cell:filled:selected, .table-row-cell:selected{
 | 
			
		||||
    -fx-background-color: -fx-table-cell-border-color, #d3fffd;
 | 
			
		||||
    -fx-background-insets: 0, 0 0 1 0;
 | 
			
		||||
    -fx-padding: 0.0em; /* 0 */
 | 
			
		||||
    -fx-table-cell-border-color: #b0b0b0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table-row-cell:odd, .table-row-cell:odd:filled:selected, .table-row-cell:odd:selected{
 | 
			
		||||
    -fx-background-color: -fx-table-cell-border-color, #fefefe;
 | 
			
		||||
    -fx-background-insets: 0, 0 0 1 0;
 | 
			
		||||
    -fx-padding: 0.0em; /* 0 */
 | 
			
		||||
    -fx-table-cell-border-color: #b0b0b0;
 | 
			
		||||
}
 | 
			
		||||
// -========================== Context menu =====================-
 | 
			
		||||
.context-menu {
 | 
			
		||||
  -fx-background-color: #fefefe;
 | 
			
		||||
  -fx-text-fill: white;
 | 
			
		||||
  -fx-cursor: hand;
 | 
			
		||||
}
 | 
			
		||||
.context-menu .menu-item .label {
 | 
			
		||||
  -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
.context-menu .menu-item:focused .label {
 | 
			
		||||
  -fx-text-fill: #2c2c2c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.regionUpload{
 | 
			
		||||
    -fx-shape: "M8,21V19H16V21H8M8,17V15H16V17H8M8,13V11H16V13H8M19,9H5L12,2L19,9Z";
 | 
			
		||||
    -fx-background-color: #a2e019;
 | 
			
		||||
    -size: 24;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: 20;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.regionStop{
 | 
			
		||||
    -fx-shape: "M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z";
 | 
			
		||||
    -fx-background-color: #fb582c;
 | 
			
		||||
    -size: 24;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: -size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.regionLamp {
 | 
			
		||||
    -fx-shape: "M12,2A7,7 0 0,0 5,9C5,11.38 6.19,13.47 8,14.74V17A1,1 0 0,0 9,18H15A1,1 0 0,0 16,17V14.74C17.81,13.47 19,11.38 19,9A7,7 0 0,0 12,2M9,21A1,1 0 0,0 10,22H14A1,1 0 0,0 15,21V20H9V21Z";
 | 
			
		||||
    -fx-background-color: #2c2c2c;
 | 
			
		||||
    -size: 17.5;
 | 
			
		||||
    -fx-min-height: -size;
 | 
			
		||||
    -fx-min-width: 12.5;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/res/app_logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/res/app_logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 13 KiB  | 
		Loading…
	
		Reference in a new issue