package org.openslx.dozmod.gui.configurator; import java.awt.Color; import java.awt.Dialog.ModalityType; import java.awt.GridBagConstraints; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; 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.JDialog; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; import org.apache.log4j.Logger; 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 scripts; private LectureRead lecture; private ChangeListener mightyListener; 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), "Startscripte auswählen", ModalityType.APPLICATION_MODAL); JPanel pane = new JPanel(); dialog.setContentPane(pane); dialog.setMinimumSize(Gui.getScaledDimension(200, 300)); GridManager grid = new GridManager(pane, 2, true, new Insets(2, 2, 2, 2)); final Map mapper = new HashMap<>(); boolean haveDisabled = false; for (PresetRunScript ruleSet : scripts) { JCheckBox button = new JCheckBox(ruleSet.displayname); grid.add(button, 2); 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"), 2); grid.nextRow(); } JButton btnCancel = new JButton("Abbrechen"); JButton btnOk = new JButton("SPASCHAN"); grid.add(btnCancel).anchor(GridBagConstraints.LINE_START); grid.add(btnOk).anchor(GridBagConstraints.LINE_END); grid.finish(true); btnCancel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { dialog.dispose(); } }); final AtomicReference> selectedScripts = new AtomicReference<>(); btnOk.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { List selected = new ArrayList<>(); for (Entry 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 startupSettings.selectedScripts = selectedScripts.get(); mightyListener.stateChanged(null); } } 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) { } } }); } private void setCustomScript(final String lecture) { if (lecture == null || lecture.isEmpty()) { cboRunscriptType.setSelectedItem(null); taRunScript.setText(""); return; } String header = null; try (BufferedReader reader = new BufferedReader(new StringReader(lecture))) { header = reader.readLine(); } catch (IOException e) { // swallow ... } if (header != null) { // we should have following format: ext=;visibility=;... // e.g. ext=sh;visibility=0 startupSettings.deserializeItems(header); 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); } } // finished with the interpreter, remove that line from the given config // before setting that text taRunScript.setText(lecture.replaceFirst(".*?\n", "")); } public void addToChangeMonitor(DialogChangeMonitor changeMonitor) { changeMonitor.add(taRunScript); changeMonitor.addEditableCombo(cboRunscriptType, null); changeMonitor.addFixedCombo(cboRunscriptVisibility, null); changeMonitor.addFixedCombo(cboSoundState, null); // TODO: Preset runscript } 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 items = new HashMap<>(); public String runScript; public List 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 e : items.entrySet()) { if (sb.length() != 0) { sb.append(';'); } sb.append(e.getKey()); sb.append('='); sb.append(e.getValue()); } return sb.toString(); } public void deserializeItems(String data) { if (data == null) return; items.clear(); String[] parts = data.split(";"); for (String s : parts) { String[] entry = s.split("="); if (entry.length == 2) { items.put(entry[0], entry[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 cboRunscriptType; protected final ComboBox cboRunscriptVisibility; protected final ComboBox 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() { @Override public String renderItem(SoundState item) { return item.displayName; } }); cboSoundState.setModel(new DefaultComboBoxModel(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( new ComboBoxRenderer() { @Override public String renderItem(RunscriptType item) { if (item == null) return null; return item.toString(); } }); cboRunscriptType.setModel(new DefaultComboBoxModel( 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(new ComboBoxRenderer() { @Override public String renderItem(RunscriptVisibility item) { if (item == null) return null; return item.displayName; } }); cboRunscriptVisibility.setModel(new DefaultComboBoxModel(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 Scripte..."); 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; } }