From c041f6baaa0b97c98c9638f70cdf792a6821321a Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Thu, 28 May 2020 20:08:57 +0100 Subject: [PATCH] skeleton of setup wizard --- gui/griffon-app/conf/Config.groovy | 5 ++ .../muwire/gui/wizard/WizardController.groovy | 47 ++++++++++++ gui/griffon-app/lifecycle/Ready.groovy | 56 +++++++++------ .../com/muwire/gui/wizard/WizardModel.groovy | 32 +++++++++ .../com/muwire/gui/wizard/WizardView.groovy | 63 ++++++++++++++++ .../muwire/gui/wizard/DirectoriesStep.groovy | 72 +++++++++++++++++++ .../com/muwire/gui/wizard/NicknameStep.groovy | 34 +++++++++ .../com/muwire/gui/wizard/WizardStep.groovy | 22 ++++++ 8 files changed, 310 insertions(+), 21 deletions(-) create mode 100644 gui/griffon-app/controllers/com/muwire/gui/wizard/WizardController.groovy create mode 100644 gui/griffon-app/models/com/muwire/gui/wizard/WizardModel.groovy create mode 100644 gui/griffon-app/views/com/muwire/gui/wizard/WizardView.groovy create mode 100644 gui/src/main/groovy/com/muwire/gui/wizard/DirectoriesStep.groovy create mode 100644 gui/src/main/groovy/com/muwire/gui/wizard/NicknameStep.groovy create mode 100644 gui/src/main/groovy/com/muwire/gui/wizard/WizardStep.groovy diff --git a/gui/griffon-app/conf/Config.groovy b/gui/griffon-app/conf/Config.groovy index 8638ff80..be1d228d 100644 --- a/gui/griffon-app/conf/Config.groovy +++ b/gui/griffon-app/conf/Config.groovy @@ -141,4 +141,9 @@ mvcGroups { view = 'com.muwire.gui.SignView' controller = 'com.muwire.gui.SignController' } + 'wizard' { + model = 'com.muwire.gui.wizard.WizardModel' + view = 'com.muwire.gui.wizard.WizardView' + controller = 'com.muwire.gui.wizard.WizardController' + } } diff --git a/gui/griffon-app/controllers/com/muwire/gui/wizard/WizardController.groovy b/gui/griffon-app/controllers/com/muwire/gui/wizard/WizardController.groovy new file mode 100644 index 00000000..e2d817e3 --- /dev/null +++ b/gui/griffon-app/controllers/com/muwire/gui/wizard/WizardController.groovy @@ -0,0 +1,47 @@ +package com.muwire.gui.wizard + +import griffon.core.artifact.GriffonController +import griffon.core.controller.ControllerAction +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor +import javax.annotation.Nonnull +import javax.swing.JOptionPane + +@ArtifactProviderFor(GriffonController) +class WizardController { + @MVCMember @Nonnull + WizardModel model + @MVCMember @Nonnull + WizardView view + + @ControllerAction + void previous() { + model.currentStep-- + recalcButtons() + view.updateLayout() + } + + @ControllerAction + void next() { + def errors = model.steps[model.currentStep].validate() + if (errors) { + String errorMessage = String.join("\n", errors) + JOptionPane.showMessageDialog(model.parent, errorMessage, "Invalid Input", JOptionPane.ERROR_MESSAGE) + } else { + model.currentStep++ + recalcButtons() + view.updateLayout() + } + } + + @ControllerAction + void finish() { + + } + + private void recalcButtons() { + model.previousButtonEnabled = model.currentStep > 0 + model.nextButtonEnabled = model.steps.size() > (model.currentStep + 1) + model.finishButtonEnabled = model.steps.size() == (model.currentStep + 1) + } +} \ No newline at end of file diff --git a/gui/griffon-app/lifecycle/Ready.groovy b/gui/griffon-app/lifecycle/Ready.groovy index e31d2953..13cb77c7 100644 --- a/gui/griffon-app/lifecycle/Ready.groovy +++ b/gui/griffon-app/lifecycle/Ready.groovy @@ -54,27 +54,41 @@ class Ready extends AbstractLifecycleHandler { } else { log.info("creating new properties") props = new MuWireSettings() - props.incompleteLocation = new File(home, "incompletes") - props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter")) - props.updateType = System.getProperty("updateType","jar") - props.setNickname(selectNickname()) - - - def portableDownloads = System.getProperty("portable.downloads") - if (portableDownloads != null) { - props.downloadLocation = new File(portableDownloads) - } else { - def chooser = new JFileChooser() - chooser.setFileHidingEnabled(false) - chooser.setDialogTitle("Select a directory where downloads will be saved") - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY) - int rv = chooser.showOpenDialog(null) - if (rv != JFileChooser.APPROVE_OPTION) { - JOptionPane.showMessageDialog(null, "MuWire will now exit") - System.exit(0) - } - props.downloadLocation = chooser.getSelectedFile() - } + boolean embeddedRouterAvailable = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter")) + + def parent = application.windowManager.findWindow("event-list") + Properties i2pProps = new Properties() + + def params = [:] + params['parent'] = parent + params['embeddedRouterAvailable'] = embeddedRouterAvailable + params['muSettings'] = props + params['i2pProps'] = i2pProps + + application.mvcGroupManager.createMVCGroup("wizard", params) + + +// props.incompleteLocation = new File(home, "incompletes") +// props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter")) +// props.updateType = System.getProperty("updateType","jar") +// props.setNickname(selectNickname()) +// +// +// def portableDownloads = System.getProperty("portable.downloads") +// if (portableDownloads != null) { +// props.downloadLocation = new File(portableDownloads) +// } else { +// def chooser = new JFileChooser() +// chooser.setFileHidingEnabled(false) +// chooser.setDialogTitle("Select a directory where downloads will be saved") +// chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY) +// int rv = chooser.showOpenDialog(null) +// if (rv != JFileChooser.APPROVE_OPTION) { +// JOptionPane.showMessageDialog(null, "MuWire will now exit") +// System.exit(0) +// } +// props.downloadLocation = chooser.getSelectedFile() +// } propsFile.withPrintWriter("UTF-8", { props.write(it) diff --git a/gui/griffon-app/models/com/muwire/gui/wizard/WizardModel.groovy b/gui/griffon-app/models/com/muwire/gui/wizard/WizardModel.groovy new file mode 100644 index 00000000..c5c1e579 --- /dev/null +++ b/gui/griffon-app/models/com/muwire/gui/wizard/WizardModel.groovy @@ -0,0 +1,32 @@ +package com.muwire.gui.wizard + +import java.awt.Component + +import com.muwire.core.MuWireSettings + +import griffon.core.artifact.GriffonModel +import griffon.transform.Observable +import griffon.metadata.ArtifactProviderFor + +@ArtifactProviderFor(GriffonModel) +class WizardModel { + Component parent + boolean embeddedRouterAvailable + MuWireSettings muSettings + Properties i2pProps + + final List steps = [new NicknameStep(), + new DirectoriesStep()] + int currentStep + + @Observable boolean finishButtonEnabled + @Observable boolean previousButtonEnabled + @Observable boolean nextButtonEnabled + + void mvcGroupInit(Map args) { + currentStep = 0 + previousButtonEnabled = false + nextButtonEnabled = steps.size() > (currentStep + 1) + finishButtonEnabled = steps.size() == currentStep + 1 + } +} \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/wizard/WizardView.groovy b/gui/griffon-app/views/com/muwire/gui/wizard/WizardView.groovy new file mode 100644 index 00000000..50fd4199 --- /dev/null +++ b/gui/griffon-app/views/com/muwire/gui/wizard/WizardView.groovy @@ -0,0 +1,63 @@ +package com.muwire.gui.wizard + +import griffon.core.artifact.GriffonView +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor + +import javax.swing.JDialog +import javax.swing.SwingConstants + +import java.awt.BorderLayout +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent + +import javax.annotation.Nonnull + +@ArtifactProviderFor(GriffonView) +class WizardView { + @MVCMember @Nonnull + FactoryBuilderSupport builder + @MVCMember @Nonnull + WizardModel model + + def dialog + def p + + void initUI() { + dialog = new JDialog(model.parent, "Setup Wizard", true) + + p = builder.panel { + borderLayout() + panel (id : "cards-panel", constraints : BorderLayout.CENTER) { + cardLayout() + model.steps.each { + it.buildUI(builder) + } + } + panel (constraints : BorderLayout.SOUTH) { + button(text : "Previous", enabled : bind {model.previousButtonEnabled}, previousAction) + button(text : "Next", enabled : bind {model.nextButtonEnabled}, nextAction) + button(text : "Finish", enabled : bind {model.finishButtonEnabled}, finishAction) + } + } + } + + void updateLayout() { + String constraints = model.steps[model.currentStep].getConstraint() + def cardsPanel = builder.getVariable("cards-panel") + cardsPanel.getLayout().show(cardsPanel, constraints) + } + + void mvcGroupInit(Map args) { + dialog.getContentPane().add(p) + dialog.pack() + dialog.setLocationRelativeTo(model.parent) + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) + dialog.addWindowListener( new WindowAdapter() { + public void windowClosed(WindowEvent e) { + mvcGroup.destroy() + } + }) + dialog.show() + } +} \ No newline at end of file diff --git a/gui/src/main/groovy/com/muwire/gui/wizard/DirectoriesStep.groovy b/gui/src/main/groovy/com/muwire/gui/wizard/DirectoriesStep.groovy new file mode 100644 index 00000000..53de38fc --- /dev/null +++ b/gui/src/main/groovy/com/muwire/gui/wizard/DirectoriesStep.groovy @@ -0,0 +1,72 @@ +package com.muwire.gui.wizard + +import javax.swing.JFileChooser + +import com.muwire.core.MuWireSettings + +class DirectoriesStep extends WizardStep { + + def downloadLocationField + def incompleteLocationField + def downloadLocationButton + def incompleteLocationButton + + public DirectoriesStep(String constraint) { + super("directories") + } + + @Override + protected void buildUI(FactoryBuilderSupport builder) { + File defaultDownloadLocation = new File(System.getProperty("user.home"), "MuWire Downloads") + File defaultIncompleteLocation = new File(System.getProperty("user.home"), "MuWire Incompletes") + + builder.panel(constraints : getConstraint()) { + gridBagLayout() + label(text : "Select directories for saving downloaded and incomplete files. They will be created if they do not already exist", + constraints : gbc(gridx: 0, gridy: 0, gridwidth : 2)) + + label(text : "Directory for saving downloaded files", constraints : gbc(gridx:0, gridy: 1)) + downloadLocationField = textField(text : defaultDownloadLocation.getAbsolutePath(), constraints : gbc(gridx : 0, gridy : 2)) + downloadLocationButton = button(text : "Choose", constraints : gbc(gridx: 1, gridy: 2), actionPerformed : showDownloadChooser) + label(text : "Directory for storing incomplete files", constraints : gbc(gridx:0, gridy: 3)) + incompleteLocationField = textField(text : defaultIncompleteLocation.getAbsolutePath(), constraints : gbc(gridx:0, gridy:4)) + incompleteLocationButton = button(text : "Choose", constraints : gbc(gridx: 1, gridy: 4), actionPerformed : showIncompleteChooser) + } + } + + @Override + protected List validate() { + // TODO Auto-generated method stub + return null + } + + @Override + protected void apply(MuWireSettings muSettings, Properties i2pSettings) { + // TODO Auto-generated method stub + + } + + def showDownloadChooser = { + String text = chooseFile("Select directory for downloaded files") + if (text != null) + downloadLocationField.text = text + } + + def showIncompleteChooser = { + String text = chooseFile("Select directory for incomplete files") + if (text != null) + incompleteLocationField.text = text + } + + private String chooseFile(String title) { + def chooser = new JFileChooser() + chooser.setFileHidingEnabled(false) + chooser.setDialogTitle(title) + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY) + int rv = chooser.showOpenDialog(null) + if (rv == JFileChooser.APPROVE_OPTION) + return chooser.getSelectedFile().getAbsolutePath() + else + return null + } +} diff --git a/gui/src/main/groovy/com/muwire/gui/wizard/NicknameStep.groovy b/gui/src/main/groovy/com/muwire/gui/wizard/NicknameStep.groovy new file mode 100644 index 00000000..a2ae904a --- /dev/null +++ b/gui/src/main/groovy/com/muwire/gui/wizard/NicknameStep.groovy @@ -0,0 +1,34 @@ +package com.muwire.gui.wizard + +import com.muwire.core.Constants +import com.muwire.core.MuWireSettings +import com.muwire.core.util.DataUtil + +class NicknameStep extends WizardStep { + + volatile def nickField + + public NicknameStep() { + super("nickname") + } + + @Override + protected void buildUI(FactoryBuilderSupport builder) { + builder.panel(constraints : getConstraint()) { + label(text: "Select a nickname") + nickField = textField(columns: 30) + } + } + + @Override + protected List validate() { + if (!DataUtil.isValidName(nickField.text)) + return ["Nickname cannot contain any of ${Constants.INVALID_NICKNAME_CHARS} and must be no longer than ${Constants.MAX_NICKNAME_LENGTH} characters. Choose another."] + null + } + + @Override + protected void apply(MuWireSettings muSettings, Properties i2pSettings) { + muSettings.nickname = nickField.text + } +} diff --git a/gui/src/main/groovy/com/muwire/gui/wizard/WizardStep.groovy b/gui/src/main/groovy/com/muwire/gui/wizard/WizardStep.groovy new file mode 100644 index 00000000..96f366d7 --- /dev/null +++ b/gui/src/main/groovy/com/muwire/gui/wizard/WizardStep.groovy @@ -0,0 +1,22 @@ +package com.muwire.gui.wizard + +import com.muwire.core.MuWireSettings + +abstract class WizardStep { + + final String constraint + + protected WizardStep(String constraint) { + this.constraint = constraint + } + + + protected abstract void buildUI(FactoryBuilderSupport builder) + + /** + * @return list of errors, null if validation is successful + */ + protected abstract List validate() + + protected abstract void apply(MuWireSettings muSettings, Properties i2pSettings) +}