/* Copyright (C) 2013 BarD Software s.r.o This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject.wizard; import com.google.common.collect.Maps; import net.sourceforge.ganttproject.action.CancelAction; import net.sourceforge.ganttproject.action.GPAction; import net.sourceforge.ganttproject.action.OkAction; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.gui.UIFacade.Centering; import net.sourceforge.ganttproject.gui.UIFacade.Dialog; import net.sourceforge.ganttproject.gui.options.TopPanel; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * A wizard abstraction capable of managing wizard pages and showing them in the UI * according to the user actions. * * @author dbarashev (Dmitry Barashev) */ public class AbstractWizard { private final ArrayList<WizardPage> myPages = new ArrayList<WizardPage>(); private final Map<String, JComponent> myTitle2component = Maps.newHashMap(); private int myCurrentPage; private final JPanel myPagesContainer; private final CardLayout myCardLayout; private final AbstractAction myNextAction; private final AbstractAction myBackAction; private final AbstractAction myOkAction; private final AbstractAction myCancelAction; private final UIFacade myUIFacade; private final String myTitle; private final Dialog myDialog; private Runnable myOkRunnable; public AbstractWizard(UIFacade uiFacade, String title, WizardPage firstPage) { myUIFacade = uiFacade; myTitle = title; myCardLayout = new CardLayout(); myPagesContainer = new JPanel(myCardLayout); myNextAction = new GPAction("next") { @Override public void actionPerformed(ActionEvent e) { AbstractWizard.this.nextPage(); } }; myBackAction = new GPAction("back") { @Override public void actionPerformed(ActionEvent e) { AbstractWizard.this.backPage(); } }; myOkAction = new OkAction() { @Override public void actionPerformed(ActionEvent e) { onOkPressed(); } }; myCancelAction = new CancelAction(); myPagesContainer.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); myDialog = myUIFacade.createDialog(myPagesContainer, new Action[] { myBackAction, myNextAction, myOkAction, myCancelAction }, myTitle); addPageComponent(firstPage); myPages.add(firstPage); myDialog.layout(); myDialog.center(Centering.WINDOW); adjustButtonState(); } private void nextPage() { assert myCurrentPage + 1 < myPages.size() : "It is a bug: we have no next page while Next button is enabled and has been pressed"; getCurrentPage().setActive(null); WizardPage nextPage = myPages.get(myCurrentPage + 1); if (myTitle2component.get(nextPage.getTitle()) == null) { addPageComponent(nextPage); } myCurrentPage++; nextPage.setActive(this); myCardLayout.show(myPagesContainer, nextPage.getTitle()); myDialog.center(Centering.WINDOW); myDialog.layout(); adjustButtonState(); } private void addPageComponent(WizardPage page) { if (myTitle2component.get(page.getTitle()) == null) { JComponent c = wrapePageComponent(page.getTitle(), page.getComponent()); myPagesContainer.add(c, page.getTitle()); myTitle2component.put(page.getTitle(), c); } } private JComponent wrapePageComponent(String title, JComponent c) { JPanel pagePanel = new JPanel(new BorderLayout()); JComponent titlePanel = TopPanel.create(title, null); pagePanel.add(titlePanel, BorderLayout.NORTH); c.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); pagePanel.add(c, BorderLayout.CENTER); return pagePanel; } private void backPage() { if (myCurrentPage > 0) { getCurrentPage().setActive(null); myCurrentPage--; myCardLayout.show(myPagesContainer, getCurrentPage().getTitle()); getCurrentPage().setActive(this); } //myDialog.center(Centering.WINDOW); adjustButtonState(); } public void show() { myCardLayout.first(myPagesContainer); getCurrentPage().setActive(this); adjustButtonState(); myDialog.center(Centering.SCREEN); myDialog.show(); } private void adjustButtonState() { myBackAction.setEnabled(myCurrentPage > 0); myNextAction.setEnabled(myCurrentPage < myPages.size() - 1); myOkAction.setEnabled(canFinish()); } protected void onOkPressed() { myOkRunnable.run(); } protected boolean canFinish() { return myOkRunnable != null; } private boolean isExistingNextPage(WizardPage page) { if (page == null) { return false; } int idxPage = myPages.indexOf(page); return (idxPage != -1 && myCurrentPage == idxPage - 1); } /** * Active wizard page can call this method to set a next page. * * @param page next page */ public void setNextPage(WizardPage page) { boolean isExisting = isExistingNextPage(page); if (!isExisting) { List<WizardPage> tail = myPages.subList(myCurrentPage + 1, myPages.size()); for (WizardPage tailPage : tail) { JComponent component = myTitle2component.remove(tailPage.getTitle()); if (component != null) { myPagesContainer.remove(component); } } tail.clear(); if (page != null) { myPages.add(page); } } adjustButtonState(); } /** * Wizard pages or specific wizard implementations can call this method to set an * action to be called when user clicks OK. This makes OK button enabled. * * @param action action to be called on OK */ public void setOkAction(Runnable action) { myOkRunnable = action; adjustButtonState(); } private WizardPage getCurrentPage() { return myPages.get(myCurrentPage); } public UIFacade getUIFacade() { return myUIFacade; } public Dialog getDialog() { return myDialog; } }