package org.openslx.dozmod.gui.window;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;
import org.openslx.bwlp.thrift.iface.ImageBaseWrite;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.ImageSummaryRead;
import org.openslx.bwlp.thrift.iface.ImageVersionDetails;
import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.ShareMode;
import org.openslx.bwlp.thrift.iface.UserInfo;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.MainWindow;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.helper.PopupMenu;
import org.openslx.dozmod.gui.helper.UiFeedback;
import org.openslx.dozmod.gui.window.UserListWindow.UserAddedCallback;
import org.openslx.dozmod.gui.window.layout.ImageDetailsWindowLayout;
import org.openslx.dozmod.gui.wizard.LectureWizard;
import org.openslx.dozmod.permissions.ImagePerms;
import org.openslx.dozmod.thrift.Session;
import org.openslx.dozmod.thrift.ThriftError;
import org.openslx.dozmod.thrift.ThriftActions;
import org.openslx.dozmod.thrift.cache.MetaDataCache;
import org.openslx.dozmod.thrift.cache.UserCache;
import org.openslx.dozmod.util.FormatHelper;
import org.openslx.thrifthelper.ThriftManager;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
@SuppressWarnings("serial")
public class ImageDetailsWindow extends ImageDetailsWindowLayout implements UiFeedback {
private static final Logger LOGGER = Logger.getLogger(ImageDetailsWindow.class);
/**
* Self-reference
*/
private final ImageDetailsWindow me = this;
/**
* Callback interface to refresh image list after changing image details
*/
public interface ImageUpdatedCallback {
public void updated();
}
/**
* Callback instance
*/
private ImageUpdatedCallback callback = null;
/**
* Image that this window shows the details of
*/
private ImageDetailsRead image = null;
/**
* Popup menu items
*/
private JMenuItem popupItemNew = new JMenuItem("Neue Veranstaltung");
private JMenuItem popupItemDownload = new JMenuItem("Download");
private JMenuItem popupItemDelete = new JMenuItem("Löschen");
/**
* Constructor
*
* @param modalParent parent of this popup window
* @param callback callback to be called when the image details changed
*/
public ImageDetailsWindow(Frame modalParent, ImageUpdatedCallback callback) {
super(modalParent);
this.callback = callback;
/**
* Button listeners
*/
btnClose.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
});
btnSaveChanges.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
saveChanges();
}
});
btnUpdateImage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO image version up
}
});
btnChangeOwner.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UserListWindow.open(JOptionPane.getFrameForComponent(btnChangeOwner),
new UserAddedCallback() {
@Override
public void userAdded(UserInfo user, UserListWindow window) {
window.dispose();
setImageOwner(user);
}
}, "Besitzer festlegen");
}
});
/**
* Popup menu for the version table on the right side
*/
final PopupMenu pop = new PopupMenu(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ImageVersionDetails selected = versionTable.getSelectedItem();
if (selected == null)
return;
if (e.getSource().equals(popupItemNew)) {
ImageSummaryRead summary = new ImageSummaryRead();
summary.setImageName(image.getImageName()); // Maybe create a helper class/function some day that transforms all fields
new LectureWizard(me, summary, selected.getVersionId()).setVisible(true);
}
if (e.getSource().equals(popupItemDownload)) {
performImageDownload(selected);
}
if (e.getSource().equals(popupItemDelete)) {
deleteVersion(selected);
}
}
});
pop.addMenuItem(popupItemNew);
pop.addMenuItem(popupItemDownload);
pop.addSeparator();
pop.addMenuItem(popupItemDelete);
// TODO check permissions and disable them if needed
/**
* Mouse adapter for the version table
*/
final MouseAdapter ma = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
processClick(e);
}
@Override
public void mouseReleased(MouseEvent e) {
processClick(e);
}
private void processClick(MouseEvent e) {
int r = versionTable.rowAtPoint(e.getPoint());
if (r >= 0 && r < versionTable.getRowCount()) {
// highlight the row and popup the menu
versionTable.setRowSelectionInterval(r, r);
if (e.isPopupTrigger()) {
popupItemNew.setEnabled(ImagePerms.canLink(image));
popupItemDownload.setEnabled(ImagePerms.canDownload(image));
popupItemDelete.setEnabled(ImagePerms.canAdmin(image));
pop.show(e.getComponent(), e.getX(), e.getY());
}
} else {
versionTable.clearSelection();
}
}
};
versionTableScrollPane.addMouseListener(ma);
versionTable.addMouseListener(ma);
/**
* Initial state of GUI elements
*/
setFocusable(true);
btnSaveChanges.setEnabled(false);
txtVersion.setEditable(false);
txtId.setEditable(false);
chkIsTemplate.setEnabled(Session.isSuperUser());
makeEditable(false);
}
/********************************************************************************
*
* Helper triggering the actual thrift calls
*
********************************************************************************/
/**
* @param imageBaseId the id of the image to be displayed
*/
public void setImage(final String imageBaseId) {
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
Exception error = null;
try {
synchronized (me) {
if (image != null)
return;
image = ThriftManager.getSatClient().getImageDetails(Session.getSatelliteToken(),
imageBaseId);
}
} catch (Exception e) {
error = e;
}
// Just prime the cache...
MetaDataCache.getOperatingSystems();
MetaDataCache.getVirtualizers();
final Exception e = error;
Gui.asyncExec(new Runnable() {
@Override
public void run() {
if (e != null || image == null) {
Gui.showMessageBox(null, "Konnte Daten des Images nicht abrufen",
MessageType.ERROR, LOGGER, e);
dispose();
} else {
fill();
}
}
});
}
});
}
/**
* Sets the owner of the selected image to the given user.
*
* @param user UserInfo to set the owner to
*/
private void setImageOwner(final UserInfo user) {
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
try {
ThriftManager.getSatClient().setImageOwner(Session.getSatelliteToken(),
image.getImageBaseId(), user.getUserId());
} catch (TException e) {
ThriftError.showMessage(me, LOGGER, e, "Fehler beim Übertragen der Besitzrechte");
return;
}
Gui.showMessageBox(me, "Besitzrechte übertragen an " + FormatHelper.userName(user),
MessageType.INFO, null, null);
makeEditable(false);
String baseId = image.getImageBaseId();
synchronized (me) {
image = null;
}
setImage(baseId);
}
});
}
/**
* Push the changes of the image details to the satellite
*/
private void saveChanges() {
// first we build an ImageBaseWrite
final ImageBaseWrite ibw = new ImageBaseWrite(
txtTitle.getText(),
txtDescription.getText(),
((OperatingSystem)cboOperatingSystem.getSelectedItem()).osId,
image.virtId,
chkIsTemplate.isSelected(),
image.defaultPermissions,
image.shareMode);
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
try {
// push to sat
ThriftManager.getSatClient().updateImageBase(Session.getSatelliteToken(),
image.getImageBaseId(), ibw);
} catch (TException e) {
ThriftError.showMessage(me, LOGGER, e, "Fail to update image details to satellite");
return;
}
// no success message I'd say...
btnSaveChanges.setEnabled(false);
String baseId = image.getImageBaseId();
synchronized (me) {
image = null;
}
setImage(baseId);
callback.updated();
}
});
}
/**
* Triggers the download of the given image version
*
* @param selected image to download
*/
private void performImageDownload(ImageVersionDetails selected) {
if (selected.getVersionId() == null) {
Gui.showMessageBox(this, "Ausgewählte Version ist ungültig", MessageType.ERROR,
null, null);
return;
}
ThriftActions.initDownload(JOptionPane.getFrameForComponent(this), selected.versionId,
image.imageName, image.virtId, selected.fileSize, null);
}
/**
* Triggers the deletion of the given image version
*
* @param version image version to delete
*/
private void deleteVersion(final ImageVersionDetails version) {
// requires confirmation of the user
if (!Gui.showMessageBox(me, "Wollen Sie diese Version des Images wirklich löschen?",
MessageType.QUESTION_YESNO, LOGGER, null))
return;
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
try {
ThriftManager.getSatClient().deleteImageVersion(Session.getSatelliteToken(), version.versionId);
} catch (TException e) {
ThriftError.showMessage(me, LOGGER, e, "Fehler beim Löschen der Version");
return;
}
// no feedback, only force refresh
Gui.asyncExec(new Runnable() {
@Override
public void run() {
// "manual" refresh
String baseId = image.getImageBaseId();
image = null;
setImage(baseId);
}
});
}
});
}
/********************************************************************************
*
* General UI helpers
*
********************************************************************************/
/**
* callback function when we received the image's details from the server
*/
private void fill() {
if (image == null)
return;
LOGGER.debug(image);
txtTitle.setText(image.getImageName());
txtDescription.setText(image.getDescription());
lblOwner.setUser(UserCache.find(image.getOwnerId()));
lblUpdater.setUser(UserCache.find(image.getUpdaterId()));
lblCreateTime.setText(FormatHelper.longDate(image.getCreateTime()));
lblUpdateTime.setText(FormatHelper.longDate(image.getUpdateTime()));
txtVersion.setText(image.getLatestVersionId());
txtId.setText(image.getImageBaseId());
setTitle(image.getImageName());
List<OperatingSystem> osList = MetaDataCache.getOperatingSystems();
// all fine, lets sort it
Collections.sort(osList, new Comparator<OperatingSystem>() {
public int compare(OperatingSystem o1, OperatingSystem o2) {
return o1.getOsName().compareTo(o2.getOsName());
}
});
for (OperatingSystem os : osList) {
cboOperatingSystem.addItem(os);
}
cboOperatingSystem.setSelectedItem(new OperatingSystem(image.getOsId(), null, null, null));
Virtualizer virt = MetaDataCache.getVirtualizerById(image.getVirtId());
if (virt != null)
lblVirtualizer.setText(virt.getVirtName());
String tagsString = "";
for (String tag : image.getTags()) {
tagsString = tagsString + ", " + tag;
}
txtTags.setText(tagsString);
chkIsTemplate.setSelected(image.isTemplate);
// set the versions of the image to the table
versionTable.setData(image.getVersions(), true);
// final step, add listeners to react to change
final DocumentListener docListener = new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
changedUpdate(e);
}
@Override
public void insertUpdate(DocumentEvent e) {
changedUpdate(e);
}
@Override
public void changedUpdate(DocumentEvent e) {
reactToChange();
}
};
txtTitle.getDocument().addDocumentListener(docListener);
txtDescription.getDocument().addDocumentListener(docListener);
txtTags.getDocument().addDocumentListener(docListener);
// Comboboxes
final ItemListener comboItemListener = new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
reactToChange();
}
}
};
cboOperatingSystem.addItemListener(comboItemListener);
cboShareMode.addItemListener(comboItemListener);
chkIsTemplate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
reactToChange();
}
});
// make fields editable is allowed
makeEditable(true);
// finally do show it all
pack();
setVisible(true);
}
/**
* Enables/disables the editable fields based on 'editable'
*
* @param editable true to make fields editable, false otherwise.
*/
private void makeEditable(boolean editable) {
editable = editable && ImagePerms.canEdit(image);
txtTitle.setEditable(editable);
txtDescription.setEditable(editable);
txtTags.setEditable(editable);
cboOperatingSystem.setEnabled(editable);
cboShareMode.setEnabled(editable);
btnChangeOwner.setEnabled(editable && ImagePerms.canAdmin(image));
}
/**
* Checks whether the user changed any fields of the image details and
* enables the save button if so.
*/
private void reactToChange() {
OperatingSystem newOs = cboOperatingSystem.getItemAt(cboOperatingSystem.getSelectedIndex());
ShareMode newShareMode = cboShareMode.getItemAt(cboShareMode.getSelectedIndex());
boolean changed = false;
if (!txtTitle.getText().isEmpty() && !txtTitle.getText().equals(image.getImageName())) {
changed = true;
} else if (!txtDescription.getText().isEmpty() && !txtDescription.getText().equals(image.getDescription())) {
changed = true;
} else if (newOs != null && newOs.getOsId() != image.getOsId()) {
changed = true;
} else if (newShareMode != null && !newShareMode.equals(image.shareMode)) {
changed = true;
} else if (chkIsTemplate.isSelected() != image.isTemplate) {
changed = true;
}
// TODO TAGS
btnSaveChanges.setEnabled(changed);
}
/**
* Opens a new ImageDetailsWindow showing the details of the image with ID =
* imageBaseId
*
* @param modalParent parent of this window
* @param imageBaseId id of the image to set the details of
*/
public static void open(Frame modalParent, String imageBaseId, ImageUpdatedCallback callback) {
ImageDetailsWindow win = new ImageDetailsWindow(modalParent, callback);
win.setImage(imageBaseId);
}
/********************************************************************************
*
* Dialog class overrides
*
********************************************************************************/
@SuppressWarnings("deprecation")
@Override
public void show() {
if (!isVisible()) {
pack();
MainWindow.centerShell(this);
}
super.show();
}
/********************************************************************************
*
* UIFeedback implementation
*
********************************************************************************/
@Override
public boolean wantConfirmQuit() {
return btnSaveChanges.isEnabled();
}
@Override
public void escapePressed() {
// Also ask if applicable
this.dispose();
}
}