package org.openslx.dozmod.gui; import java.awt.BorderLayout; import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JSeparator; import javax.swing.SwingUtilities; import org.apache.log4j.Logger; import org.apache.thrift.TException; import org.openslx.bwlp.thrift.iface.SatelliteStatus; import org.openslx.bwlp.thrift.iface.TransferState; import org.openslx.dozmod.App; import org.openslx.dozmod.Branding; import org.openslx.dozmod.Config; import org.openslx.dozmod.filetransfer.DownloadTask; import org.openslx.dozmod.filetransfer.PassiveTransfer; import org.openslx.dozmod.filetransfer.TransferEvent; import org.openslx.dozmod.filetransfer.TransferEventListener; import org.openslx.dozmod.filetransfer.UploadTask; import org.openslx.dozmod.gui.Gui.GuiCallable; import org.openslx.dozmod.gui.activity.ActivityPanel; import org.openslx.dozmod.gui.activity.DownloadPanel; import org.openslx.dozmod.gui.activity.PassiveUploadPanel; import org.openslx.dozmod.gui.activity.UpdatePanel; import org.openslx.dozmod.gui.activity.UploadPanel; import org.openslx.dozmod.gui.control.QLabel; import org.openslx.dozmod.gui.helper.CompositePage; import org.openslx.dozmod.gui.helper.DebugWindow; import org.openslx.dozmod.gui.helper.GridManager; import org.openslx.dozmod.gui.helper.MessageType; import org.openslx.dozmod.gui.helper.UiFeedback; import org.openslx.dozmod.gui.window.CheckUpdateWindow; import org.openslx.dozmod.gui.window.ConfigWindow; import org.openslx.dozmod.gui.window.DisclaimerWindow; import org.openslx.dozmod.gui.window.ImageListWindow; import org.openslx.dozmod.gui.window.LectureListWindow; import org.openslx.dozmod.gui.window.LoginWindow; import org.openslx.dozmod.gui.window.MainMenuWindow; import org.openslx.dozmod.gui.window.PrivacyNoticeWindow; import org.openslx.dozmod.gui.window.VirtualizerNoticeWindow; import org.openslx.dozmod.state.UploadWizardState; import org.openslx.dozmod.thrift.GuiErrorCallback; import org.openslx.dozmod.thrift.Session; import org.openslx.dozmod.thrift.ThriftActions; import org.openslx.dozmod.util.ClientVersion; import org.openslx.dozmod.util.FormatHelper; import org.openslx.dozmod.util.DesktopEnvironment; import org.openslx.dozmod.util.DesktopEnvironment.Link; import org.openslx.thrifthelper.ThriftManager; import org.openslx.util.QuickTimer; import org.openslx.util.QuickTimer.Task; public abstract class MainWindow { private final static Logger LOGGER = Logger.getLogger(MainWindow.class); private static final JFrame mainWindow; private static final JPanel mainContainer; private static final JPanel activityPanel; private static CompositePage currentPage; private static boolean isQuitQuestionOpen = false; private static final Map, CompositePage> pages = new ConcurrentHashMap<>(); private static final List activities = new ArrayList<>(); /** * Set the visible page of the main window. * * @param clazz */ public static T showPage(Class clazz) { T page = getPage(clazz); if (page == null) { throw new RuntimeException("Tried to show unknown page " + clazz.getSimpleName()); } if (currentPage != null) { if (!currentPage.requestHide()) { return null; // Canceled by currently shown page } currentPage.setVisible(false); } currentPage = page; // sets the starting preferred size. currentPage.requestShow(); currentPage.setVisible(true); mainWindow.validate(); return page; } @SuppressWarnings("unchecked") public static T getPage(Class clazz) { CompositePage page = pages.get(clazz); if (page == null) { return null; } return (T) page; } public static void centerShell(Window shell) { Gui.centerShellOverShell(mainWindow, shell); } static { mainWindow = Gui.syncExec(new GuiCallable() { @Override public JFrame run() { return new JFrame(Branding.getApplicationName()); } }); mainContainer = Gui.syncExec(new GuiCallable() { @Override public JPanel run() { return new JPanel(); } }); activityPanel = Gui.syncExec(new GuiCallable() { @Override public JPanel run() { return new JPanel(); } }); } /** * Initializes the GUI by creating the main window, adding the menu and * creating the login mask as the first content window. * Further sets up the global thrift error callback to catch any * connection errors during the communication with the servers. */ public static void open() { // init SWT stuff mainWindow.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // Catch the close button (X) mainWindow.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { MainWindow.askApplicationQuit(); } }); // Set up thrift error message displaying ThriftManager.setMasterErrorCallback(new GuiErrorCallback(mainWindow, "dem " + Branding.getServiceName() + "-Zentralserver")); ThriftManager.setSatelliteErrorCallback(new GuiErrorCallback(mainWindow, "dem Satellitenserver")); // Same for config errors Config.setErrorCallback(new Config.ErrorCallback() { @Override public void writeError(final Throwable t) { Gui.asyncExec(new Runnable() { @Override public void run() { Gui.showMessageBox(mainWindow, "Konnte Programmeinstellungen nicht speichern", MessageType.WARNING, LOGGER, t); } }); } }); // Global key listener KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() { @Override public boolean dispatchKeyEvent(KeyEvent event) { int type = event.getID(); int code = event.getKeyChar(); if (code == 17) { // Ctrl-Q = Quit if (type == KeyEvent.KEY_RELEASED && !isQuitQuestionOpen) { isQuitQuestionOpen = true; askApplicationQuit(); event.consume(); } } else if (code == 27 || code == 23) { // ESC or Ctrl-W closes current window if (type == KeyEvent.KEY_PRESSED) { Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager() .getActiveWindow(); if (window instanceof UiFeedback) { ((UiFeedback) window).escapePressed(); event.consume(); } } } return event.isConsumed(); } }); // Set layout for the mainshell, items added to the shell should get a gridData mainContainer.setLayout(new BoxLayout(mainContainer, BoxLayout.PAGE_AXIS)); // Scale the ui with the font. mainWindow.setMinimumSize(Gui.getScaledDimension(1050, 670)); // register all pages of the main window registerPage(new MainMenuWindow()); registerPage(new ImageListWindow()); registerPage(new LectureListWindow()); registerPage(new PleaseWait()); // Debug? if (System.getProperty("log") != null) { DebugWindow win = new DebugWindow(); win.setMinimumSize(Gui.getScaledDimension(0, 250)); win.setPreferredSize(win.getMinimumSize()); mainWindow.getContentPane().add(win, BorderLayout.PAGE_START); } // Activity panel at the bottom (file transfer, ...) activityPanel.setLayout(new BoxLayout(activityPanel, BoxLayout.PAGE_AXIS)); activityPanel.setVisible(false); activityPanel.add(new JSeparator()); mainWindow.getContentPane().add(activityPanel, BorderLayout.PAGE_END); // center the window on the primary monitor mainWindow.getContentPane().add(mainContainer, BorderLayout.CENTER); mainWindow.setLocationRelativeTo(null); mainWindow.setVisible(true); if (Config.getSavedSession() == null) { LoginWindow.open(mainWindow); initWindow(); } else { if (!App.isInitDone()) { showPage(PleaseWait.class); } QuickTimer.scheduleOnce(new Task() { @Override public void fire() { App.waitForInit(); // now try to init the session with the saved configuration (by giving it null) if (ThriftActions.initSession(null, false, SwingUtilities.getWindowAncestor(mainWindow))) { initWindow(); } else { // session resume failed, so do the normal login procedure Gui.asyncExec(new Runnable() { @Override public void run() { LoginWindow.open(mainWindow); initWindow(); } }); } } }); } } private static boolean initOnce = false; private static void initWindow() { if (initOnce || !App.isInitDone()) return; initOnce = true; // Sanity check: This should only happen once the user is logged in, if we don't have // a session, just bail out completely if (Session.getSatelliteToken() == null) System.exit(42); // at this point we are sure to be logged in with a proper token // thus we directly check for new version if (!ClientVersion.isNewest()) { addPanel(new UpdatePanel(ClientVersion.getRemoteRevision())); } // Show main menu by default showPage(MainMenuWindow.class); createMenu(); mainWindow.setTitle(Branding.getApplicationName() + " - " + Session.getFirstName() + " " + Session.getLastName() + " [" + Session.getSatelliteAddress() + "]"); if (DisclaimerWindow.shouldBeShown()) { DisclaimerWindow.open(mainWindow); } if (PrivacyNoticeWindow.shouldBeShown()) { PrivacyNoticeWindow.open(mainWindow); } } /** * Request application quit. Will show a message box asking the user for * confirmation. */ protected static void askApplicationQuit() { boolean open = false; for (ActivityPanel activity : activities) { if (activity.wantConfirmQuit()) { open = true; break; } } if (!open) { Window[] windows = Window.getWindows(); for (Window window : windows) { if (window.isVisible() && window instanceof UiFeedback && ((UiFeedback) window).wantConfirmQuit()) { open = true; break; } } } if (!open || Gui.showMessageBox(mainWindow, "Möchten Sie das Programm wirklich beenden?", MessageType.QUESTION_YESNO, null, null)) { Gui.exit(0); } isQuitQuestionOpen = false; } /** * Register a page that can be displayed in the main window. * * @param window */ private static synchronized void registerPage(CompositePage window) { Class clazz = window.getClass(); if (pages.containsKey(clazz)) throw new IllegalArgumentException("Page " + clazz.getSimpleName() + " already registered!"); pages.put(clazz, window); mainContainer.add(window); window.setVisible(false); } private static void addPanel(ActivityPanel panel) { activities.add(panel); activityPanel.add(panel); activityPanel.setVisible(true); mainWindow.validate(); } public static void addUpload(UploadWizardState state) { addPanel(new UploadPanel(state)); final UploadTask task = state.upload.getUploadTask(); task.addListener(new TransferEventListener() { @Override public void update(TransferEvent event) { if (event.state == TransferState.FINISHED) { ImageListWindow page = getPage(ImageListWindow.class); if (page != null) { page.refresh(true); } task.removeListener(this); } } }); } public static void addPassiveTransfer(String transferToken, String name, boolean queryMaster) { final PassiveTransfer transfer = new PassiveTransfer(transferToken, queryMaster); addPanel(new PassiveUploadPanel(transfer, name)); transfer.addListener(new TransferEventListener() { @Override public void update(TransferEvent event) { if (event.state == TransferState.FINISHED) { ImageListWindow page = getPage(ImageListWindow.class); if (page != null) { page.refresh(true); } transfer.removeListener(this); } } }); } public static void addDownload(String imageName, String diskFile, DownloadTask dlTask) { addPanel(new DownloadPanel(imageName, diskFile, dlTask)); } public static void removeActivity(ActivityPanel panel) { activities.remove(panel); activityPanel.remove(panel); if (activities.isEmpty()) activityPanel.setVisible(false); mainWindow.validate(); } private static void createMenu() { // the File menu button JMenuBar menuBar = new JMenuBar(); mainWindow.setJMenuBar(menuBar); JMenu cascadeSessionMenu = new JMenu("Sitzung"); menuBar.add(cascadeSessionMenu); JMenuItem configItem = new JMenuItem("Einstellungen"); cascadeSessionMenu.add(configItem); JMenuItem logDirItem = new JMenuItem("Logverzeichnis öffnen"); cascadeSessionMenu.add(logDirItem); cascadeSessionMenu.addSeparator(); JMenuItem logoutItem = new JMenuItem("Abmelden und beenden"); cascadeSessionMenu.add(logoutItem); JMenuItem exitItem = new JMenuItem("Beenden"); cascadeSessionMenu.add(exitItem); JMenu cascadeViewMenu = new JMenu("Ansicht"); menuBar.add(cascadeViewMenu); JMenuItem homeItem = new JMenuItem("Startseite"); cascadeViewMenu.add(homeItem); JMenuItem imagesItem = new JMenuItem("Virtuelle Maschinen"); cascadeViewMenu.add(imagesItem); // Prevent switching to imageList when user is student if (!Session.canListImages()) { imagesItem.setEnabled(false); } JMenuItem lecturesItem = new JMenuItem("Veranstaltungen"); cascadeViewMenu.add(lecturesItem); // the About menu button JMenu cascadeAboutMenu = new JMenu("Über"); menuBar.add(cascadeAboutMenu); JMenuItem disclaimerItem = new JMenuItem("Nutzungsvereinbarung"); JMenuItem privacyNoticeItem = new JMenuItem("Datenschutzerklärung"); JMenuItem virtualizerNoticeItem = new JMenuItem("Virtualisierer"); JMenuItem wikiItem = new JMenuItem(Branding.getServiceFAQWebsite()); JMenuItem updateCheckItem = new JMenuItem("Software-Aktualisierung"); cascadeAboutMenu.add(disclaimerItem); cascadeAboutMenu.add(privacyNoticeItem); cascadeAboutMenu.add(virtualizerNoticeItem); cascadeAboutMenu.addSeparator(); cascadeAboutMenu.add(wikiItem); cascadeAboutMenu.add(updateCheckItem); menuBar.add(Box.createHorizontalGlue()); final QLabel memStats = new QLabel(); menuBar.add(memStats); // "View" actions homeItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { MainWindow.showPage(MainMenuWindow.class); } }); imagesItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { MainWindow.showPage(ImageListWindow.class); } }); lecturesItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { MainWindow.showPage(LectureListWindow.class); } }); // "Session" actions configItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ConfigWindow.open(mainWindow); } }); logDirItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DesktopEnvironment.openLocal(new File(Config.getPath())); } }); logoutItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Config.saveCurrentSession("", "", ""); askApplicationQuit(); } }); exitItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { askApplicationQuit(); } }); // "About" actions disclaimerItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DisclaimerWindow.open(mainWindow); } }); privacyNoticeItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { PrivacyNoticeWindow.open(mainWindow); } }); virtualizerNoticeItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { VirtualizerNoticeWindow.open(mainWindow); } }); wikiItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { DesktopEnvironment.openWebpage(Link.FAQ); } }); updateCheckItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { CheckUpdateWindow.open(mainWindow); } }); // Debug label QuickTimer.scheduleAtFixedDelay(new Task() { private int failures = 0; private int ignoreCount = 0; private String remoteString = ""; private boolean timeDiffChecked = false; @Override public void fire() { Runtime rt = Runtime.getRuntime(); long maxMemory = rt.maxMemory(); long totalMemory = rt.totalMemory(); long usedMemory = totalMemory - rt.freeMemory(); String txt = "[JVM: " + FormatHelper.bytes(usedMemory, false) + "/" + FormatHelper.bytes(totalMemory, false); if (maxMemory != Long.MAX_VALUE) { txt += ", Limit: " + FormatHelper.bytes(maxMemory, false); } txt += "]"; if (ignoreCount > 0) { ignoreCount -= 1; } else if (Session.getUserId() != null) { try { SatelliteStatus status = ThriftManager.getSatClient().getStatus(); failures = 0; remoteString = " [Store: " + FormatHelper.bytes(status.availableStorageBytes, false) + "]"; if (!timeDiffChecked) { final long now = System.currentTimeMillis() / 1000; final long diffSecs = Math.abs(now - status.serverTime); LOGGER.debug("Clock diff client<->server: " + diffSecs + "s"); timeDiffChecked = true; if (diffSecs > TimeUnit.MINUTES.toMillis(10)) { Gui.asyncMessageBox( "ACHTUNG: Die Uhrzeit Ihres Computers weicht von der Uhrzeit auf dem Satellitenserver ab.\n" + "Bitte stellen Sie sicher, dass die Uhr Ihres Computers richtig gestellt ist.\n" + "Falls Ihre Sytemzeit korrekt gesetzt ist, ist möglicherweise die Uhrzeit auf\n" + "dem Satellitenserver nicht korrekt eingestellt.\n" + "In diesem Fall kann es - je nach Abweichung - zu unerwarteten Problemen mit den\n" + "Start- und Endzeiten von Veranstaltungen kommen. Kontaktieren Sie in diesem\n" + "Fall den zuständigen Administrator, damit die Uhrzeit auf dem Satellitenserver\n" + "korrigiert werden kann.\n\n" + "Ihr Computer: " + FormatHelper.longDate(now) + "\nSatellitenserver: " + FormatHelper.longDate(status.serverTime), MessageType.WARNING, LOGGER, null); } } // If user is not active and no upload is running, only update the value once a minute // TODO: Count active uploads long inactiveMins = (System.currentTimeMillis() - Gui.getLastUserActivityMillis()) / 60000l; if (inactiveMins > 10 && UploadTask.getNumberOfUploads() == 0) { ignoreCount = 30; } } catch (TException e) { failures += 1; ignoreCount = Math.min(10, failures / 3); remoteString = " [Store: ???]"; } } final String labelText = txt + remoteString; Gui.asyncExec(new Runnable() { @Override public void run() { memStats.setText(labelText); } }); } }, 10, 2001); } @SuppressWarnings("serial") private static class PleaseWait extends CompositePage { public PleaseWait() { GridManager grid = new GridManager(this, 3); grid.add(Box.createHorizontalGlue()).expand(true, true); grid.add(new JLabel("Bitte warten, suche Proxy-Konfiguration...")) .expand(false, true) .fill(true, true); grid.add(Box.createHorizontalGlue()).expand(true, true); grid.finish(false); } @Override public boolean requestHide() { return true; } @Override public void requestShow() { } } }