package org.openslx.dozmod.gui.configurator;
import java.awt.Color;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.Box;
import javax.swing.ButtonModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.LectureRead;
import org.openslx.bwlp.thrift.iface.PresetRunScript;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor;
import org.openslx.dozmod.gui.control.ComboBox;
import org.openslx.dozmod.gui.control.ComboBox.ComboBoxRenderer;
import org.openslx.dozmod.gui.control.QLabel;
import org.openslx.dozmod.gui.control.WordWrapLabel;
import org.openslx.dozmod.gui.helper.GridManager;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.thrift.Session;
import org.openslx.dozmod.thrift.cache.MetaDataCache;
import org.openslx.thrifthelper.ThriftManager;
import org.openslx.util.QuickTimer;
import org.openslx.util.QuickTimer.Task;
import org.openslx.util.Util;
/**
* Widget for advanced configuration options for lectures. This handles
* Runscript and sound (un)muting
*/
public class StartupConfigurator extends StartupConfiguratorLayout {
private static final long serialVersionUID = -3497629601818983994L;
private StartupSettings startupSettings = new StartupSettings(null);
private ImageDetailsRead image;
private List<PresetRunScript> scripts;
private LectureRead lecture;
/**
* Used to fool the change monitoeur
*/
protected final JTextField fakeTextField = new JTextField();
public StartupConfigurator() {
super();
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
scripts = MetaDataCache.getPredefinedRunScripts();
if (scripts.isEmpty()) {
Gui.asyncExec(new Runnable() {
@Override
public void run() {
btnPredefinedScripts.setVisible(false);
}
});
}
}
});
btnPredefinedScripts.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showRunscriptSelector();
}
});
}
private void showRunscriptSelector() {
if (scripts == null) {
Gui.showMessageBox("Wah wah wah! Null scripts", MessageType.ERROR, null, null);
return;
}
final JDialog dialog = new JDialog(SwingUtilities.getWindowAncestor(this),
"Auswahl", ModalityType.APPLICATION_MODAL);
JPanel pane = new JPanel();
pane.setBorder(new EmptyBorder(new Insets(10, 5, 5, 10)));
pane.registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.dispose();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
dialog.setContentPane(pane);
Dimension dd = Gui.getScaledDimension(300, 250 + 15 * scripts.size());
dialog.setMinimumSize(dd);
dialog.setPreferredSize(dd);
GridManager grid = new GridManager(pane, 3, true, new Insets(2, 2, 2, 2));
grid.add(new WordWrapLabel("Vordefinierte Startskripte:"), 3).fill(true, false);
grid.nextRow();
final Map<ButtonModel, Integer> mapper = new HashMap<>();
boolean haveDisabled = false;
for (PresetRunScript ruleSet : scripts) {
JCheckBox button = new JCheckBox(ruleSet.displayname);
grid.add(button, 3);
grid.nextRow();
mapper.put(button.getModel(), ruleSet.scriptId);
if (image != null && ruleSet.osIds != null
&& !ruleSet.osIds.contains(image.osId)) {
button.setEnabled(false);
haveDisabled = true;
}
if (lecture != null && lecture.presetScriptIds != null && lecture.presetScriptIds.contains(ruleSet.scriptId)) {
button.setSelected(true);
}
}
if (haveDisabled) {
grid.add(new WordWrapLabel("Ausgegraute Elemente sind mit dem zur"
+ " Veranstaltung gehörendem Betriebssystem nicht kompatibel."), 3)
.fill(true, false);
grid.nextRow();
}
grid.add(Box.createVerticalGlue(), 3).expand(true, true).fill(true, true);
grid.nextRow();
JButton btnCancel = new JButton("Abbrechen");
JButton btnOk = new JButton("Speichern");
grid.add(Box.createHorizontalGlue()).expand(true, true).fill(true, true);
grid.add(btnCancel);
grid.add(btnOk);
grid.finish(false);
btnCancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dialog.dispose();
}
});
final AtomicReference<List<Integer>> selectedScripts = new AtomicReference<>();
btnOk.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<Integer> selected = new ArrayList<>();
for (Entry<ButtonModel, Integer> button : mapper.entrySet()) {
if (button.getKey().isSelected()) {
selected.add(button.getValue());
}
}
selectedScripts.set(selected);
dialog.dispose();
}
});
dialog.pack();
Gui.centerShellOverShell(SwingUtilities.getWindowAncestor(this), dialog);
dialog.setVisible(true);
// Call blocks as it's a modal window
if (selectedScripts.get() != null) {
// User clicked OK
lecture.presetScriptIds = startupSettings.selectedScripts = selectedScripts.get();
fakeTextField.setText(startupSettings.selectedScripts.toString());
}
}
private void setError(final String msg) {
lblError.setText(msg);
}
/**
* Gets the runscript as String. The chosen interpreter and visibility flag
* will get encoded in the first line of the script.
*
* @return runscript as String. If no text was entered, returns a empty
* string.
*/
public StartupSettings getState() {
setError(""); // fill remove any prior errors, we'll reset them if needed
// handle user input, this is tricky since
// * either an item has been selected -> editorContent will be of our enum type
// * user typed its own interpreter into the box -> editorContent will be a castable String
Object cboContent = cboRunscriptType.getEditor().getItem();
if (cboContent instanceof RunscriptType) {
startupSettings.put(Field.EXTENSION, ((RunscriptType) cboContent).extension);
} else if (cboContent instanceof String) {
startupSettings.put(Field.EXTENSION, (String) cboContent);
}
startupSettings.runScript = taRunScript.getText();
RunscriptVisibility visibility = (RunscriptVisibility) cboRunscriptVisibility.getSelectedItem();
startupSettings.put(Field.VISIBILITY, Integer.toString(visibility.value));
SoundState sound = (SoundState)cboSoundState.getSelectedItem();
startupSettings.put(Field.MUTED, Integer.toString(sound.value));
setError("");
return startupSettings;
}
/**
* Sets the state of this widget to the given AdvancedConfiguration. Basicly
* this sets the content of the text areas to the corresponding network
* rules/runscript as given by the AdvancedConfiguration object
*
* @param lecture
* AdvancedConfiguration to set the state to
*/
public void setState(final LectureRead lecture) {
if (lecture == null)
return;
this.lecture = lecture;
setCustomScript(lecture.runscript);
QuickTimer.scheduleOnce(new Task() {
@Override
public void fire() {
try {
image = ThriftManager.getSatClient().getImageDetails(Session.getSatelliteToken(), lecture.imageBaseId);
} catch (Exception e) {
}
}
});
if (lecture.presetScriptIds != null) {
fakeTextField.setText(lecture.presetScriptIds.toString());
}
}
private void setCustomScript(final String lecture) {
if (lecture == null || lecture.isEmpty()) {
cboRunscriptType.setSelectedItem(null);
taRunScript.setText("");
return;
}
startupSettings.deserializeItems(lecture);
String extension = startupSettings.get(Field.EXTENSION);
for (RunscriptType type : RunscriptType.values()) {
if (type.extension.equals(extension)) {
cboRunscriptType.setSelectedItem(type);
// mark that we found it by nulling the shebang...
extension = null;
break;
}
}
int visibility = Util.parseInt(startupSettings.get(Field.VISIBILITY), 1);
for (RunscriptVisibility windowFlag : RunscriptVisibility.values()) {
if (windowFlag.value == visibility) {
cboRunscriptVisibility.setSelectedItem(windowFlag);
break;
}
}
cboSoundState.setSelectedItem(SoundState.DEFAULT);
int mute = Util.parseInt(startupSettings.get(Field.MUTED), -1);
for (SoundState s : SoundState.values()) {
if (s.value == mute) {
cboSoundState.setSelectedItem(s);
break;
}
}
if (extension != null) {
// user specific shebang, so just write the text to the cbo
cboRunscriptType.getEditor().setItem(extension);
}
taRunScript.setText(startupSettings.runScript);
}
public void addToChangeMonitor(DialogChangeMonitor changeMonitor) {
changeMonitor.add(taRunScript);
changeMonitor.addEditableCombo(cboRunscriptType, null);
changeMonitor.addFixedCombo(cboRunscriptVisibility, null);
changeMonitor.addFixedCombo(cboSoundState, null);
changeMonitor.add(fakeTextField);
}
private static enum Field {
EXTENSION("ext"),
VISIBILITY("visibility"),
MUTED("soundMuted");
public final String id;
Field(String id) {
this.id = id;
}
}
/**
* Object holding all the startup settings
*/
public static class StartupSettings {
public Map<String, String> items = new HashMap<>();
public String runScript;
public List<Integer> selectedScripts;
public StartupSettings(String data) {
super();
deserializeItems(data);
}
public String put(Field key, String value) {
value = value.replace(';', '_').replace('\r', '_').replace('\n', '_');
return items.put(key.id, value);
}
public String get(Field key)
{
String ret = items.get(key.id);
if (ret == null)
return "";
return ret;
}
public String serializeItems() {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> e : items.entrySet()) {
if (sb.length() != 0) {
sb.append(';');
}
sb.append(e.getKey());
sb.append('=');
sb.append(e.getValue());
}
sb.append('\n');
if (this.runScript != null) {
sb.append(this.runScript);
}
return sb.toString();
}
public void deserializeItems(String data) {
if (data == null || data.isEmpty())
return;
String[] stuff = data.split("\n", 2);
// we should have following format: ext=<interpreter/extension>;visibility=<flag>;...
// e.g. ext=sh;visibility=0
items.clear();
String[] parts = stuff[0].split(";");
for (String s : parts) {
String[] entry = s.split("=");
if (entry.length == 2) {
items.put(entry[0], entry[1]);
}
}
if (stuff.length < 2) {
runScript = "";
} else {
runScript = stuff[1];
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StartupSettings))
return false;
StartupSettings other = (StartupSettings) obj;
if (this.selectedScripts == other.selectedScripts)
return true;
if (this.selectedScripts == null)
return false;
return this.selectedScripts.equals(other.selectedScripts);
}
}
}
/**
* Internal layout class for the advanced configurator (to keep it clean even
* for widgets)
*/
class StartupConfiguratorLayout extends JPanel {
private static final long serialVersionUID = 648729071828404053L;
private final static String RUN_SCRIPT_HELP = "Ein hier eingetragenes Skript wird nach dem Start"
+ " der VM automatisch ausgeführt.";
protected final QLabel lblError;
protected final JTextArea taRunScript;
protected final ComboBox<RunscriptType> cboRunscriptType;
protected final ComboBox<RunscriptVisibility> cboRunscriptVisibility;
protected final ComboBox<SoundState> cboSoundState;
protected final JButton btnPredefinedScripts;
public StartupConfiguratorLayout() {
GridManager grid = new GridManager(this, 2, true, new Insets(5, 5, 5, 5));
grid.add(new QLabel("Audio"));
cboSoundState = new ComboBox<>(new ComboBoxRenderer<SoundState>() {
@Override
public String renderItem(SoundState item) {
return item.displayName;
}
});
cboSoundState.setModel(new DefaultComboBoxModel<SoundState>(SoundState.values()));
grid.add(cboSoundState).fill(true, false).expand(true, false);
grid.nextRow();
grid.add(Box.createVerticalStrut(4), 2);
grid.nextRow();
grid.add(new WordWrapLabel(RUN_SCRIPT_HELP, false, true), 2)
.fill(true, false).expand(true, false);
grid.nextRow();
cboRunscriptType = new ComboBox<RunscriptType>(
new ComboBoxRenderer<RunscriptType>() {
@Override
public String renderItem(RunscriptType item) {
if (item == null)
return null;
return item.toString();
}
});
cboRunscriptType.setModel(new DefaultComboBoxModel<RunscriptType>(
RunscriptType.values()));
cboRunscriptType.setEditable(true);
grid.add(new QLabel("Dateinamenserweiterung: ")).fill(false, false)
.expand(false, false);
grid.add(cboRunscriptType).fill(true, false)
.expand(true, false);
grid.nextRow();
cboRunscriptVisibility = new ComboBox<RunscriptVisibility>(new ComboBoxRenderer<RunscriptVisibility>() {
@Override
public String renderItem(RunscriptVisibility item) {
if (item == null)
return null;
return item.displayName;
}
});
cboRunscriptVisibility.setModel(new DefaultComboBoxModel<RunscriptVisibility>(RunscriptVisibility.values()));
grid.add(new QLabel("Sichtbarkeit: ")).fill(false, false)
.expand(false, false);
grid.add(cboRunscriptVisibility).fill(true, false)
.expand(true, false);
grid.nextRow();
taRunScript = new JTextArea("", 5, 20);
JScrollPane scpRunScript = new JScrollPane(taRunScript,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
taRunScript.setLineWrap(true);
taRunScript.setWrapStyleWord(true);
grid.add(scpRunScript, 2).fill(true, true).expand(true, true);
grid.nextRow();
btnPredefinedScripts = new JButton("Vordefinierte Skripte...");
grid.add(btnPredefinedScripts, 2).anchor(GridBagConstraints.LINE_END);
grid.nextRow();
lblError = new QLabel("");
lblError.setForeground(Color.RED);
JPanel pnlError = new JPanel();
pnlError.add(Box.createGlue());
pnlError.add(lblError);
pnlError.add(Box.createGlue());
grid.add(pnlError, 2).fill(true, false).expand(true, false);
grid.finish(false);
}
}
enum RunscriptType {
SHELL("Shellskript", "sh"), BATCH("Windows-Batch", "bat");
public final String displayName;
public final String extension;
private RunscriptType(String name, String extension) {
this.displayName = name;
this.extension = extension;
}
@Override
public String toString() {
return extension + " (" + displayName + ")";
}
}
enum RunscriptVisibility {
NORMAL("Normal", 1), MINIMIZED("Minimiert", 2), HIDDEN("Versteckt", 0);
public final String displayName;
public final int value;
private RunscriptVisibility(String name, int flag) {
this.displayName = name;
this.value = flag;
}
}
enum SoundState {
DEFAULT("Vorgabe des Pools", -1), MUTED("Stummschalten", 1), UNMUTED("Aktivieren", 0);
public final String displayName;
public final int value;
private SoundState(String name, int value) {
this.displayName = name;
this.value = value;
}
}