summaryrefslogblamecommitdiffstats
path: root/dozentenmodul/src/main/java/org/openslx/dozmod/gui/Gui.java
blob: b61f961deded05fd4971845b6a3c8fc7a7ed328f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                               
 
                         
                          
                          
                     



                                    
                                     



                          
                                       
                                                   
                                   

                                                   
                                        
                        
                               


                                  



                                           
 
                               
                                 
                                                 
                                                  
                                              
                                   

                  
 
                                                                         










                                                                                        
 








                                                                                                         

           
                                                                            
                                                                 


                                  
                                                      

                                                                                                






                                                                     




                                                                     
                                                                                      











                                                                     

                                                                                    


                                  



                                                                                                 









                                                         

                                                                










                                                                       
                                                           



                 
                                                                      
           







                                                                                














                                                                                                                     
 
           
                                                                                



                                                                              
                                             
           

                                                                                                
                                                                              

                                                                                     
                                                                              
                                                   
                                           
                 
                                       
                                                                 
                                                                                                          
                 



                            

                                                                           



                                                                              
                                             
           
                                                                                                        

                                                                                       


                                                                                                                
                                                                                         
                 
                                                                                                         

















                                                                                           
                                                                     
                                                                                           
                                                                                                                 

                                                  
 
           


                                                                                  




                                                                            












                                                                                  
                                 







                                                                                                     
                                      
         






                                                                                
                                                           
                                                 
         
 

                                                                           
           




                                                
 
           













                                                                             


                                                                                   
                                                                                
                                                    
                                                                


                                                                           
                                                                         
           

                                                                                                       

                                                                                
                                                                          





                                                                                                         
                         
                 
 
                                        
                                                                                                                       

                                                                               














                                                                                                          
                                                                                                              








                                                                                                               
                                                
                                                                                                                    

                                    
                                                             
                                                                                                                
                                                          
                                                                                     

         
           














                                                                                                    











                                                                                               



                                                                                    






                                                                                              







                                                        
 
 
package org.openslx.dozmod.gui;

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.util.concurrent.atomic.AtomicReference;

import javax.management.monitor.Monitor;
import javax.swing.Icon;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLDocument;

import org.apache.log4j.Logger;
import org.openslx.dozmod.Config;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.util.DesktopEnvironment;
import org.openslx.dozmod.util.ResourceLoader;
import org.openslx.util.QuickTimer;

public class Gui {

	private static final Logger LOGGER = Logger.getLogger(Gui.class);
	
	private static long lastUserActivity = System.currentTimeMillis();
	
	static {
		Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
			@Override
			public void eventDispatched(AWTEvent event) {
				lastUserActivity = System.currentTimeMillis();
			}
		}, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
	}

	private static Rectangle clientArea(GraphicsDevice gd) {
		Insets inset = Toolkit.getDefaultToolkit().getScreenInsets(gd.getDefaultConfiguration());
		Rectangle bounds = gd.getDefaultConfiguration().getBounds();
		bounds.x += inset.left;
		bounds.y += inset.top;
		bounds.width -= (inset.top + inset.bottom);
		bounds.height -= (inset.left + inset.right);
		return bounds;
	}

	/**
	 * Center the given shell on the {@link Monitor} it is displayed on.
	 * WARNING: Seems broken on Linux (depending on DE or WM)
	 * 
	 * @param shell Some shell
	 */
	public static void centerShell(Window shell) {
		GraphicsDevice activeMonitor = getMonitorFromRectangle(shell.getBounds(), true);
		Rectangle bounds = clientArea(activeMonitor);
		Rectangle rect = shell.getBounds();
		int x = bounds.x + (bounds.width - rect.width) / 2;
		int y = bounds.y + (bounds.height - rect.height) / 2;
		shell.setLocation(x, y);
	}

	/**
	 * Take a shell and center it relative to another shell
	 * 
	 * @param parent Parent shell, used as reference
	 * @param shellToCenter The shell that should be repositioned
	 */
	public static void centerShellOverShell(Window parent, Window shellToCenter) {
		Rectangle bounds = parent.getBounds();
		Rectangle rect = shellToCenter.getBounds();
		int x = bounds.x + (bounds.width - rect.width) / 2;
		int y = bounds.y + (bounds.height - rect.height) / 2;
		if (x < bounds.x)
			x = bounds.x;
		if (y < bounds.y)
			y = bounds.y;
		shellToCenter.setLocation(x, y);
	}

	/**
	 * Make sure the given shell fits the {@link GraphicsDevice} it is displayed
	 * on.
	 * 
	 * @param shell Some shell
	 */
	public static void limitShellSize(JFrame window) {
		GraphicsDevice activeMonitor = getMonitorFromRectangle(window.getBounds(), true);
		Rectangle bounds = clientArea(activeMonitor);
		Rectangle rect = window.getBounds();
		boolean changed = false;
		if (rect.width + 20 > bounds.width) {
			rect.width = bounds.width - 20;
			changed = true;
		}
		if (rect.height + 20 > bounds.height) {
			rect.height = bounds.height - 20;
			changed = true;
		}
		if (changed) {
			window.setSize(rect.width, rect.height);
			rect = window.getBounds();
		}
		changed = false;
		if (rect.x + rect.width >= bounds.x + bounds.width) {
			rect.x = 5;
			changed = true;
		}
		if (rect.y + rect.height >= bounds.y + bounds.height) {
			rect.y = 5;
			changed = true;
		}
		if (changed) {
			window.setLocation(rect.x, rect.y);
		}
	}

	/**
	 * Gets the given dimension scaled to the saved scaling factor
	 * 
	 * @param width starting width to scale
	 * @param height starting height to scale
	 * @return scaled dimension
	 */
	public static Dimension getScaledDimension(int width, int height) {
		int scale = Config.getFontScaling();
		return new Dimension(width * scale / 100, height * scale / 100);
	}
	
	/**
	 * Load given icon resource, optionally scaling it while taking
	 * the user's zoom factor into account.
	 *
	 * @param path resource path
	 * @param description resource description
	 * @param maxHeight maximum height of image, which it will be scaled to
	 * @param context context component, for alpha blending
	 * @return scaled icon, if too large, otherwise the unmodified icon
	 */
	public static Icon getScaledIconResource(String path, String description, int maxHeight, Component context) {
		int height = maxHeight * Config.getFontScaling() / 100;
		return ResourceLoader.getIcon(path, description, height, context);
	}

	/**
	 * Get the {@link GraphicsDevice} which the given {@link Point} lies in.
	 * 
	 * @param point The point in question
	 * @param defaultToPrimary if no monitor matches the check, return the
	 *            primary monitor if true, <code>null</code> otherwise
	 * @return the {@link GraphicsDevice}
	 */
	private static GraphicsDevice getMonitorFromPoint(GraphicsDevice[] screens, Point point,
			boolean defaultToPrimary) {
		LOGGER.debug("Finding monitor for point " + point.toString());
		for (GraphicsDevice dev : screens) {
			Rectangle bounds = dev.getDefaultConfiguration().getBounds();
			LOGGER.debug("Checking monitor " + bounds.toString());
			if (bounds.contains(point))
				return dev;
		}
		if (defaultToPrimary) {
			LOGGER.debug("Defaulting to primary...");
			return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
		}
		return null;
	}

	/**
	 * Get the {@link GraphicsDevice} which most of the given rectangle
	 * overlaps.
	 * 
	 * @param rect The rectangle to check
	 * @param defaultToPrimary if no monitor matches the check, return the
	 *            primary monitor if true, <code>null</code> otherwise
	 * @return the {@link GraphicsDevice}
	 */
	public static GraphicsDevice getMonitorFromRectangle(Rectangle rect, boolean defaultToPrimary) {
		// Make sure rectangle is in bounds. This is not completely accurate
		// in case there are multiple monitors that have different resolutions.
		GraphicsDevice[] screens = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
		Rectangle bounds = new Rectangle();
		for (GraphicsDevice dev : screens) {
			bounds = bounds.union(dev.getDefaultConfiguration().getBounds());
		}
		LOGGER.debug("Display bounds are " + bounds.toString() + ", rect is " + rect.toString());
		if (rect.x + rect.width >= bounds.x + bounds.width) {
			rect.width -= (rect.x + rect.width) - (bounds.x + bounds.width);
			if (rect.width < 1)
				rect.width = 1;
		}
		if (rect.y + rect.height >= bounds.y + bounds.height) {
			rect.height -= (rect.y + rect.height) - (bounds.y + bounds.height);
			if (rect.height < 1)
				rect.height = 1;
		}
		if (rect.x < bounds.x) {
			rect.width -= bounds.x - rect.x;
			rect.x = bounds.x;
		}
		if (rect.y < bounds.y) {
			rect.height -= bounds.y - rect.y;
			rect.y = bounds.y;
		}
		LOGGER.debug("After correction: " + rect.toString());
		// Now just use the same code as *FromPoint by using the rectangle's center
		return getMonitorFromPoint(screens, new Point(rect.x + rect.width / 2, rect.y + rect.height / 2),
				defaultToPrimary);
	}

	/**
	 * Run given task in the GUI thread, blocking the calling thread until the
	 * task is done.
	 * 
	 * @param task Task to run
	 * @return return value of the task
	 */
	public static <T> T syncExec(final GuiCallable<T> task) {
		final AtomicReference<T> instance = new AtomicReference<>();
		final AtomicReference<Throwable> thrown = new AtomicReference<>();
		if (SwingUtilities.isEventDispatchThread()) {
			return task.run();
		}
		try {
			SwingUtilities.invokeAndWait(new Runnable() {
				@Override
				public void run() {
					try {
						instance.set(task.run());
					} catch (Throwable e) {
						thrown.set(e);
					}
				}
			});
		} catch (InvocationTargetException | InterruptedException e) {
			LOGGER.warn("syncExec() failed", e);
			return null;
		}
		if (thrown.get() != null) {
			throw new RuntimeException("task passed to syncExec() failed", thrown.get());
		}
		return instance.get();
	}

	/**
	 * Run given task as soon as possible in the GUI thread, but don't block
	 * calling thread.
	 * 
	 * @param task Task to run
	 */
	public static void asyncExec(final Runnable task) {
		SwingUtilities.invokeLater(task);
	}

	/**
	 * Pretty much the same as Callable, but no exceptions are allowed.
	 * 
	 * @param <T> return value
	 */
	public static interface GuiCallable<T> {
		T run();
	}

	/**
	 * Exit application - dispose all shells, so mainloop will terminate.
	 * 
	 * @param code
	 */
	public static void exit(int code) {
		QuickTimer.cancel();
		Window[] ownerlessWindows = Frame.getOwnerlessWindows();
		for (Window w : ownerlessWindows) {
			w.dispose();
		}
		System.exit(code);
	}

	/**
	 * Generic helper to show a message box to the user, and optionally log the
	 * message to the log file.
	 * 
	 * @param parent parent window (used for positioning/modality). If null,
	 *            the active window will be used
	 * @param message Message to display. Can be multi line.
	 * @param messageType Type of message (warning, information)
	 * @param logger Logger instance to log to. Can be null.
	 * @param exception Exception related to this message. Can be null.
	 * @return true if OK or YES was clicked, false for CANCEL/NO/(X)
	 */
	public static boolean showMessageBox(Component parent, String message, MessageType messageType,
			Logger logger, Throwable exception) {
		if (logger != null)
			logger.log(messageType.logPriority, message, exception);
		// Only needs to be done, if parent isn't already a window
		if (parent == null) {
			parent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
		} else if (!(parent instanceof Window)) {
			Window ancestor = SwingUtilities.getWindowAncestor(parent);
			if (ancestor != null) {
				parent = ancestor;
			}
		}

		if (exception != null) {
			message += "\n\n" + exception.getClass().getSimpleName() + "\n" + exception.getMessage() + "\n"
					+ " (Für Stack-Trace siehe Logdatei)";
		}
		if (message.startsWith("<html>")) {
			JEditorPane ep = new JEditorPane("text/html", message);
			ep.setEditable(false);
			ep.setOpaque(false);

			Font font = UIManager.getFont("Label.font");
			String bodyRule = "body { font-family: " + font.getFamily() + "; " +
					"font-size: " + font.getSize() + "pt; }";
			((HTMLDocument)ep.getDocument()).getStyleSheet().addRule(bodyRule);

			ep.addHyperlinkListener(new HyperlinkListener() {
				@Override
				public void hyperlinkUpdate(HyperlinkEvent e) {
					if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
						try {
							DesktopEnvironment.openWebpageUri(e.getURL().toURI());
						} catch (URISyntaxException ex) {
							LOGGER.error("Couldn't parse hyperlink", ex);
						}
					}
				}
			});
			JOptionPane.showMessageDialog(parent, ep, messageType.title, messageType.optionPaneId);
			return true;
		}
		if (messageType.buttons == -1) {
			JOptionPane.showMessageDialog(parent, message, messageType.title, messageType.optionPaneId);
			return true;
		}
		// TODO set the default button that has focus
		int ret = JOptionPane.showConfirmDialog(parent, message, messageType.title, messageType.buttons,
				messageType.optionPaneId);
		return ret == JOptionPane.OK_OPTION || ret == JOptionPane.YES_OPTION;
	}

	/**
	 * Generic helper to show a message box to the user, and optionally log the
	 * message to the log file.
	 * 
	 * @param message Message to display. Can be multi line.
	 * @param messageType Type of message (warning, information)
	 * @param logger Logger instance to log to. Can be null.
	 * @param exception Exception related to this message. Can be null.
	 * @return true if OK or YES was clicked, false for CANCEL/NO/(X)
	 */
	public static boolean showMessageBox(String message, MessageType messageType, Logger logger,
			Throwable exception) {
		return showMessageBox(null, message, messageType, logger, exception);
	}

	/**
	 * Show a message box to the user asynchronously, and optionally log the
	 * message to the log file. This is most useful when working from another
	 * thread.
	 * 
	 * @param message Message to display. Can be multi line.
	 * @param messageType Type of message (warning, information)
	 * @param logger Logger instance to log to. Can be null.
	 * @param exception Exception related to this message. Can be null.
	 * @return true if OK or YES was clicked, false for CANCEL/NO/(X)
	 */
	public static void asyncMessageBox(final String message, final MessageType messageType,
			final Logger logger, final Throwable exception) {
		if (SwingUtilities.isEventDispatchThread()) {
			Gui.showMessageBox(message, messageType, logger, exception);
			return;
		}
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				showMessageBox(null, message, messageType, logger, exception);
			}
		});
	}
	
	/**
	 * Get last user activity timestamp.
	 * This considers mouse clicks and key presses.
	 */
	public static long getLastUserActivityMillis() {
		return lastUserActivity;
	}

}