diff --git a/src/zutil/data/wizard1.jpg b/src/zutil/data/wizard1.jpg new file mode 100644 index 0000000..69e27e5 Binary files /dev/null and b/src/zutil/data/wizard1.jpg differ diff --git a/src/zutil/data/wizard2.jpg b/src/zutil/data/wizard2.jpg new file mode 100644 index 0000000..1e167bd Binary files /dev/null and b/src/zutil/data/wizard2.jpg differ diff --git a/src/zutil/data/wizard3.png b/src/zutil/data/wizard3.png new file mode 100644 index 0000000..8c0b7f1 Binary files /dev/null and b/src/zutil/data/wizard3.png differ diff --git a/src/zutil/ui/JImagePanel.java b/src/zutil/ui/JImagePanel.java new file mode 100644 index 0000000..64c9431 --- /dev/null +++ b/src/zutil/ui/JImagePanel.java @@ -0,0 +1,92 @@ +package zutil.ui; + +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.JPanel; + +import zutil.FileFinder; +import zutil.image.ImageUtil; + +/** + * This class is a panel with a background image + * @author Ziver + * + */ +public class JImagePanel extends JPanel { + private static final long serialVersionUID = 1L; + + /** The original image */ + private BufferedImage org_img; + /** An resized copy of the image */ + private BufferedImage resized_img; + /** If the image should be scaled to the size of the component */ + private boolean scale = true; + /** If the aspect ratio is to be kept */ + private boolean keep_aspect = true; + + public JImagePanel(){} + + /** + * Creates a new instance of this class + * + * @param img is the path to the image + */ + public JImagePanel(String img) throws IOException { + this(ImageIO.read( FileFinder.find( img ) )); + } + + /** + * Creates a new instance of this class + * + * @param img is the image to use + */ + public JImagePanel(BufferedImage img) { + this.org_img = img; + } + + /** + * Sets if the image should be scaled to the size of the panel + * @param b true of false + */ + public void scale(boolean b){ + scale = b; + } + + /** + * Sets the background image + * + * @param img is the image that will be used + */ + public void setImage(BufferedImage img){ + this.org_img = img; + this.resized_img = null; + } + + /** + * If the panel should keep the aspect ratio in the image when resizing + */ + public void keepAspect(boolean b){ + keep_aspect = b; + } + + public void paintComponent(Graphics g) { + if(org_img == null) + super.paintComponent(g); + else if(scale){ + if(resized_img == null || + this.getWidth() != resized_img.getWidth() || + this.getHeight() != resized_img.getHeight()){ + resized_img = ImageUtil.scale(org_img, this.getWidth(), this.getHeight(), keep_aspect); + } + g.drawImage(resized_img, 0, 0, null); + } + else{ + g.drawImage(org_img, 0, 0, null); + } + + } + +} \ No newline at end of file diff --git a/src/zutil/ui/wizard/Bundle.properties b/src/zutil/ui/wizard/Bundle.properties new file mode 100644 index 0000000..7ad7eb9 --- /dev/null +++ b/src/zutil/ui/wizard/Bundle.properties @@ -0,0 +1,10 @@ +# To change this template, choose Tools | Templates +# and open the template in the editor. + +WizardManager.error.text= +WizardManager.steps.text=Steps: +WizardManager.cancel.text=Cancel +WizardManager.finish.text=Finish +WizardManager.next.text=Next > +WizardManager.back.text=< Back +WizardManager.pageTitle.text= diff --git a/src/zutil/ui/wizard/Wizard.java b/src/zutil/ui/wizard/Wizard.java new file mode 100644 index 0000000..705e339 --- /dev/null +++ b/src/zutil/ui/wizard/Wizard.java @@ -0,0 +1,345 @@ +package zutil.ui.wizard; + +import java.awt.Color; +import java.awt.EventQueue; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.ResourceBundle; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.GroupLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.WindowConstants; +import javax.swing.GroupLayout.Alignment; +import javax.swing.LayoutStyle.ComponentPlacement; + +import zutil.FileFinder; +import zutil.MultiPrintStream; +import zutil.struct.HistoryList; +import zutil.ui.JImagePanel; +import zutil.ui.wizard.listener.BlockingWizardListener; + +/** + * This class manages the whole wizard + * + * @author Ziver + */ +public class Wizard implements ActionListener{ + public static final boolean DEBUG = true; + private static final long serialVersionUID = 1L; + + /** Some defoult backgrounds for the sidebar */ + public static final String BACKGROUND_1 = "zutil/data/wizard1.jpg"; + public static final String BACKGROUND_2 = "zutil/data/wizard2.jpg"; + public static final String BACKGROUND_3 = "zutil/data/wizard3.png"; + + /** An list with all the previous pages and the current at the beginning */ + private HistoryList pages; + /** HashMap containing all the selected values */ + private HashMap values; + /** The general component listener */ + private WizardActionHandler handler; + /** This is the user listener that handles all the values after the wizard */ + private WizardListener listener; + + /** This is the old validation fail, this is needed for reseting purposes */ + private ValidationFail oldFail; + + + private Wizard(WizardListener listener){ + this(listener, null); + } + + /** + * Creates a new Wizard + */ + public Wizard(WizardListener listener, WizardPage start){ + this(listener, start, BACKGROUND_1); + } + + /** + * Creates a new Wizard + * + * @param start is the first page in the wizard + * @param bg is the background image to use + */ + public Wizard(WizardListener listener, final WizardPage start, final String bg){ + try { + this.listener = listener; + pages = new HistoryList(); + values = new HashMap(); + handler = new WizardActionHandler( values ); + + // GUI + frame = new JFrame(); + initComponents(); + sidebar.scale( false ); + + // add action listener to the buttons + back.addActionListener( this ); + next.addActionListener( this ); + cancel.addActionListener( this ); + finish.addActionListener( this ); + + // Set the image in the sidebar + sidebar.setImage(ImageIO.read( FileFinder.getInputStream( bg ) )); + + // add the first page + pages.add( start ); + displayWizardPage( start ); + + } catch (Exception e) { + e.printStackTrace(MultiPrintStream.out); + } + } + + /** + * Sets the title of the Wizard + */ + public void setTitle(String s){ + frame.setTitle(s); + } + + /** + * Sets the size of the Wizard frame + * + * @param w is the width + * @param h is the height + */ + public void setSize(int w, int h){ + frame.setSize(w, h); + } + + /** + * Displays the wizard + */ + public void start(){ + frame.setVisible(true); + } + + /** + * @return the JFrame used for the wizard + */ + public JFrame getFrame(){ + return frame; + } + + /** + * Set the current WizardPage + * + * @param page is the page to be displayed + */ + protected void displayWizardPage(WizardPage page){ + pageContainer.getViewport().setView(page); + pageTitle.setText( page.getPageDescription() ); + } + + public void actionPerformed(ActionEvent e) { + // Back Button + if(e.getSource() == back){ + WizardPage page = pages.getPrevious(); + displayWizardPage( page ); + if(pages.get(0) == page){ + back.setEnabled( false ); + } + } + // Next Button and Finish Button + else if(e.getSource() == next || e.getSource() == finish){ + WizardPage page = pages.getCurrent(); + page.registerValues( handler ); + if(DEBUG) MultiPrintStream.out.println(values); + + ValidationFail fail = page.validate( values ); + if(fail != null){ + // reset old fail + if(oldFail != null) oldFail.getSource().setBorder( BorderFactory.createEmptyBorder() ); + if(fail.getSource() != null) + fail.getSource().setBorder( BorderFactory.createLineBorder(Color.RED) ); + //pageStatus.setText( fail.getMessage() ); + } + else if(e.getSource() == finish){ + frame.dispose(); + listener.onFinished( values ); + } + else if(e.getSource() == next){ + WizardPage nextPage = page.getNextPage( values ); + if(nextPage == null){ + frame.dispose(); + listener.onCancel(page, values ); + return; + } + pages.add( nextPage ); + displayWizardPage( nextPage ); + back.setEnabled( true ); + if( nextPage.isFinalPage() ){ + next.setEnabled( false ); + finish.setEnabled( true ); + } + } + } + // Cancel Button + else if(e.getSource() == cancel){ + frame.dispose(); + listener.onCancel(pages.getCurrent(), values ); + } + } + + + private void initComponents() { + cancel = new JButton(); + finish = new JButton(); + next = new JButton(); + back = new JButton(); + + JSeparator separator = new JSeparator(); // NOI18N + sidebar = new JImagePanel(); + JLabel steps = new JLabel(); + JSeparator separator2 = new JSeparator(); + pageTitle = new JLabel(); + JSeparator separator3 = new JSeparator(); + error = new JLabel(); + pageContainer = new JScrollPane(); + + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + ResourceBundle bundle = ResourceBundle.getBundle("zutil/ui/wizard/Bundle"); + cancel.setText(bundle.getString("WizardManager.cancel.text")); // NOI18N + + finish.setText(bundle.getString("WizardManager.finish.text")); // NOI18N + finish.setEnabled(false); + + next.setText(bundle.getString("WizardManager.next.text")); // NOI18N + + back.setText(bundle.getString("WizardManager.back.text")); // NOI18N + back.setEnabled(false); + + sidebar.setBorder(BorderFactory.createEtchedBorder()); + + steps.setText(bundle.getString("WizardManager.steps.text")); // NOI18N + + GroupLayout sidebarLayout = new GroupLayout(sidebar); + sidebar.setLayout(sidebarLayout); + sidebarLayout.setHorizontalGroup( + sidebarLayout.createParallelGroup(Alignment.LEADING) + .addGroup(sidebarLayout.createSequentialGroup() + .addContainerGap() + .addGroup(sidebarLayout.createParallelGroup(Alignment.LEADING) + .addComponent(separator2, GroupLayout.DEFAULT_SIZE, 151, Short.MAX_VALUE) + .addComponent(steps, GroupLayout.DEFAULT_SIZE, 151, Short.MAX_VALUE)) + .addContainerGap()) + ); + sidebarLayout.setVerticalGroup( + sidebarLayout.createParallelGroup(Alignment.LEADING) + .addGroup(sidebarLayout.createSequentialGroup() + .addGap(23, 23, 23) + .addComponent(steps) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(separator2, GroupLayout.PREFERRED_SIZE, 10, GroupLayout.PREFERRED_SIZE) + .addContainerGap(347, Short.MAX_VALUE)) + ); + + pageTitle.setFont(new Font("Tahoma", 1, 18)); + pageTitle.setText(bundle.getString("WizardManager.pageTitle.text")); // NOI18N + + error.setFont(new Font("Times New Roman", 1, 12)); + error.setForeground(new Color(255, 0, 0)); + error.setText(bundle.getString("WizardManager.error.text")); // NOI18N + + pageContainer.setBorder(null); + + GroupLayout layout = new GroupLayout(frame.getContentPane()); + frame.getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(error, GroupLayout.DEFAULT_SIZE, 371, Short.MAX_VALUE) + .addPreferredGap(ComponentPlacement.UNRELATED) + .addComponent(back) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(next) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(finish) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(cancel) + .addContainerGap()) + .addGroup(layout.createSequentialGroup() + .addComponent(sidebar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(6, 6, 6) + .addGroup(layout.createParallelGroup(Alignment.TRAILING) + .addComponent(separator, GroupLayout.DEFAULT_SIZE, 492, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(ComponentPlacement.UNRELATED) + .addComponent(pageTitle, GroupLayout.DEFAULT_SIZE, 492, Short.MAX_VALUE) + .addPreferredGap(ComponentPlacement.RELATED))) + .addGap(2, 2, 2)) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(Alignment.LEADING) + .addComponent(separator3, GroupLayout.DEFAULT_SIZE, 494, Short.MAX_VALUE) + .addComponent(pageContainer, GroupLayout.DEFAULT_SIZE, 494, Short.MAX_VALUE))))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(Alignment.TRAILING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(Alignment.TRAILING) + .addComponent(sidebar, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(Alignment.LEADING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(pageTitle, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(separator3, GroupLayout.PREFERRED_SIZE, 2, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(pageContainer, GroupLayout.DEFAULT_SIZE, 341, Short.MAX_VALUE) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(separator, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(Alignment.BASELINE) + .addComponent(cancel) + .addComponent(finish) + .addComponent(next) + .addComponent(back) + .addComponent(error)) + .addContainerGap()) + ); + + frame.pack(); + } + + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + final BlockingWizardListener listener = new BlockingWizardListener(); + EventQueue.invokeLater(new Runnable() { + public void run() { + Wizard wizard = new Wizard(listener); + wizard.start(); + } + }); + MultiPrintStream.out.dump( listener.getValues() ); + + } + + // Variables declaration - do not modify + private JLabel error; + private JButton back; + private JButton cancel; + private JButton finish; + private JButton next; + private JScrollPane pageContainer; + private JLabel pageTitle; + private JImagePanel sidebar; + private JFrame frame; + // End of variables declaration + +} diff --git a/src/zutil/ui/wizard/WizardActionHandler.java b/src/zutil/ui/wizard/WizardActionHandler.java new file mode 100644 index 0000000..7e740c3 --- /dev/null +++ b/src/zutil/ui/wizard/WizardActionHandler.java @@ -0,0 +1,101 @@ +package zutil.ui.wizard; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.HashMap; + +import javax.swing.JList; +import javax.swing.JToggleButton; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.JTextComponent; + +public class WizardActionHandler implements ActionListener, FocusListener, ListSelectionListener{ + private HashMap values; + + public WizardActionHandler(HashMap values){ + this.values = values; + } + + public void actionPerformed(ActionEvent e) { + event(e); + } + public void focusGained(FocusEvent e) { + event(e); + } + public void focusLost(FocusEvent e) { + event(e); + } + public void event(AWTEvent e){ + if(e.getSource() instanceof Component) + registerValue( (Component)e.getSource() ); + } + public void valueChanged(ListSelectionEvent e) { + if(e.getSource() instanceof Component) + registerValue( (Component)e.getSource() ); + } + + public void registerListener(Component c){ + /** + * JToggleButton + * JCheckBox + * JRadioButton + */ + if(c instanceof JToggleButton){ + JToggleButton o = (JToggleButton) c; + o.addActionListener( this ); + } + /** + * JEditorPane + * JTextArea + * JTextField + */ + else if(c instanceof JTextComponent){ + JTextComponent o = (JTextComponent) c; + o.addFocusListener( this ); + } + /** + * JList + */ + else if(c instanceof JList){ + JList o = (JList) c; + o.addListSelectionListener( this ); + } + } + + /** + * Registers the state of the event source + * @param e is the event + */ + public void registerValue(Component c) { + /** + * JToggleButton + * JCheckBox + * JRadioButton + */ + if(c instanceof JToggleButton){ + JToggleButton o = (JToggleButton) c; + values.put( o.getName() , o.isSelected() ); + } + /** + * JEditorPane + * JTextArea + * JTextField + */ + else if(c instanceof JTextComponent){ + JTextComponent o = (JTextComponent) c; + values.put( o.getName() , o.getText() ); + } + /** + * JList + */ + else if(c instanceof JList){ + JList o = (JList) c; + values.put( o.getName() , o.getSelectedValue() ); + } + } +} diff --git a/src/zutil/ui/wizard/WizardListener.java b/src/zutil/ui/wizard/WizardListener.java new file mode 100644 index 0000000..0ee05a6 --- /dev/null +++ b/src/zutil/ui/wizard/WizardListener.java @@ -0,0 +1,21 @@ +package zutil.ui.wizard; + +import java.util.HashMap; + +public interface WizardListener { + + /** + * Will be called when the cancel button is pressed + * + * @param page is the WizardPage where the cancel button was pressed + * @param values is the values until now + */ + public void onCancel(WizardPage page, HashMap values); + + /** + * Will be called when the wizard is finished + * + * @param values is the values until now + */ + public void onFinished(HashMap values); +} diff --git a/src/zutil/ui/wizard/WizardPage.java b/src/zutil/ui/wizard/WizardPage.java new file mode 100644 index 0000000..b2528e5 --- /dev/null +++ b/src/zutil/ui/wizard/WizardPage.java @@ -0,0 +1,129 @@ +package zutil.ui.wizard; + +import java.awt.Component; +import java.util.HashMap; +import java.util.LinkedList; + +import javax.swing.JComponent; +import javax.swing.JPanel; + +/** + * This abstract class is one step in the wizard + * + * @author Ziver + */ +public abstract class WizardPage extends JPanel{ + private static final long serialVersionUID = 1L; + + /** contains the components whom values will be saved */ + private LinkedList components; + /** if this is the last page in the wizard */ + private boolean lastPage = false; + + public WizardPage(){ + components = new LinkedList(); + } + + /** + * Register a component whom the value will be saved with + * the key that is what getName returns from the component + * and passed on to the other pages. + * + * @param c is the component + */ + public void registerComponent(Component c){ + components.add( c ); + } + + /** + * Sets if this is the last page in the wizard, + * Should be called as early as possible. + */ + public void setFinalPage(boolean b){ + lastPage = b; + } + + /** + * @return is this is the last page in the wizard + */ + public boolean isFinalPage(){ + return lastPage; + } + + /** + * @return the next page in the wizard, + * is called when the next button is pressed, + * return null to end the wizard + */ + public abstract WizardPage getNextPage(HashMap values); + + /** + * @return a very short description of this page + */ + public abstract String getPageDescription(); + + + /** + * This method is called when the next button is pressed + * and the input values are going to be validated. + * + * @param values is the values until now + * @return a ValidateFail object or null if the validation passed + */ + public ValidationFail validate(HashMap values){ + return null; + } + + /** + * Will be called after the validation passes and will + * save all the states of the registered components + * + * @param listener is the object that handles the save process + */ + public void registerValues(WizardActionHandler listener){ + for(Component c : components){ + listener.registerValue( c ); + } + } +} + +/** + * This class is for failed validations + * + * @author Ziver + */ +class ValidationFail{ + /** The component that failed the validation */ + private JComponent source; + /** An message to the user about the fault */ + private String msg; + + /** + * Creates an ValidationFail object + * + * @param c is the component that failed the validation + * @param msg is a message to the user about the fault + */ + public ValidationFail(String msg){ + this(null, msg); + } + + /** + * Creates an ValidationFail object + * + * @param c is the component that failed the validation + * @param msg is a message to the user about the fault + */ + public ValidationFail(JComponent c, String msg){ + this.source = c; + this.msg = msg; + } + + public JComponent getSource(){ + return source; + } + + public String getMessage(){ + return msg; + } +} diff --git a/src/zutil/ui/wizard/listener/BlockingWizardListener.java b/src/zutil/ui/wizard/listener/BlockingWizardListener.java new file mode 100644 index 0000000..083c2d8 --- /dev/null +++ b/src/zutil/ui/wizard/listener/BlockingWizardListener.java @@ -0,0 +1,45 @@ +package zutil.ui.wizard.listener; + +import java.util.HashMap; + +import zutil.ui.wizard.WizardListener; +import zutil.ui.wizard.WizardPage; + +/** + * This listener class will block until the wizard is finished + * and than return the values of the wizard + * + * @author Ziver + */ +public class BlockingWizardListener implements WizardListener{ + private HashMap values; + + /** + * Will block until the wizard is finished + * + * @return the values with a extra parameter "canceled" set + * as a boolean if the wizard was canceled and "canceledPage" + * witch is the page where the cancel button was pressed + */ + public HashMap getValues(){ + while(values == null){ + try{ + Thread.sleep(100); + }catch(Exception e){} + } + return values; + } + + + public void onCancel(WizardPage page, HashMap values) { + values.put("canceled", Boolean.TRUE); + values.put("canceledPage", page); + this.values = values; + } + + public void onFinished(HashMap values) { + values.put("canceled", Boolean.FALSE); + this.values = values; + } + +} diff --git a/src/zutil/ui/wizard/pages/SummaryPage.java b/src/zutil/ui/wizard/pages/SummaryPage.java new file mode 100644 index 0000000..86f4788 --- /dev/null +++ b/src/zutil/ui/wizard/pages/SummaryPage.java @@ -0,0 +1,47 @@ +package zutil.ui.wizard.pages; + +import java.util.HashMap; + +import javax.swing.JTextArea; + +import zutil.ui.wizard.WizardPage; + +/** + * This class will show a summary of all the values + * in the wizard + * + * @author Ziver + * + */ +public class SummaryPage extends WizardPage{ + private static final long serialVersionUID = 1L; + + public SummaryPage(HashMap values){ + this.setFinalPage( true ); + + JTextArea summary = new JTextArea(); + summary.setEditable(false); + summary.setEnabled(false); + this.add( summary ); + + StringBuffer tmp = new StringBuffer(); + for(String key : values.keySet()){ + tmp.append(key); + tmp.append(": "); + tmp.append(values.get( key )); + tmp.append("\n"); + } + summary.setText( tmp.toString() ); + } + + @Override + public WizardPage getNextPage(HashMap values) { + return null; + } + + @Override + public String getPageDescription() { + return "Summary"; + } + +}