package org.fenrir.yggdrasil.ui;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.JPanel;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
import javax.inject.Inject;
import org.jdesktop.jxlayer.JXLayer;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.fenrir.yggdrasil.core.AbstractApplication;
import org.fenrir.yggdrasil.core.ApplicationContext;
import org.fenrir.yggdrasil.core.definition.ProductDefinition;
import org.fenrir.yggdrasil.core.event.IWorkspaceEventListener;
import org.fenrir.yggdrasil.core.event.annotation.EventListener;
import org.fenrir.yggdrasil.core.service.IPreferenceService;
import org.fenrir.yggdrasil.core.service.IWorkspaceAdministrationService;
import org.fenrir.yggdrasil.core.util.ICallback;
import org.fenrir.yggdrasil.ui.dialog.ErrorDialog;
import org.fenrir.yggdrasil.ui.dialog.ErrorFrameDialog;
import org.fenrir.yggdrasil.ui.event.ApplicationWindowListener;
import org.fenrir.yggdrasil.ui.mvc.AbstractPerspective;
import org.fenrir.yggdrasil.ui.mvc.AbstractView;
import org.fenrir.yggdrasil.ui.mvc.EmptyPerspective;
import org.fenrir.yggdrasil.ui.widget.InProcessLayerUI;
/**
* TODO v1.0 Documentació
* TODO v1.0 Internacionalitzar
* @author Antonio Archilla Nava
* @version v0.1.20140723
*/
@EventListener(definitions=IWorkspaceEventListener.class)
public class ApplicationWindowManager implements IWorkspaceEventListener
{
private static Logger log = LoggerFactory.getLogger(ApplicationWindowManager.class);
private static ApplicationWindowManager instance;
private JFrame mainWindow;
private TrayIcon trayIcon;
// S'inicialitzarà en crear les vistes de l'aplicació
private ApplicationTray applicationTray;
private JXLayer layer;
private InProcessLayerUI layerUI;
// Necessari per poder canviar entre les prespectives de l'àrea principal
private JPanel mainArea;
private CardLayout mainAreaLayout;
private HashMap<String, AbstractView<?>> viewRegistry = new HashMap<String, AbstractView<?>>();
private HashMap<String, AbstractPerspective> perspectiveRegistry = new HashMap<String, AbstractPerspective>();
private String currentPerspectiveId;
@Inject
private IPreferenceService preferenceService;
@Inject
private IWorkspaceAdministrationService workspaceAdministrationService;
private ApplicationWindowListener windowListener;
private ApplicationWindowManager()
{
windowListener = new ApplicationWindowListener();
ApplicationContext applicationContext = ApplicationContext.getInstance();
applicationContext.injectMembers(windowListener);
}
public static ApplicationWindowManager getInstance()
{
if(instance==null){
instance = new ApplicationWindowManager();
ApplicationContext applicationContext = ApplicationContext.getInstance();
applicationContext.injectMembers(instance);
}
return instance;
}
public void setPreferenceService(IPreferenceService preferenceService)
{
this.preferenceService = preferenceService;
}
public void setWorkspaceAdministrationService(IWorkspaceAdministrationService workspaceAdministrationService)
{
this.workspaceAdministrationService = workspaceAdministrationService;
}
public void initialize(AbstractApplication application)
{
windowListener.setApplicationInstance(application);
}
public void notifyPreWindowOpenEvent()
{
windowListener.preWindowOpen();
}
public void createWindow() throws InvocationTargetException, InterruptedException
{
// TODO Si la finestra principal ja ha estat creada llançar excepció
mainWindow = new JFrame();
JFrame.setDefaultLookAndFeelDecorated(true);
SwingUtilities.invokeAndWait(new Runnable()
{
@Override
public void run()
{
// #29: Error en el càlcul de la posició del cursor del mouse quan la finestra està maximitzada
if(Arrays.asList("gnome-shell", "mate", "other...").contains(System.getenv("DESKTOP_SESSION"))){
try{
Class<?> xwm = Class.forName("sun.awt.X11.XWM");
Field awt_wmgr = xwm.getDeclaredField("awt_wmgr");
awt_wmgr.setAccessible(true);
Field other_wm = xwm.getDeclaredField("OTHER_WM");
other_wm.setAccessible(true);
if(awt_wmgr.get(null).equals(other_wm.get(null))){
Field metacity_wm = xwm.getDeclaredField("METACITY_WM");
metacity_wm.setAccessible(true);
awt_wmgr.set(null, metacity_wm.get(null));
}
}
catch(Exception e){
/* Encara que sigui no es pugui calcular correctament
* la posició del mouse es continua l'execució
*/
log.error("Error: " + e.getMessage(), e);
}
}
// S'especifica el look and feel. Si no es troba per defecte serà Metal
String laf = preferenceService.getProperty(UIConstants.PREFERENCES_UI_LOOK_AND_FEEL_SKIN);
if(StringUtils.isNotBlank(laf)){
try{
UIManager.setLookAndFeel(laf);
// Es important fer la crida a l'update per actualitzar els painters i no doni error per intentar fer servir els per defecte
SwingUtilities.updateComponentTreeUI(mainWindow);
}
catch(Exception e){
log.error("Error al carregar look and feel {}: {}", new Object[]{laf, e.getMessage(), e});
}
}
// S'especifica el títol.
ProductDefinition productDefinition = ApplicationContext.getInstance().getPluginRegistry().getProductDefinition();
String fullVersionName = productDefinition.getFullVersionName();
mainWindow.setTitle(fullVersionName);
// Creació del menú de l'aplicació
JMenuBar menuBar = (JMenuBar)ApplicationContext.getInstance().getRegisteredComponent(JMenuBar.class);
mainWindow.setJMenuBar(menuBar);
// Creació de la UI principal
createInitialUI();
switchToPerspective(EmptyPerspective.ID);
mainWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Mida de la finestra per defecte per quan està desmaximitzada
mainWindow.setPreferredSize(new Dimension(800, 600));
mainWindow.setMinimumSize(new Dimension(640, 480));
mainWindow.pack();
// Icona a la safata del sistema
if(SystemTray.isSupported()){
SystemTray tray = SystemTray.getSystemTray();
String icon = productDefinition.getIcon();
Image image = Toolkit.getDefaultToolkit().getImage(getClass().getResource(icon));
trayIcon = new TrayIcon(image, fullVersionName);
// Es redimensiona la imatge per tal que s'adapti a l'espai disponible
trayIcon.setImageAutoSize(true);
try{
tray.add(trayIcon);
}
catch(AWTException e){
log.error("Error en afegir la icona de l'aplicació a la safata del sistema");
}
}
else{
log.warn("Safata del sistema no disponible");
}
}
});
mainWindow.addWindowListener(windowListener);
}
@Override
public void beforeWorkspaceLoad()
{
}
@Override
public void workspaceLoaded()
{
}
@Override
public void beforeWorkspaceUnload()
{
}
@Override
public void workspaceUnloaded()
{
mainWindow.getContentPane().removeAll();
}
public JFrame getMainWindow()
{
return mainWindow;
}
public void showWindow()
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
// Es centra al mig de la pantalla
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
mainWindow.setLocation((d.width - mainWindow.getWidth()) / 2, (d.height - mainWindow.getHeight()) / 2);
mainWindow.setVisible(true);
// Es maximitza la finestra
mainWindow.setExtendedState(mainWindow.getExtendedState() | JFrame.MAXIMIZED_BOTH);
}
});
}
public void closeWindow()
{
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
mainWindow.setVisible(false);
mainWindow.dispose();
}
});
}
public boolean isMainWindowActive()
{
return mainWindow.isActive();
}
public void displaySystemTrayMessage(String caption, String message, TrayIcon.MessageType messageType)
{
trayIcon.displayMessage(caption, message, messageType);
}
public void displayErrorMessage(final String message, final Throwable error)
{
if(mainWindow!=null){
if(SwingUtilities.isEventDispatchThread()){
new ErrorDialog(mainWindow, message, error).open();
}
else{
try{
SwingUtilities.invokeAndWait(new Runnable()
{
@Override
public void run()
{
new ErrorDialog(mainWindow, message, error).open();
}
});
}
catch(Exception e){
log.error("Error mostrant finestra d'error: {}", e.getMessage(), e);
}
}
}
else{
try{
displayStandaloneErrorMessage(message, error);
}
catch(Exception e){
log.error("Error obrint finestra d'error: {}", e.getMessage(), e);
}
}
}
/**
* ATENCIÓ!! Aquest mètode només s'haurà d'utilitzar en cas de no poder inicial el contexte bàsic de l'aplicació,
* i per tant, falli la creació del manager.
* @param message
* @param error
*/
public static void displayStandaloneErrorMessage(final String message, final Throwable error)
{
try{
ErrorFrameDialog dialog = new ErrorFrameDialog(message, error);
dialog.setCloseCallback(new ICallback<Void, Void>()
{
@Override
public Void execute(Void cArg)
{
System.exit(-1);
return null;
}
});
dialog.open();
}
catch(Exception e){
log.error("Error mostrant finestra d'error: {}", e.getMessage(), e);
}
}
public void setInProcessMessage(String inProcessMessage)
{
layerUI.setMessage(inProcessMessage);
}
public void showInProcess(final boolean inProcess)
{
if(SwingUtilities.isEventDispatchThread()){
layerUI.setLocked(inProcess);
layer.repaint();
}
else{
try{
SwingUtilities.invokeAndWait(new Runnable()
{
@Override
public void run()
{
layerUI.setLocked(inProcess);
layer.repaint();
}
});
}
catch(Exception e){
log.error("Error mostrant la capa wait: {}", e.getMessage(), e);
}
}
}
public void createInitialUI()
{
JPanel panel = new JPanel();
// 5 px de marge per tal que no quedi enganxada l'area de notificació
panel.setLayout(new BorderLayout(0, 5));
// Es crea el panell a on aniran les perspectives
mainArea = new JPanel();
mainAreaLayout = new CardLayout();
mainArea.setLayout(mainAreaLayout);
panel.add(mainArea, BorderLayout.CENTER);
// Es crea la zona d'avisos de l'aplicació
applicationTray = (ApplicationTray)ApplicationContext.getInstance().getRegisteredComponent(ApplicationTray.class);
panel.add(applicationTray, BorderLayout.SOUTH);
// Es crea la capa per sobre la la UI de l'aplicació que permetrà bloquejar la interacció de l'usuari quan calgui
layer = new JXLayer(panel);
layerUI = new InProcessLayerUI();
layer.setUI(layerUI);
// S'afageixen les vistes creades a la finestra
mainWindow.getContentPane().setLayout(new BorderLayout());
mainWindow.getContentPane().add(layer, BorderLayout.CENTER);
/* Necessari per refrescar el panell i que es mostrin els components afegits
* en cas que es crein les vistes després que s'hagi creat la finestra principal,
* per exemple, durant la primera execució quan no hi ha workspace per defecte.
*/
mainWindow.getContentPane().validate();
}
public void switchToDefaultPerspective()
{
String defaultId = preferenceService.getProperty(UIConstants.PREFERENCES_UI_DEFAULT_PERSPECTIVE);
switchToPerspective(defaultId);
}
public void switchToPerspective(String id)
{
// Si la perspectiva actual és a la que es vol canviar, no es fa res
if(id.equals(currentPerspectiveId)){
return;
}
if(!perspectiveRegistry.containsKey(id)){
AbstractPerspective perspective = (AbstractPerspective)ApplicationContext.getInstance().getRegisteredComponent(AbstractPerspective.class, id);
mainArea.add(perspective, id);
// Es necessari repintar el panell tal i com indica el mètode add
mainArea.validate();
perspectiveRegistry.put(id, perspective);
}
mainAreaLayout.show(mainArea, id);
currentPerspectiveId = id;
}
public AbstractPerspective getCurrentPerspective()
{
return getPerspective(currentPerspectiveId);
}
public AbstractPerspective getPerspective(String id)
{
if(id!=null){
return perspectiveRegistry.get(id);
}
return null;
}
public AbstractView<?> getViewFromRegistry(String id)
{
return viewRegistry.get(id);
}
public <T extends AbstractView<?>> void addViewToRegistry(String id, Class<T> viewClass)
{
T view = (T)ApplicationContext.getInstance().getRegisteredComponent(viewClass);
addViewToRegistry(id, view);
}
public <T extends AbstractView<?>> void addViewToRegistry(String id, AbstractView<?> view)
{
viewRegistry.put(id, view);
}
public void removeViewFromRegistry(String id)
{
AbstractView<?> view = viewRegistry.remove(id);
for(AbstractPerspective perspective:perspectiveRegistry.values()){
perspective.viewRemoved(view);
}
}
}