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


                           


                                                   
                           


                                          
                                          

                                       
                                                 

                  
 
                                                                         






                                                                     






                                                                                






































































































                                                                                                        
 
           


                                                                                  














                                                                            
                                      
         






                                                                                


                                                           
 

                                                                           
           




                                                
 























                                                                                                       
































































                                                                                  
 
package org.openslx.dozmod.gui;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.log4j.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
import org.openslx.dozmod.gui.helper.MessageType;

public class Gui {

	private static final Logger LOGGER = Logger.getLogger(Gui.class);

	/**
	 * The one and only display to use throughout the application
	 */
	public static final Display display = new Display();

	/**
	 * All active shells - we don't use display.getShells() as it is slow...
	 */
	private static final List<Shell> shells = new ArrayList<>();

	private static volatile int exitCode = 0;

	/**
	 * Center the given shell on the {@link Monitor} it is displayed on.
	 * 
	 * @param shell Some shell
	 */
	public static void centerShell(Shell shell) {
		Monitor activeMonitor = getMonitorFromRectangle(shell.getBounds(), true);
		Rectangle bounds = activeMonitor.getClientArea();
		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);
	}

	/**
	 * Make sure the given shell fits the {@link Monitor} it is displayed on.
	 * 
	 * @param shell Some shell
	 */
	public static void limitShellSize(Shell shell) {
		Monitor activeMonitor = getMonitorFromRectangle(shell.getBounds(), true);
		Rectangle bounds = activeMonitor.getClientArea();
		Rectangle rect = shell.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) {
			shell.setSize(rect.width, rect.height);
			rect = shell.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) {
			shell.setLocation(rect.x, rect.y);
		}
	}

	/**
	 * Get the {@link Monitor} 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 Monitor}
	 */
	public static Monitor getMonitorFromPoint(Point point, boolean defaultToPrimary) {
		Monitor[] monitors = display.getMonitors();
		for (Monitor monitor : monitors) {
			Rectangle bounds = monitor.getBounds();
			if (bounds.contains(point))
				return monitor;
		}
		if (defaultToPrimary)
			return display.getPrimaryMonitor();
		return null;
	}

	/**
	 * Get the {@link Monitor} 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 Monitor}
	 */
	public static Monitor 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.
		Rectangle bounds = display.getBounds();
		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;
		}
		// Now just use the same code as *FromPoint by using the rectangle's center
		return getMonitorFromPoint(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<>();
		display.syncExec(new Runnable() {
			@Override
			public void run() {
				try {
					instance.set(task.run());
				} catch (Throwable e) {
					LOGGER.warn("syncExec failed!", e);
				}
			}
		});
		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) {
		display.asyncExec(task);
	}

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

	/**
	 * Generic helper to show a message box to the user, and optionally log the
	 * message to the log file.
	 * 
	 * @param parent parent shell this message box belongs to
	 * @param message Message to display. Can be multiline.
	 * @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, YES or RETRY was clicked, false for CANCEL or NO
	 */
	public static boolean showMessageBox(Shell parent, String message, MessageType messageType,
			Logger logger, Throwable exception) {
		if (logger != null)
			logger.log(messageType.logPriority, message, exception);
		if (exception != null)
			message += "\n\n" + exception.getClass().getSimpleName() + " (Siehe Logdatei)";
		MessageBox box = new MessageBox(parent, messageType.style);
		box.setMessage(message);
		box.setText(messageType.title);
		int ret = box.open();
		return ret == SWT.OK || ret == SWT.RETRY || ret == SWT.YES;
	}

	/**
	 * Run the GUI mainloop as long as a window exists. This method does not
	 * return.
	 */
	public static void mainloop() {
		do {
			Iterator<Shell> it = shells.iterator();
			while (it.hasNext()) {
				if (it.next().isDisposed())
					it.remove();
			}
			if (!display.readAndDispatch())
				display.sleep();
		} while (!shells.isEmpty());
		display.dispose();
		System.exit(exitCode);
	}

	/**
	 * Exit application - dispose all shells, so mainloop will terminate.
	 * 
	 * @param code
	 */
	public static void exit(int code) {
		exitCode = code;
		asyncExec(new Runnable() {
			@Override
			public void run() {
				for (Shell shell : shells) {
					shell.dispose();
				}
			}
		});
	}

	/**
	 * Wrapper to get a new shell. This adds the shell to the list of shells,
	 * which we need to determine whether the app should still be running.
	 * 
	 * @param style the style of the {@link Shell}
	 * @return a new {@link Shell} that has no other {@link Shell} as its
	 *         parent.
	 */
	public static Shell newShell(int style) {
		Shell shell = new Shell(display, style);
		shells.add(shell);
		return shell;
	}

	/**
	 * Wrapper to get a new shell that is a child of another shell. This adds
	 * the shell to the list of shells, which we need to determine whether the
	 * app should still be running.
	 * 
	 * @param parent the parent {@link Shell}
	 * @param style the style of the {@link Shell}
	 * @return a new {@link Shell} that has no other {@link Shell} as its
	 *         parent.
	 */
	public static Shell newShell(Shell parent, int style) {
		Shell shell = new Shell(parent, style);
		shells.add(shell);
		return shell;
	}

}