diff options
author | Simon Rettberg | 2018-06-21 15:41:44 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-06-21 15:41:44 +0200 |
commit | c0003a559a36dfca1bdc4add0034e67bd22824ed (patch) | |
tree | d0f03daa4eb8b94cbfb9472213a109eade52a0dc /dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator | |
parent | [client] Refactor change monitor classes, better error message handling (diff) | |
download | tutor-module-c0003a559a36dfca1bdc4add0034e67bd22824ed.tar.gz tutor-module-c0003a559a36dfca1bdc4add0034e67bd22824ed.tar.xz tutor-module-c0003a559a36dfca1bdc4add0034e67bd22824ed.zip |
[client] Sanitize class/var names, split up control package
All configurators have moved from *.control to *.configurator
*.control should be used for simple controls that feel like
they're really just one thing. The configurators are more like
a group of controls.
Diffstat (limited to 'dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator')
6 files changed, 1746 insertions, 0 deletions
diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/ImagePermissionConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/ImagePermissionConfigurator.java new file mode 100644 index 00000000..e20378f9 --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/ImagePermissionConfigurator.java @@ -0,0 +1,201 @@ +package org.openslx.dozmod.gui.configurator; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.ImagePermissions; +import org.openslx.bwlp.thrift.iface.UserInfo; +import org.openslx.dozmod.gui.control.table.ImagePermissionTable; +import org.openslx.dozmod.gui.control.table.QScrollPane; +import org.openslx.dozmod.gui.control.table.ImagePermissionTable.UserImagePermissions; +import org.openslx.dozmod.gui.helper.GridManager; +import org.openslx.dozmod.gui.window.UserListWindow; +import org.openslx.dozmod.gui.window.UserListWindow.UserAddedCallback; +import org.openslx.dozmod.thrift.cache.UserCache; +import org.openslx.dozmod.util.FormatHelper; + +/** + * Panel including ImagePermissionTable and add/remove buttons for setting + * customImagePermissions. + */ +@SuppressWarnings("serial") +public class ImagePermissionConfigurator extends JPanel { + + protected ImagePermissionTable permissionTable; + + protected JButton btnAddUser; + protected JButton btnRemoveUser; + + private ImagePermissionConfigurator me; + + private String ownerId; + + private ArrayList<UserImagePermissions> permissionList = new ArrayList<UserImagePermissions>(); + private Map<String, ImagePermissions> newPermissionMap; + + private ImagePermissions defaultPermissions; + + private static final Logger LOGGER = Logger.getLogger(ImagePermissionConfigurator.class); + + public ImagePermissionConfigurator() { + super(); + me = this; + GridManager grid = new GridManager(this, 1); + + permissionTable = new ImagePermissionTable(); + + // Panel for the add- and remove buttons + JPanel userButtonPane = new JPanel(); + userButtonPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + userButtonPane.setLayout(new BoxLayout(userButtonPane, BoxLayout.LINE_AXIS)); + + btnAddUser = new JButton("Benutzer hinzufügen"); + userButtonPane.add(btnAddUser); + btnRemoveUser = new JButton("Benutzer entfernen"); + userButtonPane.add(btnRemoveUser); + userButtonPane.add(Box.createGlue()); + + // Put everything into the grid + QScrollPane jsp = new QScrollPane(permissionTable); + jsp.setBackground(UIManager.getColor("Table.background")); + grid.add(jsp).fill(true, true).expand(true, true); + grid.nextRow(); + grid.add(userButtonPane).fill(true, false).expand(true, false); + grid.nextRow(); + grid.finish(false); + + // add user button listener + btnAddUser.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + UserListWindow.open(SwingUtilities.getWindowAncestor(me), new UserAddedCallback() { + @Override + public void userAdded(final UserInfo newUser, UserListWindow window) { + // check if we have this user already + for (UserImagePermissions current : permissionList) { + if (current.userId.equals(newUser.userId)) { + LOGGER.debug("User already present in the list, skipping!"); + return; + } + } + // add it to the list with default permissions + permissionList.add(new UserImagePermissions(newUser.userId, new ImagePermissions( + defaultPermissions))); + permissionTable.setData(permissionList, false); + } + }, "Hinzufügen", ownerId); + } + }); + + // delete user button listener + btnRemoveUser.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final UserImagePermissions selected = permissionTable.getSelectedItem(); + if (selected != null && !permissionList.remove(selected)) { + LOGGER.debug("Could not remove: " + selected); + } + permissionTable.setData(permissionList, false); + } + }); + + // keyboard shortcut + permissionTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( + KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); + permissionTable.getActionMap().put("delete", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent ae) { + btnRemoveUser.doClick(); + } + }); + } + + /** + * Initialize the PermissionManager. Note that the given permissionMap and + * defaultPermissions reference + * will get changed when the user interacts with the controls. + * + * @param permissionMap the old permission, to initialize the table with, + * null creates empty table. + * @param defaultPermissions the permissions for a newly added user + * @param ownerId The user to exclude from the add user list. Can be null. + */ + public void initPanel(Map<String, ImagePermissions> permissionMap, + final ImagePermissions defaultPermissions, String ownerId) { + this.ownerId = ownerId; + this.newPermissionMap = permissionMap == null ? new HashMap<String, ImagePermissions>() + : permissionMap; + this.defaultPermissions = defaultPermissions; + + permissionList.clear(); + for (Entry<String, ImagePermissions> e : newPermissionMap.entrySet()) { + permissionList.add(new UserImagePermissions(e.getKey(), e.getValue())); + } + // lexicographic sort on the last names + Collections.sort(permissionList, new Comparator<UserImagePermissions>() { + @Override + public int compare(UserImagePermissions o1, UserImagePermissions o2) { + UserInfo u1 = UserCache.find(o1.userId); + UserInfo u2 = UserCache.find(o2.userId); + if (u1 != null && u2 != null) + return FormatHelper.userName(u1).compareTo(FormatHelper.userName(u2)); + else + return 0; + } + }); + permissionTable.setData(permissionList, false); + } + + /** + * Get map with the permissions set in the table of the manager. + * + * @return Map with new custom permissions, null if something went wrong + */ + public Map<String, ImagePermissions> updatePermissionReferences() { + if (permissionList == null) + return null; + + newPermissionMap.clear(); + + // put permissions of the list into the map + for (UserImagePermissions perm : permissionList) { + newPermissionMap.put(perm.userId, perm.permissions); + } + return newPermissionMap; + } + + /** + * Update the used default permissions of the manager. + * + * @param link + * @param download + * @param edit + * @param admin + */ + public void updateDefaultPermissions(boolean link, boolean download, boolean edit, boolean admin) { + this.defaultPermissions.link = link; + this.defaultPermissions.download = download; + this.defaultPermissions.edit = edit; + this.defaultPermissions.admin = admin; + } + +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/LdapFilterConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/LdapFilterConfigurator.java new file mode 100755 index 00000000..3727b50e --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/LdapFilterConfigurator.java @@ -0,0 +1,221 @@ +package org.openslx.dozmod.gui.configurator; + +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.ChangeListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.LdapFilter; +import org.openslx.dozmod.gui.Gui; +import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor; +import org.openslx.dozmod.gui.control.QLabel; +import org.openslx.dozmod.gui.control.WordWrapLabel; +import org.openslx.dozmod.gui.control.table.LectureLdapFilterTable; +import org.openslx.dozmod.gui.control.table.QScrollPane; +import org.openslx.dozmod.gui.helper.GridManager; +import org.openslx.dozmod.gui.helper.MessageType; + +/** + * Widget for network share configuration of lectures + */ +public class LdapFilterConfigurator extends LdapFilterConfiguratorLayout { + + private static final long serialVersionUID = -3336605759245603655L; + private final static Logger LOGGER = Logger.getLogger(LdapFilterConfigurator.class); + private List<LdapFilter> filterList = null; + + public LdapFilterConfigurator() { + + super(); + + tblFilters.getModel().addTableModelListener(new TableModelListener() { + @Override + public void tableChanged(TableModelEvent e) { + fireChangeEvent(); + } + }); + + tblFilters.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + LdapFilter item = tblFilters.getSelectedItem(); + if (item == null) { + clearInputFields(); + return; + } + // share from the list is selected: fill bottom form and change "Add" to "Apply" + btnDel.setEnabled(true); + txtAttribute.setText(item.attribute); + txtValue.setText(item.value); + btnAdd.setText("Ändern"); + } + }); + + btnAdd.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // check if we are editing an existing share entry or + // creating a new one, check for input either way + LdapFilter input = new LdapFilter(); + + input.attribute = txtAttribute.getText(); + if (input.attribute == null || input.attribute.isEmpty()) { + Gui.showMessageBox("Kein Attribut angegeben", MessageType.ERROR, null, null); + return; + } + input.value = txtValue.getText(); + if (input.value == null) { + input.value = ""; + } + + // either we delete the existing share from the data or we are + // creating a new one, either way add it to the list and update + // the table, if its not present already + if (filterList.contains(input)) { + Gui.showMessageBox("Eintrag bereits vorhanden", MessageType.ERROR, null, null); + return; + } + // now decide whether to create a new entry or update existing one + LdapFilter oldEntry = tblFilters.getSelectedItem(); + if (oldEntry != null) { + // editing existing one, delete it from the internal data + filterList.remove(oldEntry); + } + filterList.add(input); + tblFilters.setData(filterList, false); + clearInputFields(); + } + }); + + btnDel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // try to delete the selected share + LdapFilter selection = tblFilters.getSelectedItem(); + if (selection == null) { + return; + } + try { + if (!filterList.remove(selection)) { + // false if it was not found + LOGGER.error("Could not remove non-existent filter '" + selection.toString() + + "' from the table data: " + filterList.toString()); + return; + } + // refresh table data + tblFilters.setData(filterList, false); + } catch (Exception ex) { + LOGGER.debug("Failed to remove " + selection.toString() + " from the table data.", ex); + return; + } + } + }); + btnDel.setEnabled(false); + } + + private void clearInputFields() { + btnDel.setEnabled(false); + txtAttribute.setText(null); + txtValue.setText(null); + btnAdd.setText("Hinzufügen"); + } + + public List<LdapFilter> getState() { + return filterList; + } + + public boolean setState(List<LdapFilter> data) { + if (data == null) + return false; + if (filterList == null) { + filterList = new ArrayList<>(); + } + filterList = data; + tblFilters.setData(filterList, false); + return true; + } + + protected List<ChangeListener> listenerList = new ArrayList<>(); + + public void addLdapFilterListTableContentConfigurationChangeEventListener(ChangeListener listener) { + listenerList.add(listener); + } + + public void removeNetshareConfigurationChangeEventListener(ChangeListener listener) { + listenerList.remove(listener); + } + + void fireChangeEvent() { + for (ChangeListener listener : new ArrayList<>(listenerList)) { + listener.stateChanged(null); + } + } + + public void addToChangeMonitor(DialogChangeMonitor changeMonitor) { + changeMonitor.add(tblFilters); + } + +} + +/** + * Internal layout class for this widget + */ +class LdapFilterConfiguratorLayout extends JPanel { + + private static final long serialVersionUID = 6479838641542743622L; + + private final static String HELPTEXT = "Geben Sie hier LDAP Filter ein, die die Sichtbarkeit" + + " der Veranstaltung abhängig vom angemeldeten Benutzer einschränken. Eine Veranstaltung" + + " ist sichtbar, sobald einer der angegebenen Filter zutrifft. Zusätzliche Raumbeschränkungen" + + " greifen weiterhin."; + + protected final LectureLdapFilterTable tblFilters = new LectureLdapFilterTable(); + protected final JTextField txtAttribute, txtValue; + protected final JButton btnAdd, btnDel; + + public LdapFilterConfiguratorLayout() { + GridManager grid = new GridManager(this, 1, true, new Insets(3, 3, 3, 3)); + // top info panel + grid.add(new WordWrapLabel(HELPTEXT)).fill(true, false).expand(true, false); + grid.nextRow(); + // middle filter list + grid.add(new QScrollPane(tblFilters)).fill(true, true).expand(true, true); + grid.nextRow(); + + btnDel = new JButton("Entfernen"); + grid.add(btnDel).anchor(GridBagConstraints.EAST); + grid.nextRow(); + + JPanel pnlNewShare = new JPanel(); + GridManager gridNewFilter = new GridManager(pnlNewShare, 2, true); + pnlNewShare.setBorder(BorderFactory.createTitledBorder("Filter definieren")); + gridNewFilter.add(new QLabel("Attribut")); + txtAttribute = new JTextField(); + gridNewFilter.add(txtAttribute).fill(true, false).expand(true, false); + gridNewFilter.nextRow(); + gridNewFilter.add(new QLabel("Wert")); + txtValue = new JTextField(); + gridNewFilter.add(txtValue).fill(true, false).expand(true, false); + gridNewFilter.nextRow(); + btnAdd = new JButton("Hinzufügen"); + gridNewFilter.add(btnAdd, 2).anchor(GridBagConstraints.EAST); + gridNewFilter.nextRow(); + gridNewFilter.finish(false); + grid.add(pnlNewShare).fill(true, false).expand(true, false); + grid.nextRow(); + grid.finish(false); + } +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/LecturePermissionConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/LecturePermissionConfigurator.java new file mode 100644 index 00000000..b99df3f2 --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/LecturePermissionConfigurator.java @@ -0,0 +1,236 @@ +package org.openslx.dozmod.gui.configurator; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.EventListener; +import java.util.EventObject; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.EventListenerList; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.LecturePermissions; +import org.openslx.bwlp.thrift.iface.UserInfo; +import org.openslx.dozmod.gui.control.table.LecturePermissionTable; +import org.openslx.dozmod.gui.control.table.LecturePermissionTable.UserLecturePermissions; +import org.openslx.dozmod.gui.control.table.QScrollPane; +import org.openslx.dozmod.gui.helper.GridManager; +import org.openslx.dozmod.gui.window.UserListWindow; +import org.openslx.dozmod.gui.window.UserListWindow.UserAddedCallback; + +/** + * Panel including LecturePermissionTable and add/remove buttons for setting + * customLecturePermissions. + */ +/** + * @author joe + * + */ +@SuppressWarnings("serial") +public class LecturePermissionConfigurator extends JPanel { + + /** + * Self reference + */ + private LecturePermissionConfigurator me; + + protected LecturePermissionTable permissionTable; + + protected JButton btnAddUser; + protected JButton btnRemoveUser; + + private final ArrayList<UserLecturePermissions> permissionList = new ArrayList<UserLecturePermissions>(); + + private LecturePermissions defaultPermissions; + + private String ownerId = null; + + private static final Logger LOGGER = Logger.getLogger(LecturePermissionConfigurator.class); + + public LecturePermissionConfigurator() { + super(); + me = this; + + GridManager grid = new GridManager(this, 1); + + permissionTable = new LecturePermissionTable(); + + // Panel for the add- and remove buttons + JPanel userButtonPane = new JPanel(); + userButtonPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + userButtonPane.setLayout(new BoxLayout(userButtonPane, BoxLayout.LINE_AXIS)); + + btnAddUser = new JButton("Benutzer hinzufügen"); + userButtonPane.add(btnAddUser); + btnRemoveUser = new JButton("Benutzer entfernen"); + userButtonPane.add(btnRemoveUser); + userButtonPane.add(Box.createGlue()); + + // Put everything into the grid + QScrollPane jsp = new QScrollPane(permissionTable); + jsp.setBackground(UIManager.getColor("Table.background")); + grid.add(jsp).fill(true, true).expand(true, true); + grid.nextRow(); + grid.add(userButtonPane).fill(true, false).expand(true, false); + grid.nextRow(); + grid.finish(false); + + // add user button listener + btnAddUser.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + UserListWindow.open(SwingUtilities.getWindowAncestor(me), new UserAddedCallback() { + @Override + public void userAdded(final UserInfo newUser, UserListWindow window) { + // check if we have this user already + for (UserLecturePermissions current : permissionList) { + if (current.userId.equals(newUser.userId)) { + LOGGER.debug("User already present in the list, skipping!"); + return; + } + } + // add it to the list with default permissions + permissionList.add(new UserLecturePermissions(newUser.userId, new LecturePermissions( + defaultPermissions))); + permissionTable.setData(permissionList, false); + fireUserChangeEvent(); + } + }, "Hinzufügen", ownerId); + } + }); + + // delete user button listener + btnRemoveUser.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final UserLecturePermissions selected = permissionTable.getSelectedItem(); + if (selected != null && !permissionList.remove(selected)) { + LOGGER.debug("Could not remove: " + selected); + } + permissionTable.setData(permissionList, false); + fireUserChangeEvent(); + } + }); + + // keyboard shortcut + permissionTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( + KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); + permissionTable.getActionMap().put("delete", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent ae) { + btnRemoveUser.doClick(); + } + }); + permissionTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + // TODO: This is stupid; permissionTable should fire an event + // if one of the check boxes changes. + fireUserChangeEvent(); + } + }); + } + + /** + * Initialise the PermissionManager + * + * @param permissionMap the old permission, to initialise the table with, + * null creates empty table. + * @param defaultPermissions the permissions for a newly added user + * @param ownerId The user to exclude in the list do add user. May be null. + */ + public void initPanel(Map<String, LecturePermissions> permissionMap, + final LecturePermissions defaultPermissions, String ownerId) { + this.ownerId = ownerId; + this.defaultPermissions = defaultPermissions; + permissionList.clear(); + if (permissionMap != null) { + for (Entry<String, LecturePermissions> e : permissionMap.entrySet()) { + permissionList.add(new UserLecturePermissions(e.getKey(), e.getValue())); + } + } + permissionTable.setData(permissionList, false); + } + + /** + * Get map with the permissions set in the table of the manager. + * + * @return Map with new custom permissions, null if something went wrong + */ + public Map<String, LecturePermissions> getPermissions() { + Map<String, LecturePermissions> newPermissionMap = new HashMap<>(permissionList.size()); + // put permissions of the list into the map + for (UserLecturePermissions perm : permissionList) { + newPermissionMap.put(perm.userId, perm.permissions); + } + return newPermissionMap; + } + + /** + * Update the default permissions used by the manager. + * + * @param admin + * @param edit + */ + public void updateDefaultPermissions(boolean admin, boolean edit) { + defaultPermissions.admin = admin; + defaultPermissions.edit = edit; + } + + + /** + * Custom event mechanism to detect changes to the user list + * (Mostly needed for the reactToChange() stuff in LectureDetailsWindow) + */ + protected EventListenerList listenerList = new EventListenerList(); + + /** + * Shared instance, since there is no data attached (yet) + */ + private static final UserChangeEvent CHANGE_EVENT = new UserChangeEvent(); + + public static class UserChangeEvent extends EventObject { + + public UserChangeEvent() { + super(new Object()); + } + } + + public interface UserChangeEventListener extends EventListener { + public void stateChanged(UserChangeEvent event); + } + + public void addUserChangeEventListener(UserChangeEventListener listener) { + listenerList.add(UserChangeEventListener.class, listener); + } + + public void removeUserChangeEventListener(UserChangeEventListener listener) { + listenerList.remove(UserChangeEventListener.class, listener); + } + + void fireUserChangeEvent() { + Object[] listeners = listenerList.getListenerList(); + for (int i = 0; i < listeners.length; i++) { + if (listeners[i] == UserChangeEventListener.class) { + ((UserChangeEventListener) listeners[i + 1]) + .stateChanged(CHANGE_EVENT); + } + } + } +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetrulesConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetrulesConfigurator.java new file mode 100644 index 00000000..cb80fc3e --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetrulesConfigurator.java @@ -0,0 +1,450 @@ +package org.openslx.dozmod.gui.configurator; + +import java.awt.Color; +import java.awt.Insets; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.EventListener; +import java.util.EventObject; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.EventListenerList; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.NetDirection; +import org.openslx.bwlp.thrift.iface.NetRule; +import org.openslx.dozmod.gui.Gui; +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.gui.helper.TextChangeListener; +import org.openslx.util.Util; + +/** + * Widget for netrules configuration of lectures + */ +public class NetrulesConfigurator extends NetrulesConfiguratorLayout { + + private static final long serialVersionUID = -3497629601818983994L; + private final static Logger LOGGER = Logger + .getLogger(NetrulesConfigurator.class); + + private String originalRawRuleText = null; + /** + * Character defining how the rules are parsed, e.g. for whitespace \\s + * Example: "8.8.8.8 80 in" would be split in -hostname "8.8.8.8" -port "80" + * -direction "in" + */ + private static final String FIELD_DELIMITER = "\\s+"; + + private static final Color FOREGROUND_TEXT_COLOR; + + static { + Color fgOrigColor = UIManager.getDefaults().getColor("ColorChooser.foreground"); + if (fgOrigColor == null) { + // use black as fallback + fgOrigColor = Color.BLACK; + } + FOREGROUND_TEXT_COLOR = fgOrigColor; + } + + public NetrulesConfigurator() { + super(); + + final TextChangeListener docListener = new TextChangeListener() { + @Override + public void changed() { + fireNetrulesConfigurationChangeEvent(new NetrulesConfigurationChangeEvent(NetrulesConfigurator.this)); + } + }; + final SimpleAttributeSet as = new SimpleAttributeSet(); + StyleConstants.setForeground(as, FOREGROUND_TEXT_COLOR); + tpNetworkRules.getDocument().addDocumentListener(docListener); + tpNetworkRules.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + int pos = tpNetworkRules.getCaretPosition(); + if (pos < 0) { + return; + } + // Odd: On windows, getText() returns the text with CRLF line breaks, + // regardless of what you put into the text box. + // The offsets passed to setCharAttrs() below and the caret position + // you get from getCaretPosition() however have to adhere to the + // text version with just LF, exactly how we created the document. + String text = tpNetworkRules.getText().replace("\r", ""); + if (pos >= text.length()) { + return; + } + int start = text.lastIndexOf('\n', pos == 0 ? 0 : pos - 1); + int end = text.indexOf('\n', pos); + if (start == -1) { + start = 0; + } + if (end == -1) { + end = text.length() - 1; + } + if (end <= start) { + return; + } + tpNetworkRules.getStyledDocument().setCharacterAttributes(start, end - start, as, true); + } + }); + } + }); + } + + public boolean hasChanged() { + return !originalRawRuleText.equalsIgnoreCase(tpNetworkRules.getText()); + } + + /** + * Gets the state of the widget as a list of netrules. Internally it first + * transforms the input text in a list of netrules. + * + * @return the list of rules as parsed by parseNetRules() + */ + public List<NetRule> getState(boolean silent) { + // cleanup the TextPane for network rules if needed + return parseNetRules(silent); + } + + /** + * Sets the state of this widget to the given list of netrules. This will + * internally transform the list to its string representation + * + * @param netrules + * as a list of NetRule to set the state to + */ + public void setState(final List<NetRule> netrules) { + // setText() blanks the text area if null is given, so no null checks + originalRawRuleText = decodeNetRulesToText(netrules); + this.tpNetworkRules.setText(originalRawRuleText); + } + + /** + * "Decodes" the given list of NetRule to a single String. This should be + * used to set the text in the TextPane for the network rules + * + * @param netRulesList + * list of NetRule to decode + * @return String representation of the list of rules + */ + public static String decodeNetRulesToText(final List<NetRule> netRulesList) { + if (netRulesList == null || netRulesList.isEmpty()) + return ""; + + String decodedRules = ""; + Iterator<NetRule> it = netRulesList.iterator(); + while (it.hasNext()) { + String currentLine = ""; + NetRule currentRule = it.next(); + // simple test for validity (since this comes from the server it + // should be correct anyways) + if (currentRule.host.isEmpty() || currentRule.port > 65535) { + LOGGER.error("Invalid rule! Ignoring: " + currentRule.host + + ":" + currentRule.port); + continue; + } + currentLine += currentRule.host + " \t "; + currentLine += currentRule.port + " \t "; + currentLine += currentRule.direction.name(); + decodedRules += currentLine + + (it.hasNext() ? "\n" : ""); + } + return decodedRules; + } + + /** + * Parses the given rawNetRules String to a list of NetRule + * + * @param rawNetRules + * the raw text to be parsed + * @return list of netrules if successful. If any errors occured while + * parsing, null is returned. + */ + public List<NetRule> parseNetRules(boolean silent) { + String rawNetRules = tpNetworkRules.getText().trim(); + List<NetRule> rulesList = new ArrayList<NetRule>(); + if (rawNetRules.isEmpty()) { + return rulesList; + } + + // split it line by line + boolean invalid = false; // True if the rules are invalid and null should be returned + DefaultStyledDocument newdoc = null; + if (!silent) { + newdoc = new DefaultStyledDocument(); // Used to build new document with colors + } + StringBuilder errors = new StringBuilder(); // Error messages to show (if not silent) + int lineNo = 0; // Show line numbers in error messages + Set<String> warnedHosts = new HashSet<>(); // Ask only once about each unknown host + for (String ruleLine : rawNetRules.split("[\r\n]+")) { + if (silent && invalid) + return null; + Color lineColor = null; + LOGGER.debug("Parsing rule: " + ruleLine); + // split the fields and check if we have 3 as expected. + String[] fields = ruleLine.trim().split(FIELD_DELIMITER); + if (fields.length != 3) { + lineNo += addLine(newdoc, ruleLine, Color.RED, true); + // log numbers for fields independently... + LOGGER.debug("Invalid number of fields! Expected 3, got: " + fields.length); + if (fields.length > 3) { + errors.append("Zeile " + lineNo + ": Zu viele Felder.\n"); + } else { + errors.append("Zeile " + lineNo + ": Zu wenig Felder.\n"); + } + invalid = true; + continue; + } + // Have 3 fields, pretty up + String ruleDirection = fields[2].toUpperCase(); + ruleLine = fields[0] + " \t " + fields[1] + " \t " + ruleDirection; + + // start to check fields one by one from the last to the first .... + // check net direction: accept either 'in' or 'out' (case + // insensitive) + // TODO support combined 'in/out' rules + if (!ruleDirection.equals("IN") && !ruleDirection.equals("OUT")) { + lineNo += addLine(newdoc, ruleLine, Color.RED, true); + LOGGER.debug("Invalid net direction! Expected 'in' or out'. Got: " + ruleDirection); + errors.append("Zeile " + lineNo + ": Ungültige Richtung. Bitte nutzen Sie 'IN' bzw. 'OUT'.\n"); + invalid = true; + continue; + } + // check port: accept if >= 0 and <= 65535 + int port = Util.parseInt(fields[1], -1); + + // port = 0 means match only host (all protocols and ports) + if (port < 0 || port > 65535) { + lineNo += addLine(newdoc, ruleLine, Color.RED, true); + LOGGER.debug("Invalid port! Got: " + port); + errors.append("Zeile " + lineNo + ": Ungültiger Port. Gültiger Bereich ist 0-65535.\n"); + invalid = true; + continue; + } + // check hostname: bit more to do here + // for IPs and/or resolvable hostnames we make use of java.net's + // InetAddress.getByName() method which checks the validity of + // an IP-Address represented by a string or if the given hostname + // is resolvable. If any of these happen, we have a valid hostname. + // Non-resolvable hostnames are handled differently, see after the + // try/catch-block + InetAddress ruleHost = null; + try { + // TODO: Find a way to reliably set a timeout (external DNS library?) + // Otherwise just remove this check + ruleHost = InetAddress.getByName(fields[0]); + } catch (UnknownHostException e) { + // might be good to see this exception in the log file + // LOGGER.debug("Invalid hostname (java.net): ", e); + } + if (ruleHost == null) { + // either invalid IP-Address or an invalid resolvable hostname + // however it might also be a non-resolvable hostname that would + // be valid in the actual pool-rooms, so lets check its syntax + // according to: http://tools.ietf.org/html/rfc1034#section-3.1 + LOGGER.debug("Invalid host/IP! Got: " + fields[0]); + String checkRes = checkHostnameSimple(fields[0]); + if (checkRes == null) { + if (!silent + && !warnedHosts.contains(fields[0]) + && !Gui.showMessageBox("Konnte '" + fields[0] + + "' nicht auflösen. Wollen Sie diesen Hostnamen trotzdem verwenden?", + MessageType.WARNING_RETRY, null, null)) { + invalid = true; + } + warnedHosts.add(fields[0]); + lineColor = Color.ORANGE; + } else { + lineNo += addLine(newdoc, ruleLine, Color.RED, true); + errors.append("Zeile " + lineNo + ": " + checkRes + "\n"); + invalid = true; + continue; + } + } + // Made it to here - line is valid + lineNo += addLine(newdoc, ruleLine, lineColor, false); + rulesList.add(new NetRule(NetDirection.valueOf(ruleDirection), fields[0], port)); + } + if (!silent && errors.length() != 0) { + Gui.showMessageBox("Fehler beim Auswerten der angegebenen Netzwerkregeln.\n\n" + errors.toString() + + "\nBitte geben Sie die Regeln zeilenweise im Format\n" + + "<host> <port> <IN|OUT>\n" + + "an.", + MessageType.ERROR, null, null); + } + if (newdoc != null) { + tpNetworkRules.setDocument(newdoc); + } + if (invalid) { + return null; + } + // Success + return rulesList; + } + + private int addLine(DefaultStyledDocument doc, String line, Color color, boolean bold) { + if (doc == null) + return 0; + if (color == null) { + color = FOREGROUND_TEXT_COLOR; + } + SimpleAttributeSet attrs = new SimpleAttributeSet(); + StyleConstants.setForeground(attrs, color); + StyleConstants.setBold(attrs, bold); + try { + doc.insertString(doc.getLength(), line + "\n", attrs); + } catch (BadLocationException e) { + LOGGER.warn("Cannot append to new textbox document", e); + } + return 1; + } + + /** + * Very simple hostname check for the given String. This will only check for + * some requirements of valid hostnames as stated in + * http://tools.ietf.org/html/rfc1034#section-3.1 To recap: max length of + * the whole hostname must be < 254 ASCII characters, all domain labels + * (between two dots) must be between 1 and 63 chars long and domain labels + * can only contain digits, letters and hyphen. (Note: we also accept + * forward slash to accept subnets!) + * + * @param hostname the hostname to check for syntactical validity + * @return null if valid, error string otherwise + */ + private String checkHostnameSimple(final String hostname) { + if (hostname.length() > 254) { + return "Hostname ist zu lang."; + } + // split by '.' to get domain levels + boolean allNumeric = true; + String[] domainLabels = hostname.split("\\."); + for (String domainLabel : domainLabels) { + if (domainLabel.length() > 63) { + // fail since domain level should be max 63 chars + return "Domain-Ebene '" + domainLabel + "' länger als 63 Zeichen."; + } + if (Util.parseInt(domainLabel, -1) == -1) { + allNumeric = false; + } + // checking for valid chars is pointless with punycode + } + if (allNumeric && domainLabels.length != 4) { + return "Unvollständige IP-Adresse."; + } + return null; + } + + /** + * Custom event mechanism to detect changes to the netrules list (Mostly + * needed for the reactToChange() stuff in LectureDetailsWindow) + */ + protected EventListenerList listenerList = new EventListenerList(); + + public class NetrulesConfigurationChangeEvent extends EventObject { + + private static final long serialVersionUID = -8779550754760035845L; + + public NetrulesConfigurationChangeEvent(Object source) { + super(source); + } + } + + public interface NetrulesConfigurationChangeEventListener extends + EventListener { + public void stateChanged(NetrulesConfigurationChangeEvent event); + } + + public void addNetrulesConfigurationChangeEventListener( + NetrulesConfigurationChangeEventListener listener) { + listenerList.add(NetrulesConfigurationChangeEventListener.class, + listener); + } + + public void removeNetrulesConfigurationChangeEventListener( + NetrulesConfigurationChangeEventListener listener) { + listenerList.remove(NetrulesConfigurationChangeEventListener.class, + listener); + } + + void fireNetrulesConfigurationChangeEvent( + NetrulesConfigurationChangeEvent evt) { + Object[] listeners = listenerList.getListenerList(); + for (int i = 0; i < listeners.length; i++) { + if (listeners[i] == NetrulesConfigurationChangeEventListener.class) { + ((NetrulesConfigurationChangeEventListener) listeners[i + 1]) + .stateChanged(evt); + } + } + } +} + +/** + * Internal layout class for this widget + */ +class NetrulesConfiguratorLayout extends JPanel { + + private static final long serialVersionUID = 5266120380443817325L; + private final static String txtNetworkOptionsTitle = "Netzwerk Einstellungen"; + private final static String txtNetworkOptionsDesc = "Wenn Sie den Internetzugriff deaktiviert haben," + + " können Sie hier Ausnahmen definieren (Whitelist)." + + " Bitte definieren Sie Ihre Regeln im Format\n<host> <port> <in|out>.\n" + + "Sie können Port 0 angeben, was sämtlichen TCP und UDP Ports eines Hosts entspricht.\n\n" + + "Wenn Sie Internetzugriff aktivieren, hat diese Liste den gegenteiligen Effekt"; + private final static String txtNetworkRulesTitle = "Netzwerkregeln"; + + private final JPanel pnlNetworkOptions; + protected final JTextPane tpNetworkRules; + + public NetrulesConfiguratorLayout() { + + GridManager grid = new GridManager(this, 1, true, + new Insets(5, 5, 5, 5)); + + // middle panel for network rules + pnlNetworkOptions = new JPanel(); + GridManager gridNetworkOptions = new GridManager(pnlNetworkOptions, 1, + true, new Insets(2, 2, 2, 2)); + pnlNetworkOptions.setBorder(BorderFactory + .createTitledBorder(txtNetworkOptionsTitle)); + tpNetworkRules = new JTextPane(); + + JScrollPane scpNetworkRules = new JScrollPane(tpNetworkRules, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + pnlNetworkOptions.setBorder(BorderFactory + .createTitledBorder(txtNetworkRulesTitle)); + gridNetworkOptions + .add(new WordWrapLabel(txtNetworkOptionsDesc)) + .fill(true, false).expand(true, false); + gridNetworkOptions.nextRow(); + gridNetworkOptions.add(scpNetworkRules).fill(true, true) + .expand(true, true); + gridNetworkOptions.finish(false); + + // build the final grid + grid.add(pnlNetworkOptions).fill(true, true).expand(true, true); + grid.finish(false); + } +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetshareConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetshareConfigurator.java new file mode 100644 index 00000000..6141992c --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/NetshareConfigurator.java @@ -0,0 +1,372 @@ +package org.openslx.dozmod.gui.configurator; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelListener; + +import org.apache.log4j.Logger; +import org.openslx.bwlp.thrift.iface.NetShare; +import org.openslx.bwlp.thrift.iface.NetShareAuth; +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.QLabel; +import org.openslx.dozmod.gui.control.ComboBox.ComboBoxRenderer; +import org.openslx.dozmod.gui.control.table.NetshareTable; +import org.openslx.dozmod.gui.control.table.QScrollPane; +import org.openslx.dozmod.gui.helper.GridManager; +import org.openslx.dozmod.gui.helper.MessageType; +import org.openslx.dozmod.util.FormatHelper; + +/** + * Widget for network share configuration of lectures + */ +public class NetshareConfigurator extends NetshareConfiguratorLayout { + + private static final long serialVersionUID = -3336605759245603655L; + private final static Logger LOGGER = Logger.getLogger(NetshareConfigurator.class); + private List<NetShare> shareList = null; + + // mount points / win drive letters - ideally, we would check whether the image is linux or windows based + // and either show a drive selection list like this one, or a textfield for a user-defined mount point... + private Character[] mountPoints = { 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', + 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; + + public NetshareConfigurator() { + super(); + tblNetshare.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + NetShare item = tblNetshare.getSelectedItem(); + // ugly block-wise sets, but only one test needed compared to + // doing lots of item != null ? ... : ... + if (item == null) { + clearInputFields(); + return; + } + // share from the list is selected: fill bottom form and change "Add" to "Apply" + btnDel.setEnabled(true); + tfSharePath.setText(item.path); + tfShareName.setText(item.displayname); + tfUsername.setText(item.username); + tfPassword.setText(item.password); + cboNetshareAuth.setSelectedItem(item.auth); + cboNetshareMountPoint.setSelectedItem(Character.valueOf(item.mountpoint.charAt(0))); + btnAdd.setText("Ändern"); + } + }); + + cboNetshareMountPoint.setModel(new DefaultComboBoxModel<Character>(mountPoints)); + cboNetshareMountPoint.setSelectedItem(null); + + // combobox for share authentication types + cboNetshareAuth.setModel(new DefaultComboBoxModel<NetShareAuth>(NetShareAuth.values())); + cboNetshareAuth.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + NetShareAuth selectedAuth = cboNetshareAuth.getItemAt(cboNetshareAuth.getSelectedIndex()); + if (selectedAuth == null) + return; + boolean activate = selectedAuth == NetShareAuth.OTHER_USER; + // username field is needed to either special or guest user + tfUsername.setEnabled(activate); + lblUsername.setEnabled(activate); + tfPassword.setEnabled(activate); + lblPassword.setEnabled(activate); + chkShowPass.setEnabled(activate); + } + }); + cboNetshareAuth.setSelectedItem(null); + + btnAdd.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // check if we are editing an existing share entry or + // creating a new one, check for input either way + NetShare input = new NetShare(); + + input.path = tfSharePath.getText(); + if (input.path == null || input.path.isEmpty()) { + lblError.setText("Kein Pfad angegeben!"); + return; + } + String inputShareName = tfShareName.getText(); + if (inputShareName.isEmpty()) { + lblError.setText("Kein Anzeigename angegeben!"); + return; + } + input.displayname = inputShareName; + + NetShareAuth inputNetShareAuth = cboNetshareAuth + .getItemAt(cboNetshareAuth.getSelectedIndex()); + if (inputNetShareAuth == null) { + lblError.setText("Kein Authentifizierungstyp angegeben!"); + return; + } + input.auth = inputNetShareAuth; + switch (inputNetShareAuth) { + case LOGIN_USER: + // this uses the bwLehrpool client's logged in user + // we don't need to have anything + break; + case OTHER_USER: + // save given username/password + input.username = tfUsername.getText(); + input.password = new String(tfPassword.getPassword()); + if (input.username.isEmpty()) { + lblError.setText("Kein Nutzername angegeben!"); + return; + } + break; + default: + input = null; + break; + } + if (input == null) { + lblError.setText("Fehlerhafte Eingabe"); + LOGGER.debug("Bad input, aborting."); + return; + } + // now check for optional mount path + Character inputMountPoint = cboNetshareMountPoint + .getItemAt(cboNetshareMountPoint.getSelectedIndex()); + if (inputMountPoint == null) { + lblError.setText("Kein Ziel angegeben!"); + return; + } + input.mountpoint = inputMountPoint.toString(); + // now decide whether to create a new entry or update existing one + NetShare oldEntry = tblNetshare.getSelectedItem(); + if (oldEntry != null) { + // editing existing one, delete it from the internal data + if (!shareList.remove(oldEntry)) { + lblError.setText("Änderung fehlgeschlagen!"); + LOGGER.debug("Failed to remove selected share for replacement: " + oldEntry); + return; + } + tblNetshare.setData(shareList, false); + } + // either we delete the existing share from the data or we are + // creating a new one, either way add it to the list and update + // the table, if its not present already + if (shareList.contains(input)) { + lblError.setText("Existiert bereits!"); + LOGGER.error("Network share already in the list, aborting."); + return; + } + // if a password is set, warn the user about its unsafe storage + // which we might want to implement one day... + if (input.password != null && !input.password.isEmpty()) { + if (!Gui.showMessageBox( + "Das eingebene Passwort wird im Klartext gespeichert " + + "und ist in der VM für jeden Nutzer sichtbar.\n" + + "Verwenden Sie auf keinen Fall sicherheitskritische Passwörter!" + + "\n\nMöchten Sie diesen Netzlaufwerk trotzdem hinzufügen?", + MessageType.QUESTION_YESNO, LOGGER, null)) { + return; + } + } + lblError.setText(null); + shareList.add(input); + tblNetshare.setData(shareList, false); + clearInputFields(); + } + }); + + btnDel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // try to delete the selected share + NetShare selection = tblNetshare.getSelectedItem(); + if (selection == null) { + return; + } + try { + if (!shareList.remove(selection)) { + // false if it was not found + LOGGER.error("Could not remove non-existant network share '" + selection.toString() + + "' from the table data: " + shareList.toString()); + return; + } + // refresh table data + tblNetshare.getModel().setData(shareList); + } catch (Exception ex) { + LOGGER.debug("Failed to remove " + selection.toString() + " from the table data.", ex); + return; + } + } + }); + + chkShowPass.addItemListener(new ItemListener() { + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + tfPassword.setEchoChar('*'); + } else { + tfPassword.setEchoChar((char) 0); + } + } + }); + chkShowPass.setEnabled(false); + tfUsername.setEnabled(false); + lblUsername.setEnabled(false); + tfPassword.setEnabled(false); + lblPassword.setEnabled(false); + } + + private void clearInputFields() { + btnDel.setEnabled(false); + tfSharePath.setText(null); + tfShareName.setText(null); + tfUsername.setText(null); + tfPassword.setText(null); + cboNetshareAuth.setSelectedItem(null); + cboNetshareMountPoint.setSelectedItem(null); + btnAdd.setText("Hinzufügen"); + } + public List<NetShare> getState() { + return shareList; + } + + public boolean setState(List<NetShare> data) { + if (data == null) + return false; + if (shareList == null) { + shareList = new ArrayList<>(); + } + shareList = data; + tblNetshare.setData(shareList, false); + return true; + } + + public void addTableModelListener(TableModelListener listener) { + tblNetshare.getModel().addTableModelListener(listener); + } + + public void addToChangeMonitor(DialogChangeMonitor changeMonitor) { + changeMonitor.add(tblNetshare); + } +} + +/** + * Internal layout class for this widget + */ +class NetshareConfiguratorLayout extends JPanel { + + private static final long serialVersionUID = 6479525981542743622L; + + private final static String txtNetshareDesc = "<html>Hier können Sie Netzlaufwerke angeben," + + " die automatisch beim Start der Veranstaltung eingebunden werden sollen." + + " Der Platzhalter <em>%loginuser%</em> wird im Pfad durch den Loginnamen des Nutzers ersetzt.</html>"; + + protected final QLabel lblShareAuth, lblSharePath, lblShareName, lblMountPoint, lblUsername, lblPassword, + lblError; + protected final NetshareTable tblNetshare = new NetshareTable(); + protected final JTextField tfSharePath, tfShareName, tfUsername; + protected final JPasswordField tfPassword; + protected final JButton btnAdd, btnDel; + protected final JCheckBox chkShowPass; + protected final ComboBox<NetShareAuth> cboNetshareAuth = new ComboBox<>(new ComboBoxRenderer<NetShareAuth>() { + @Override + public String renderItem(NetShareAuth item) { + if (item == null) + return null; + return FormatHelper.netShareAuthName(item); + } + }); + protected final ComboBox<Character> cboNetshareMountPoint = new ComboBox<>(new ComboBoxRenderer<Character>() { + @Override + public String renderItem(Character letter) { + if (letter == null) + return null; + return letter.toString() + ":"; + } + }); + + public NetshareConfiguratorLayout() { + GridManager grid = new GridManager(this, 5, true, new Insets(3, 3, 3, 3)); + // top info panel + grid.add(new JLabel(txtNetshareDesc), 5).fill(true, false).expand(true, false); + grid.nextRow(); + // middle netshare list + grid.add(new QScrollPane(tblNetshare), 5).fill(true, true).expand(true, true); + grid.nextRow(); + + JPanel pnlNewShare = new JPanel(); + GridManager gridNewShare = new GridManager(pnlNewShare, 6, true); + pnlNewShare.setBorder(BorderFactory.createTitledBorder("Details")); + lblSharePath = new QLabel("Pfad"); + gridNewShare.add(lblSharePath); + tfSharePath = new JTextField(); + gridNewShare.add(tfSharePath, 5).fill(true, false).expand(true, false); + gridNewShare.nextRow(); + // bottom form to add a new share + lblShareName = new QLabel("Anzeigename"); + lblMountPoint = new QLabel("Ziel"); + tfShareName = new JTextField(); + // extra panel for fancy layout purposes... + JPanel pnlShareName = new JPanel(); + pnlShareName.setLayout(new BoxLayout(pnlShareName, BoxLayout.LINE_AXIS)); + pnlShareName.add(lblShareName); + pnlShareName.add(tfShareName); + pnlShareName.add(lblMountPoint); + pnlShareName.add(cboNetshareMountPoint); + gridNewShare.add(lblShareName); + gridNewShare.add(pnlShareName, 5).fill(true, false).expand(true, false); + gridNewShare.nextRow(); + lblShareAuth = new QLabel("Authentifizierung"); + gridNewShare.add(lblShareAuth); + gridNewShare.add(cboNetshareAuth, 5).fill(true, false).expand(true, false); + gridNewShare.nextRow(); + lblUsername = new QLabel("Username"); + gridNewShare.add(lblUsername); + tfUsername = new JTextField(20); + gridNewShare.add(tfUsername, 2).fill(true, false).expand(true, false); + lblPassword = new QLabel("Passwort"); + gridNewShare.add(lblPassword); + tfPassword = new JPasswordField(20); + gridNewShare.add(tfPassword, 2).fill(true, false).expand(true, false); + gridNewShare.nextRow(); + chkShowPass = new JCheckBox("Passwort anzeigen"); + JPanel pnlShowPass = new JPanel(); + pnlShowPass.setLayout(new BoxLayout(pnlShowPass, BoxLayout.LINE_AXIS)); + pnlShowPass.add(Box.createGlue()); + pnlShowPass.add(chkShowPass, BorderLayout.LINE_END); + gridNewShare.add(pnlShowPass, 6).fill(true, false).expand(true, false); + gridNewShare.nextRow(); + grid.add(pnlNewShare, 5).fill(true, false).expand(true, false); + grid.nextRow(); + // bottom panel for right-aligned button... + JPanel pnlButtonAdd = new JPanel(); + pnlButtonAdd.setLayout(new BoxLayout(pnlButtonAdd, BoxLayout.LINE_AXIS)); + btnAdd = new JButton("Hinzufügen"); + btnDel = new JButton("Entfernen"); + lblError = new QLabel(""); + lblError.setForeground(Color.RED); + pnlButtonAdd.add(lblError); + pnlButtonAdd.add(Box.createGlue()); + pnlButtonAdd.add(btnAdd, BorderLayout.LINE_END); + pnlButtonAdd.add(btnDel, BorderLayout.LINE_END); + grid.add(pnlButtonAdd, 5).fill(true, false).expand(true, false); + grid.finish(false); + } +} diff --git a/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/RunscriptConfigurator.java b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/RunscriptConfigurator.java new file mode 100644 index 00000000..52a686bb --- /dev/null +++ b/dozentenmodul/src/main/java/org/openslx/dozmod/gui/configurator/RunscriptConfigurator.java @@ -0,0 +1,266 @@ +package org.openslx.dozmod.gui.configurator; + +import java.awt.Color; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.EventListener; +import java.util.EventObject; + +import javax.swing.Box; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.event.EventListenerList; + +import org.openslx.dozmod.gui.changemonitor.DialogChangeMonitor; +import org.openslx.dozmod.gui.configurator.RunscriptConfigurator.RunscriptType; +import org.openslx.dozmod.gui.control.ComboBox; +import org.openslx.dozmod.gui.control.QLabel; +import org.openslx.dozmod.gui.control.WordWrapLabel; +import org.openslx.dozmod.gui.control.ComboBox.ComboBoxRenderer; +import org.openslx.dozmod.gui.helper.GridManager; +import org.openslx.dozmod.gui.helper.TextChangeListener; + +/** + * Widget for advanced configuration options for lectures. This handles + * following options - Network rules - Runscript - USB + */ +public class RunscriptConfigurator extends RunscriptConfiguratorLayout { + + private static final long serialVersionUID = -3497629601818983994L; + + public static enum RunscriptType { + SHELL("Shellskript", "sh"), BATCH("Windows-Batch", "bat"); + + private final String displayName; + private final String extension; + + private RunscriptType(String name, String extension) { + this.displayName = name; + this.extension = extension; + } + + @Override + public String toString() { + return extension + " (" + displayName + ")"; + } + } + + public RunscriptConfigurator() { + super(); + + final TextChangeListener docListener = new TextChangeListener() { + @Override + public void changed() { + fireRunscriptConfigurationChangeEvent(new RunscriptConfigurationChangeEvent( + new Object())); + } + }; + taRunScript.getDocument().addDocumentListener(docListener); + cboRunscriptType.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + fireRunscriptConfigurationChangeEvent(new RunscriptConfigurationChangeEvent( + new Object())); + } + }); + cboRunscriptType.getEditor().getEditorComponent().addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + fireRunscriptConfigurationChangeEvent(new RunscriptConfigurationChangeEvent( + new Object())); + } + }); + } + + private void setError(final String msg) { + lblError.setText(msg); + } + /** + * Gets the runscript as String. The chosen interpreter will get encoded as + * the first line of the script. + * + * @return runscript as String. If no text was entered, returns a empty + * string. + */ + public String 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 + String extension = null; + Object cboContent = cboRunscriptType.getEditor().getItem(); + if (cboContent instanceof RunscriptType) { + extension = ((RunscriptType) cboContent).extension; + } else if (cboContent instanceof String) { + extension = (String) cboContent; + } + String taInputText = taRunScript.getText(); + if (taInputText.isEmpty()) + return ""; + if (extension == null || extension.isEmpty()) { + // this should never happen, so return null to report this invalid state + setError("Fehlende Dateinamenerweiterung!"); + return null; + } + setError(""); + return "ext=" + extension + "\n" + taInputText; + } + + /** + * 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 config + * AdvancedConfiguration to set the state to + */ + public void setState(final String config) { + if (config == null || config.isEmpty()) { + cboRunscriptType.setSelectedItem(null); + taRunScript.setText(""); + return; + } + String extensionHeader = null; + try { + BufferedReader reader = new BufferedReader(new StringReader(config)); + extensionHeader = reader.readLine(); + reader.close(); + } catch (IOException e) { + // swallow ... + } + if (extensionHeader != null) { + // we should have following format: ext=<interpreter> + // e.g. ext=sh + extensionHeader = extensionHeader.replace("ext=", ""); + for (RunscriptType type : RunscriptType.values()) { + if (type.extension.equals(extensionHeader)) { + cboRunscriptType.setSelectedItem(type); + // mark that we found it by nulling the shebang... + extensionHeader = null; + continue; + } + } + if (extensionHeader != null) { + // user specific shebang, so just write the text to the cbo + cboRunscriptType.getEditor().setItem(extensionHeader); + } + } + // finished with the interpreter, remove that line from the given config + // before setting that text + taRunScript.setText(config.replaceFirst("^ext=.*\n", "")); + } + + /** + * Custom event mechanism to detect changes to the user list (Mostly needed + * for the reactToChange() stuff in LectureDetailsWindow) + */ + protected EventListenerList listenerList = new EventListenerList(); + + public class RunscriptConfigurationChangeEvent extends EventObject { + + private static final long serialVersionUID = -8779550754760035845L; + + public RunscriptConfigurationChangeEvent(Object source) { + super(source); + } + } + + public interface RunscriptConfigurationChangeEventListener extends + EventListener { + public void stateChanged(RunscriptConfigurationChangeEvent event); + } + + public void addRunscriptConfigurationChangeEventListener( + RunscriptConfigurationChangeEventListener listener) { + listenerList.add(RunscriptConfigurationChangeEventListener.class, + listener); + } + + public void removeRunscriptConfigurationChangeEventListener( + RunscriptConfigurationChangeEventListener listener) { + listenerList.remove(RunscriptConfigurationChangeEventListener.class, + listener); + } + + void fireRunscriptConfigurationChangeEvent( + RunscriptConfigurationChangeEvent evt) { + Object[] listeners = listenerList.getListenerList(); + for (int i = 0; i < listeners.length; i++) { + if (listeners[i] == RunscriptConfigurationChangeEventListener.class) { + ((RunscriptConfigurationChangeEventListener) listeners[i + 1]) + .stateChanged(evt); + } + } + } + + public void addToChangeMonitor(DialogChangeMonitor changeMonitor) { + changeMonitor.add(taRunScript); + changeMonitor.addEditableCombo(cboRunscriptType, null); + } + +} + +/** + * Internal layout class for the advanced configurator (to keep it clean even + * for widgets) + */ +class RunscriptConfiguratorLayout extends JPanel { + + private static final long serialVersionUID = 648729071828404053L; + + private final static String txtRunScriptDesc = "Ein hier eingetragenes Skript wird nach dem Start dieser VM automatisch ausgeführt."; + protected final QLabel lblError; + protected final JTextArea taRunScript; + protected final ComboBox<RunscriptType> cboRunscriptType; + + public RunscriptConfiguratorLayout() { + + GridManager grid = new GridManager(this, 2, true, new Insets(5, 5, 5, 5)); + 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(new WordWrapLabel(txtRunScriptDesc, 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(); + grid.add(scpRunScript, 2).fill(true, true).expand(true, true); + 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); + + } +} |