/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2015 University of Dundee. All rights reserved.
*
*
* 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 2 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 org.openmicroscopy.shoola.agents.fsimporter.view;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.swing.Action;
import javax.swing.JComboBox;
import javax.swing.JMenu;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuKeyEvent;
import javax.swing.event.MenuKeyListener;
import javax.swing.event.MenuListener;
import org.apache.commons.collections.CollectionUtils;
import org.openmicroscopy.shoola.agents.fsimporter.ImporterAgent;
import org.openmicroscopy.shoola.agents.fsimporter.actions.ActivateAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.CancelAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.CloseAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.ExitAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.GroupSelectionAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.ImporterAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.LogOffAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.PersonalManagementAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.RetryImportAction;
import org.openmicroscopy.shoola.agents.fsimporter.actions.SubmitFilesAction;
import org.openmicroscopy.shoola.agents.fsimporter.chooser.ImportDialog;
import org.openmicroscopy.shoola.agents.fsimporter.util.FileImportComponent;
import org.openmicroscopy.shoola.agents.fsimporter.util.ObjectToCreate;
import org.openmicroscopy.shoola.agents.util.ViewerSorter;
import org.openmicroscopy.shoola.agents.util.ui.JComboBoxImageObject;
import org.openmicroscopy.shoola.env.LookupNames;
import org.openmicroscopy.shoola.env.data.model.ImportableObject;
import org.openmicroscopy.shoola.env.data.util.StatusLabel;
import omero.log.Logger;
import org.openmicroscopy.shoola.env.ui.UserNotifier;
import org.openmicroscopy.shoola.util.file.ImportErrorObject;
import org.openmicroscopy.shoola.util.ui.ClosableTabbedPane;
import org.openmicroscopy.shoola.util.ui.MacOSMenuHandler;
import org.openmicroscopy.shoola.util.ui.MessageBox;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import omero.gateway.model.ExperimenterData;
import omero.gateway.model.GroupData;
/**
* The {@link Importer}'s controller.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* @since 3.0-Beta4
*/
class ImporterControl
implements ActionListener, PropertyChangeListener
{
/** Action ID indicating to send the files that could not imported. */
static final Integer SEND_BUTTON = 0;
/** Action ID indicating to close the window. */
static final Integer CLOSE_BUTTON = 1;
/** Action ID indicating to cancel. */
static final Integer CANCEL_BUTTON = 2;
/** Action ID indicating to retry failed import. */
static final Integer RETRY_BUTTON = 3;
/** Action ID indicating to switch between groups. */
static final Integer GROUP_BUTTON = 4;
/** Action ID indicating to exit the application. */
static final Integer EXIT = 5;
/** Action ID indicating to log off the current server. */
static final Integer LOG_OFF = 6;
/**
* Reference to the {@link Importer} component, which, in this context,
* is regarded as the Model.
*/
private Importer model;
/** Reference to the View. */
private ImporterUI view;
/** Collection of files to submit. */
private List<FileImportComponent> markedFailed;
/** Maps actions identifiers onto actual <code>Action</code> object. */
private Map<Integer, ImporterAction> actionsMap;
/** Helper method to create all the UI actions. */
private void createActions()
{
actionsMap = new HashMap<Integer, ImporterAction>();
actionsMap.put(SEND_BUTTON, new SubmitFilesAction(model));
actionsMap.put(CLOSE_BUTTON, new CloseAction(model));
actionsMap.put(CANCEL_BUTTON, new CancelAction(model));
actionsMap.put(RETRY_BUTTON, new RetryImportAction(model));
actionsMap.put(GROUP_BUTTON, new PersonalManagementAction(model));
actionsMap.put(EXIT, new ExitAction(model));
actionsMap.put(LOG_OFF, new LogOffAction(model));
}
/**
* Creates the windowsMenuItems.
*
* @param menu The menu to handle.
*/
private void createWindowsMenuItems(JMenu menu)
{
menu.removeAll();
menu.add(new ActivateAction(model));
}
/** Attaches listener to the window listener. */
private void attachListeners()
{
if (UIUtilities.isMacOS() && model.isMaster()) {
try {
MacOSMenuHandler handler = new MacOSMenuHandler(view);
handler.initialize();
view.addPropertyChangeListener(this);
} catch (Throwable e) {}
}
view.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
view.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) { model.close(); }
});
JMenu menu = ImporterFactory.getWindowMenu();
menu.addMenuListener(new MenuListener() {
public void menuSelected(MenuEvent e)
{
Object source = e.getSource();
if (source instanceof JMenu)
createWindowsMenuItems((JMenu) source);
}
/**
* Required by I/F but not actually needed in our case,
* no-operation implementation.
* @see MenuListener#menuCanceled(MenuEvent)
*/
public void menuCanceled(MenuEvent e) {}
/**
* Required by I/F but not actually needed in our case,
* no-operation implementation.
* @see MenuListener#menuDeselected(MenuEvent)
*/
public void menuDeselected(MenuEvent e) {}
});
//Listen to keyboard selection
menu.addMenuKeyListener(new MenuKeyListener() {
public void menuKeyReleased(MenuKeyEvent e)
{
Object source = e.getSource();
if (source instanceof JMenu)
createWindowsMenuItems((JMenu) source);
}
/**
* Required by I/F but not actually needed in our case,
* no-operation implementation.
* @see MenuKeyListener#menuKeyPressed(MenuKeyEvent)
*/
public void menuKeyPressed(MenuKeyEvent e) {}
/**
* Required by I/F but not actually needed in our case,
* no-operation implementation.
* @see MenuKeyListener#menuKeyTyped(MenuKeyEvent)
*/
public void menuKeyTyped(MenuKeyEvent e) {}
});
}
/**
* Creates a new instance.
* The {@link #initialize(FSImportUI) initialize} method
* should be called straight
* after to link this Controller to the other MVC components.
*
* @param model Reference to the {@link Importer} component, which, in
* this context, is regarded as the Model.
* Mustn't be <code>null</code>.
*/
ImporterControl(Importer model)
{
if (model == null) throw new NullPointerException("No model.");
this.model = model;
}
/**
* Links this Controller to its View.
*
* @param view Reference to the View. Mustn't be <code>null</code>.
*/
void initialize(ImporterUI view)
{
if (view == null) throw new NullPointerException("No view.");
this.view = view;
createActions();
attachListeners();
ImporterFactory.attachWindowMenuToTaskBar();
}
/**
* Returns the action corresponding to the specified id.
*
* @param id One of the flags defined by this class.
* @return The specified action.
*/
ImporterAction getAction(Integer id) { return actionsMap.get(id); }
/**
* Submits the files that failed to import.
*
* @param fc The component to handle or <code>null</code>.
*/
void submitFiles(FileImportComponent fc)
{
List<FileImportComponent> list;
if (fc != null) {
list = new ArrayList<FileImportComponent>();
list.add(fc);
} else {
list = view.getMarkedFiles();
}
markedFailed = list;
//Now prepare the list of object to send.
Iterator<FileImportComponent> i = list.iterator();
ImportErrorObject object;
List<ImportErrorObject> toSubmit = new ArrayList<ImportErrorObject>();
while (i.hasNext()) {
fc = i.next();
object = fc.getImportErrorObject();
if (object != null)
toSubmit.add(object);
}
ExperimenterData exp = ImporterAgent.getUserDetails();
String email = exp.getEmail();
if (email == null) email = "";
if (CollectionUtils.isEmpty(toSubmit)) return;
//Check reader used.
Iterator<ImportErrorObject> j = toSubmit.iterator();
boolean plate = false;
while (j.hasNext()) {
object = j.next();
Boolean b = object.isHCS();
if (b != null && b.booleanValue()) {
plate = true;
break;
}
}
if (plate) {
StringBuffer buffer = new StringBuffer();
buffer.append("To submit HCS data, please e-mail us directly at ");
String address = (String) ImporterAgent.getRegistry().lookup(
LookupNames.DEBUGGER_ADDRESS);
buffer.append(address);
buffer.append("\n");
buffer.append("Do you still wish to report the error?");
MessageBox box = new MessageBox(view, "Submit Error", buffer.toString());
if (box.centerMsgBox() == MessageBox.NO_OPTION) return;
//only submit error and log
j = toSubmit.iterator();
while (j.hasNext()) {
j.next().resetFile();
}
}
UserNotifier un = ImporterAgent.getRegistry().getUserNotifier();
un.notifyError("Import Failures", "Files that failed to import", email,
toSubmit, this);
}
/**
* Returns the list of group the user is a member of.
*
* @return See above.
*/
List<GroupSelectionAction> getUserGroupAction()
{
List<GroupSelectionAction> l = new ArrayList<GroupSelectionAction>();
Collection m = ImporterAgent.getAvailableUserGroups();
if (m == null || m.size() == 0) return l;
ViewerSorter sorter = new ViewerSorter();
Iterator i = sorter.sort(m).iterator();
GroupData group;
GroupSelectionAction action;
while (i.hasNext()) {
group = (GroupData) i.next();
l.add(new GroupSelectionAction(model, group));
}
return l;
}
/**
* Returns <code>true</code> if the agent is the entry point
* <code>false</code> otherwise.
*
* @return See above.
*/
boolean isMaster() { return view.isMaster(); }
/**
* Disable the Cancel All button if there are no cancellable imports.
*/
private void checkDisableCancelAllButtons() {
final ImporterAction cancelAction = actionsMap.get(CANCEL_BUTTON);
if (!cancelAction.isEnabled()) {
return;
}
for (final ImporterUIElement importerUIElement : view.getImportElements()) {
if (importerUIElement.hasImportToCancel()) {
return;
}
}
cancelAction.setEnabled(false);
}
/**
* Reacts to property changes.
* @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
*/
public void propertyChange(final PropertyChangeEvent evt) {
if (EventQueue.isDispatchThread()) {
// only handle this event directly if we are in the EDT!
handlePropertyChangedEvent(evt);
} else {
Runnable run = new Runnable() {
@Override
public void run() {
handlePropertyChangedEvent(evt);
}
};
SwingUtilities.invokeLater(run);
}
}
/**
* Handles a PropertyChangedEvent
* @param evt The event
*/
private void handlePropertyChangedEvent(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if (ImportDialog.IMPORT_PROPERTY.equals(name)) {
actionsMap.get(CANCEL_BUTTON).setEnabled(true);
model.importData((ImportableObject) evt.getNewValue());
} else if (ImportDialog.LOAD_TAGS_PROPERTY.equals(name)) {
model.loadExistingTags();
} else if (ImportDialog.CANCEL_SELECTION_PROPERTY.equals(name)) {
model.close();
} else if (ClosableTabbedPane.CLOSE_TAB_PROPERTY.equals(name)) {
model.removeImportElement(evt.getNewValue());
} else if (FileImportComponent.SUBMIT_ERROR_PROPERTY.equals(name)) {
submitFiles((FileImportComponent) evt.getNewValue());
} else if (ImportDialog.REFRESH_LOCATION_PROPERTY.equals(name)) {
model.refreshContainers((ImportLocationDetails) evt.getNewValue());
} else if (ImportDialog.CREATE_OBJECT_PROPERTY.equals(name)) {
ObjectToCreate l = (ObjectToCreate) evt.getNewValue();
model.createDataObject(l);
} else if (StatusLabel.DEBUG_TEXT_PROPERTY.equals(name)) {
view.appendDebugText((String) evt.getNewValue());
} else if (MacOSMenuHandler.QUIT_APPLICATION_PROPERTY.equals(name)) {
Action a = getAction(EXIT);
ActionEvent event =
new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "");
a.actionPerformed(event);
} else if (ImportDialog.PROPERTY_GROUP_CHANGED.equals(name)) {
GroupData newGroup = (GroupData) evt.getNewValue();
model.setUserGroup(newGroup);
} else if (StatusLabel.FILE_IMPORT_STARTED_PROPERTY.equals(name) ||
FileImportComponent.CANCEL_IMPORT_PROPERTY.equals(name)) {
checkDisableCancelAllButtons();
} else if (StatusLabel.IMPORT_DONE_PROPERTY.equals(name)) {
model.onImportComplete((FileImportComponent) evt.getNewValue());
} else if (StatusLabel.UPLOAD_DONE_PROPERTY.equals(name)) {
model.onUploadComplete((FileImportComponent) evt.getNewValue());
}
}
/**
* Re-uploads the file.
*
* @param fc The file to upload.
*/
void retryUpload(FileImportComponent fc)
{
model.retryUpload(fc);
}
/**
* Re-uploads the file.
*
* @param fc The file to upload.
*/
void cancel(FileImportComponent fc)
{
model.onUploadComplete(fc);
}
/**
* Handles group selection.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent e)
{
int index = Integer.parseInt(e.getActionCommand());
if (index == GROUP_BUTTON) {
JComboBox box = (JComboBox) e.getSource();
Object ho = box.getSelectedItem();
if (ho instanceof JComboBoxImageObject) {
JComboBoxImageObject o = (JComboBoxImageObject) ho;
if (o.getData() instanceof GroupData) {
model.setUserGroup((GroupData) o.getData());
}
}
}
}
}