/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.ui.windows; import com.vlsolutions.swing.docking.ui.DockingUISettings; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.Rectangle; import java.awt.SystemTray; import java.awt.Toolkit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.HashSet; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import javax.swing.BoxLayout; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.WindowConstants; import net.miginfocom.swing.MigLayout; import org.jajuk.events.JajukEvent; import org.jajuk.events.JajukEvents; import org.jajuk.events.ObservationManager; import org.jajuk.events.Observer; import org.jajuk.services.players.QueueModel; import org.jajuk.services.webradio.WebRadio; import org.jajuk.ui.actions.ActionManager; import org.jajuk.ui.actions.JajukActions; import org.jajuk.ui.perspectives.PerspectiveManager; import org.jajuk.ui.widgets.CommandJPanel; import org.jajuk.ui.widgets.InformationJPanel; import org.jajuk.ui.widgets.JajukJMenuBar; import org.jajuk.ui.widgets.PerspectiveBarJPanel; import org.jajuk.ui.widgets.SearchJPanel; import org.jajuk.util.Conf; import org.jajuk.util.Const; import org.jajuk.util.IconLoader; import org.jajuk.util.JajukIcons; import org.jajuk.util.Messages; import org.jajuk.util.UtilFeatures; import org.jajuk.util.UtilGUI; import org.jajuk.util.UtilString; import org.jajuk.util.UtilSystem; import org.jajuk.util.error.JajukException; import org.jajuk.util.log.Log; import org.jdesktop.swingx.JXPanel; /** * Jajuk main window * <p> * Singleton. */ public class JajukMainWindow extends JFrame implements IJajukWindow, Observer { /** Generated serialVersionUID. */ private static final long serialVersionUID = 1L; /** Self instance. */ private static JajukMainWindow jw; /** Left side perspective selection panel. */ private PerspectiveBarJPanel perspectiveBar; /** Main frame panel. */ private JPanel jpFrame; /** specific perspective panel. */ private JPanel perspectivePanel; /** State decorator. */ private WindowStateDecorator decorator; /** Number of pixels around window at initial startup. */ private static final int FRAME_INITIAL_BORDER = 60; /** Window minimal width in pixels, set a bit less than 1024px * (lowest resolution of compatible screens) to avoid a side effect * due to negative coordinates which leads to display the frame on * the other screen if larger */ private static final int FRAME_MIN_WIDTH_PX = 1000; /** Window minimal height in pixels*/ private static final int FRAME_MIN_HEIGHT_PX = 600; /** * Get the window instance and create the specific WindowStateHandler. * * @return the instance */ public static synchronized JajukMainWindow getInstance() { if (jw == null) { jw = new JajukMainWindow(); // Install global keystrokes WindowGlobalKeystrokeManager.getInstance(); jw.decorator = new WindowStateDecorator(jw) { @Override public void specificBeforeShown() { //Nothing here, frame bounds is set after display (see next method) } @Override public void specificAfterShown() { // We have to force the new frame state, otherwise the window is deiconified but never gets focus jw.setExtendedState(Frame.NORMAL); //We have to call this next in the EDT to make sure that the window is displayed so maximalize() method get //proper screen for jw window. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (Conf.getBoolean(Const.CONF_WINDOW_MAXIMIZED)) { jw.maximalize(); } else { // We set bounds after display, otherwise, the window is blank under Gnome3 jw.applyStoredSize(); } } }); // Need focus for keystrokes jw.requestFocus(); // Make sure to display right title if a track or a webradio is launched at startup // Indeed, the window can appear after the track/webradio has been launched and miss this event UtilFeatures.updateStatus(jw); } @Override public void specificBeforeHidden() { // This is required to store last position of frame before hide jw.saveSize(); } @Override public void specificAfterHidden() { // Nothing particular } }; } return jw; } /** * Constructor. */ private JajukMainWindow() { } /* * (non-Javadoc) * * @see org.jajuk.ui.widgets.JajukWindow#getWindowStateDecorator() */ @Override public WindowStateDecorator getWindowStateDecorator() { return decorator; } /* * (non-Javadoc) * * @see org.jajuk.ui.windows.IJajukWindow#initUI() */ @Override public void initUI() { if (UtilSystem.isUnderOSX()) { // mac integration System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("apple.awt.showGrowBox", "false"); } setTitle(Messages.getString("JajukWindow.17")); setIconImage(IconLoader.getIcon(JajukIcons.LOGO).getImage()); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); // register for given events ObservationManager.register(this); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent we) { // Save windows position saveSize(); try { ActionManager.getAction(JajukActions.EXIT).perform(null); } catch (Exception e1) { Log.error(e1); } } @Override public void windowDeiconified(WindowEvent we) { getWindowStateDecorator().display(true); } }); // Light drag and drop for VLDocking UIManager.put("DragControler.paintBackgroundUnderDragRect", Boolean.FALSE); DockingUISettings.getInstance().installUI(); // Creates the panel jpFrame = (JPanel) getContentPane(); jpFrame.setOpaque(true); jpFrame.setLayout(new BorderLayout()); // create the command bar CommandJPanel command = CommandJPanel.getInstance(); command.initUI(); // Create the search bar SearchJPanel searchPanel = SearchJPanel.getInstance(); searchPanel.initUI(); // Add the search bar jpFrame.add(searchPanel, BorderLayout.NORTH); // Create and add the information bar panel InformationJPanel information = InformationJPanel.getInstance(); // Add the information panel jpFrame.add(information, BorderLayout.SOUTH); // Create the perspective manager try { PerspectiveManager.load(); } catch (JajukException e) { // problem loading the perspective, let Main to handle this Log.debug("Cannot create main window"); throw new RuntimeException(e); } perspectivePanel = new JXPanel(); // Make this panel extensible perspectivePanel.setLayout(new BoxLayout(perspectivePanel, BoxLayout.X_AXIS)); // Set menu bar to the frame JajukMainWindow.getInstance().setJMenuBar(JajukJMenuBar.getInstance()); // Create the perspective tool bar panel perspectiveBar = PerspectiveBarJPanel.getInstance(); jpFrame.add(perspectiveBar, BorderLayout.WEST); // Initialize and add the desktop PerspectiveManager.init(); // Add main container (contains toolbars + desktop) JPanel commandDesktop = new JPanel(new MigLayout("insets 0,gapy 0", "[grow]", "[grow][]")); commandDesktop.add(perspectivePanel, "grow,wrap"); commandDesktop.add(command, "grow"); jpFrame.add(commandDesktop, BorderLayout.CENTER); } /* * (non-Javadoc) * * @see org.jajuk.events.Observer#getRegistrationKeys() */ @Override public Set<JajukEvents> getRegistrationKeys() { Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>(); eventSubjectSet.add(JajukEvents.FILE_LAUNCHED); eventSubjectSet.add(JajukEvents.WEBRADIO_LAUNCHED); eventSubjectSet.add(JajukEvents.WEBRADIO_INFO_UPDATED); eventSubjectSet.add(JajukEvents.ZERO); eventSubjectSet.add(JajukEvents.PLAYER_STOP); return eventSubjectSet; } /** * Save current window size and position. */ public void saveSize() { boolean maximized = false; if (Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH) && (getExtendedState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH) { maximized = true; } Conf.setProperty(Const.CONF_WINDOW_MAXIMIZED, Boolean.toString(maximized)); String sValue = (int) getLocationOnScreen().getX() + "," + (int) getLocationOnScreen().getY() + "," + getBounds().width + "," + getBounds().height; Log.debug("Frame position position stored as :" + sValue + " maximalized=" + maximized); // Store the new position Conf.setProperty(Const.CONF_WINDOW_POSITION, sValue); } /** * Return the forced position as a rectangle or null if no forced position is provided or if the provided position is invalid * <br>See http://jajuk.info/index.php/Hidden_options * <br>The forced position is an hidden option used to force Jajuk window position manually. * @return the forced position as a rectangle or null */ private Rectangle getForcedPosition() { try { String forcedPosition = Conf.getString(Const.CONF_FRAME_POS_FORCED); int x = 0; int y = 0; int horizSize = 0; int vertSize = 0; if (UtilString.isNotEmpty(forcedPosition)) { StringTokenizer st = new StringTokenizer(forcedPosition, ","); x = Integer.parseInt(st.nextToken()); y = Integer.parseInt(st.nextToken()); horizSize = Integer.parseInt(st.nextToken()); vertSize = Integer.parseInt(st.nextToken()); return new Rectangle(x, y, horizSize, vertSize); } } catch (Exception e) { Log.error(e); } return null; } /** * Return the stored position as a rectangle or default coordinates if no stored position is provided or if the stored position is invalid. * @return the stored position as a rectangle or null */ Rectangle getStoredPosition() { try { String storedPosition = Conf.getString(Const.CONF_WINDOW_POSITION); int x = 0; int y = 0; int horizSize = 0; int vertSize = 0; if (UtilString.isNotEmpty(storedPosition)) { StringTokenizer st = new StringTokenizer(storedPosition, ","); // We need to floor the position to zero due to issues with dual screens that can produce negative x and y x = Integer.parseInt(st.nextToken()); x = Math.max(x, 0); y = Integer.parseInt(st.nextToken()); y = Math.max(y, 0); horizSize = Integer.parseInt(st.nextToken()); vertSize = Integer.parseInt(st.nextToken()); return new Rectangle(x, y, horizSize, vertSize); } } catch (Exception e) { Log.error(e); } return null; } /** * Return whether the window should be maximalized. * <br>Maximized state here refers to maximum size of JFrame on a desktop screen however not covering the taskbar. * <br>Prior to 1.9, "max" was inside CONF_WINDOW_POSITION, then it is * externalize in a specific boolean property : CONF_WINDOW_MAXIMIZED * * @return whether the window should be maximalized. */ private boolean isMaximalizationRequired() { // CONF_WINDOW_POSITION contains the last session stored position or "max" if maximalized (jajuk <1.9) String sPosition = Conf.getString(Const.CONF_WINDOW_POSITION); // workaround: needed for old configuration files to avoid an exception in // the StringTokenizer, since Jajuk 1.9 Jajuk stores in an extra property if it // is maximized if (Const.FRAME_MAXIMIZED.equals(sPosition)) { return true; } return Conf.getBoolean(Const.CONF_WINDOW_MAXIMIZED); } /** * Actually maximalize this frame. * Do not call this when hidden before the first screen will always been returned. */ private void maximalize() { GraphicsConfiguration gConf = UtilGUI.getGraphicsDeviceOfMainFrame().getDefaultConfiguration(); setMaximizedBounds(gConf.getBounds()); setExtendedState(getExtendedState() | Frame.MAXIMIZED_BOTH); setBounds(FRAME_INITIAL_BORDER, FRAME_INITIAL_BORDER, (int) (gConf.getBounds().getWidth() - 2 * FRAME_INITIAL_BORDER), (int) (gConf.getBounds() .getHeight() - 2 * FRAME_INITIAL_BORDER)); } /** * Check if provided position is correct * @return whether provided position is valid. */ private boolean isPositionValid(Rectangle position) { GraphicsConfiguration gConf = UtilGUI.getGraphicsDeviceOfMainFrame().getDefaultConfiguration(); if (position.getX() < gConf.getBounds().getX() || position.getX() > gConf.getBounds().getWidth()) { return false; } if (position.getY() < gConf.getBounds().getY() || position.getY() > gConf.getBounds().getHeight()) { return false; } if (position.getWidth() <= 0 || position.getWidth() > gConf.getBounds().getWidth() || position.getWidth() < 800) { return false; } if (position.getHeight() <= 0 || position.getHeight() > gConf.getBounds().getHeight() || position.getHeight() < 600) { return false; } return true; } /** * Apply size and position stored as property. * <br> * Note that defaults sizes (for very first startup) are set in {@code Conf.setDefaultProperties()} method ,see {@code CONF_WINDOW_POSITION} */ public void applyStoredSize() { try { setMinimumSize(new Dimension(FRAME_MIN_WIDTH_PX, FRAME_MIN_HEIGHT_PX)); Rectangle forcedPosition = getForcedPosition(); if (forcedPosition != null) { setBounds(forcedPosition); } else { if (isMaximalizationRequired() && Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH)) { maximalize(); } else { Rectangle storedPosition = getStoredPosition(); // Note that setBounds handle out of bounds issues like task bar overriding, // number of screens changes since previous jajuk session... setBounds(storedPosition); } } } catch (Exception e) { Log.error(e); maximalize(); } } /* * (non-Javadoc) * * @see org.jajuk.ui.Observer#update(java.lang.String) */ @Override public final void update(JajukEvent event) { final JajukEvents subject = event.getSubject(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (subject.equals(JajukEvents.FILE_LAUNCHED)) { String title = QueueModel.getPlayingFileTitle(); if (title != null) { setTitle(title); } } else if (subject.equals(JajukEvents.ZERO) || subject.equals(JajukEvents.PLAYER_STOP)) { setTitle(Messages.getString("JajukWindow.17")); } else if (subject.equals(JajukEvents.WEBRADIO_LAUNCHED)) { WebRadio radio = QueueModel.getCurrentRadio(); if (radio != null) { // We use vertical bar to allow scripting like MSN plugins to // detect jajuk frames and extract current track setTitle("\\ " + radio.getName() + " /"); } } else if (subject.equals(JajukEvents.WEBRADIO_INFO_UPDATED)) { Properties webradioInfoUpdatedEvent = ObservationManager .getDetailsLastOccurence(JajukEvents.WEBRADIO_INFO_UPDATED); String currentRadioTrack = (String) webradioInfoUpdatedEvent .get(Const.CURRENT_RADIO_TRACK); if (currentRadioTrack != null) { // We use vertical bar to allow scripting like MSN plugins to // detect jajuk frames and extract current track setTitle("\\ " + currentRadioTrack + " /"); } } } }); } /** * Gets the perspective panel. * * @return the perspective panel */ public JPanel getPerspectivePanel() { return perspectivePanel; } }