/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tutorial.gui;
import java.awt.event.ActionEvent;
import java.util.List;
import javax.swing.JButton;
import com.rapidminer.Process;
import com.rapidminer.gui.MainFrame;
import com.rapidminer.gui.Perspective;
import com.rapidminer.gui.PerspectiveChangeListener;
import com.rapidminer.gui.PerspectiveModel;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.processeditor.ExtendedProcessEditor;
import com.rapidminer.gui.tools.DockingTools;
import com.rapidminer.gui.tools.ResourceAction;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.dialogs.ConfirmDialog;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.ProcessRootOperator;
import com.rapidminer.tools.AbstractObservable;
import com.rapidminer.tutorial.Tutorial;
import com.vlsolutions.swing.docking.Dockable;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.event.DockableStateWillChangeEvent;
import com.vlsolutions.swing.docking.event.DockableStateWillChangeListener;
/**
* Model which holds a {@link Tutorial} and notifies {@link Observer}s.
*
* @since 7.0.0
* @author Marcel Michel
*/
public class TutorialSelector extends AbstractObservable<Tutorial> {
/**
* Dialog to confirm the closing of the tutorial browser. The dialog displays three options: (1)
* Leave tutorial and start from scratch, (2) Leave tutorial and continue with current process,
* and (3) cancel and continue with the tutorial.
*
* @author Michael Knopf
* @since 7.0.0
*/
static class LeaveTutorialDialog extends ConfirmDialog {
private static final long serialVersionUID = 1L;
public LeaveTutorialDialog() {
super(RapidMinerGUI.getMainFrame(), "close_tutorial_browser", ConfirmDialog.YES_NO_CANCEL_OPTION, false);
}
@Override
protected JButton makeYesButton() {
return new JButton(new ResourceAction("close_tutorial_browser.new") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
LeaveTutorialDialog.this.setReturnOption(YES_OPTION);
LeaveTutorialDialog.this.setVisible(false);
}
});
};
@Override
protected JButton makeNoButton() {
return new JButton(new ResourceAction("close_tutorial_browser.keep") {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
LeaveTutorialDialog.this.setReturnOption(NO_OPTION);
LeaveTutorialDialog.this.setVisible(false);
}
});
};
}
/** the observed mainFrame */
private final MainFrame mainFrame;
/** the observed perspective model */
private final PerspectiveModel perspectiveModel;
/** the current tutorial */
private Tutorial selectedTutorial;
/** listener for process change events of the {@link #mainFrame} */
private ExtendedProcessEditor processListener;
/** listener for perspective change events of the {@link #perspectiveModel} */
private PerspectiveChangeListener perspectiveListener;
public TutorialSelector(MainFrame mainFrame, PerspectiveModel perspectveModel) {
this.mainFrame = mainFrame;
this.perspectiveModel = perspectveModel;
registerListeners();
}
/**
* Registers the listeners to observe the process.
*/
private void registerListeners() {
processListener = new ExtendedProcessEditor() {
@Override
public void setSelection(List<Operator> selection) {
// nothing to do
}
@Override
public void processUpdated(Process process) {
// nothing to do
}
@Override
public void processChanged(Process process) {
// check if the process is not the tutorial process anymore
// since a new process is created on every process change, we check the redo and
// undo steps, which are reset when a new process is opened
MainFrame mainFrame = RapidMinerGUI.getMainFrame();
if (mainFrame != null && process.getRootOperator() != null
&& process.getRootOperator().getUserData(Tutorial.KEY_USER_DATA_FLAG) == null) {
// If getUserData(Tutorial.KEY_USER_DATA_FLAG) == null the current process is
// not considered to be a tutorial process. Thus, leave the tutorial.
setSelectedTutorial(null);
closeAllTutorialBrowsers();
}
}
@Override
public void processViewChanged(Process process) {
// nothing to do
}
};
mainFrame.addExtendedProcessEditor(processListener);
perspectiveListener = new PerspectiveChangeListener() {
@Override
public void perspectiveChangedTo(Perspective perspective) {
if (PerspectiveModel.RESULT.equals(perspective.getName())
|| PerspectiveModel.DESIGN.equals(perspective.getName())) {
if (getSelectedTutorial() != null) {
// the user has opened a tutorial, ensure that the tutorial browser is
// displayed
SwingTools.invokeLater(new Runnable() {
@Override
public void run() {
DockingTools.openDockable(TutorialBrowser.TUTORIAL_BROWSER_DOCK_KEY, null,
TutorialBrowser.POSITION);
}
});
} else {
// no tutorial selected, ensure that no tutorial browser is displayed
closeAllTutorialBrowsers();
}
}
}
};
perspectiveModel.addPerspectiveChangeListener(perspectiveListener);
DockableStateWillChangeListener listener = new DockableStateWillChangeListener() {
@Override
public void dockableStateWillChange(DockableStateWillChangeEvent event) {
if (getSelectedTutorial() == null) {
// no tutorial selected -> close right away
return;
}
DockableState current = event.getCurrentState();
DockableState future = event.getFutureState();
if (current.getDockable().getDockKey().getKey().equals(TutorialBrowser.TUTORIAL_BROWSER_DOCK_KEY)
&& future.isClosed()) {
LeaveTutorialDialog dialog = new LeaveTutorialDialog();
dialog.setVisible(true);
if (dialog.getReturnOption() == ConfirmDialog.CANCEL_OPTION) {
event.cancel();
} else {
setSelectedTutorial(null);
// this event will only close the panel in the current perspective
mainFrame.getPerspectiveController().removeFromInvisiblePerspectives(current.getDockable());
if (dialog.getReturnOption() == ConfirmDialog.YES_OPTION) {
// start with new process (discard unsaved work without warning)
mainFrame.newProcess(false);
} else {
// Keep process but remove the tutorial process flag and the fire remove
// background event (to remove tutorial background).
ProcessRootOperator rootOperator = mainFrame.getProcess().getRootOperator();
rootOperator.setUserData(Tutorial.KEY_USER_DATA_FLAG, null);
mainFrame.getProcessPanel().getBackgroundImageHandler()
.makeRemoveBackgroundImageAction(rootOperator.getSubprocess(0)).actionPerformed(null);
}
}
dialog.dispose();
}
}
};
mainFrame.getDockingDesktop().addDockableStateWillChangeListener(listener);
}
/**
* @return the selected tutorial
*/
public Tutorial getSelectedTutorial() {
return selectedTutorial;
}
/**
* Updates the selected tutorial and notifies the registered observers.
*/
public void setSelectedTutorial(Tutorial selectedTutorial) {
this.selectedTutorial = selectedTutorial;
fireUpdate(selectedTutorial);
}
/**
* Attempts to close all instances of the {@link TutorialBrowser} (both visible and hidden).
*/
private void closeAllTutorialBrowsers() {
DockableState state = DockingTools.getDockableState(TutorialBrowser.TUTORIAL_BROWSER_DOCK_KEY);
if (state != null) {
Dockable browser = state.getDockable();
mainFrame.getDockingDesktop().close(browser);
mainFrame.getPerspectiveController().removeFromInvisiblePerspectives(browser);
}
}
}