/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2000-2006 Keith Godfrey, Maxym Mykhalchuk, Henry Pijffers,
Benjamin Siband, and Kim Bruning
2007 Zoltan Bartko
2008 Andrzej Sawula, Alex Buloichik
2009-2010 Alex Buloichik
2014 Yu Tang
2015 Aaron Madlon-Kay
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT 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 3 of the License, or
(at your option) any later version.
OmegaT 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, see <http://www.gnu.org/licenses/>.
**************************************************************************/
package org.omegat.util.gui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Image;
import java.awt.Toolkit;
import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.UIManager;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.plaf.ColorUIResource;
import org.omegat.util.OStrings;
import org.omegat.util.Platform;
import com.vlsolutions.swing.docking.AutoHidePolicy;
import com.vlsolutions.swing.docking.AutoHidePolicy.ExpandMode;
import com.vlsolutions.swing.docking.DockableContainerFactory;
import com.vlsolutions.swing.docking.DockableState;
import com.vlsolutions.swing.docking.DockingDesktop;
import com.vlsolutions.swing.docking.ui.DockingUISettings;
/**
* Docking UI support.
* @author Keith Godfrey
* @author Maxym Mykhalchuk
* @author Henry Pijffers
* @author Benjamin Siband
* @author Kim Bruning
* @author Zoltan Bartko
* @author Andrzej Sawula
* @author Alex Buloichik
* @author Yu Tang
* @author Aaron Madlon-Kay
*/
public class DockingUI {
/**
* Initialize docking subsystem.
*/
public static void initialize() {
// Install VLDocking defaults
DockingUISettings.getInstance().installUI();
DockableContainerFactory.setFactory(new CustomContainerFactory());
// Enable animated popup when mousing over minimized tab
AutoHidePolicy.getPolicy().setExpandMode(ExpandMode.EXPAND_ON_ROLLOVER);
// UI strings
UIManager.put("DockViewTitleBar.minimizeButtonText", OStrings.getString("DOCKING_HINT_MINIMIZE"));
UIManager.put("DockViewTitleBar.maximizeButtonText", OStrings.getString("DOCKING_HINT_MAXIMIZE"));
UIManager.put("DockViewTitleBar.restoreButtonText", OStrings.getString("DOCKING_HINT_RESTORE"));
UIManager.put("DockViewTitleBar.attachButtonText", OStrings.getString("DOCKING_HINT_DOCK"));
UIManager.put("DockViewTitleBar.floatButtonText", OStrings.getString("DOCKING_HINT_UNDOCK"));
UIManager.put("DockViewTitleBar.closeButtonText", "");
UIManager.put("DockTabbedPane.minimizeButtonText", OStrings.getString("DOCKING_HINT_MINIMIZE"));
UIManager.put("DockTabbedPane.maximizeButtonText", OStrings.getString("DOCKING_HINT_MAXIMIZE"));
UIManager.put("DockTabbedPane.restoreButtonText", OStrings.getString("DOCKING_HINT_RESTORE"));
UIManager.put("DockTabbedPane.floatButtonText", OStrings.getString("DOCKING_HINT_UNDOCK"));
UIManager.put("DockTabbedPane.closeButtonText", "");
// Fonts
Font defaultFont = UIManager.getFont("Label.font");
UIManager.put("DockViewTitleBar.titleFont", defaultFont);
UIManager.put("JTabbedPaneSmartIcon.font", defaultFont);
UIManager.put("AutoHideButton.font", defaultFont);
// UI settings
UIManager.put("DockViewTitleBar.isCloseButtonDisplayed", false);
UIManager.put("DockingDesktop.closeActionAccelerator", null);
UIManager.put("DockingDesktop.maximizeActionAccelerator", null);
UIManager.put("DockingDesktop.dockActionAccelerator", null);
UIManager.put("DockingDesktop.floatActionAccelerator", null);
// Disused icons
UIManager.put("DockViewTitleBar.menu.close", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.close", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.close.rollover", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.close.pressed", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.menu.close", getIcon("empty.gif"));
// Classic design overridden by flat design
//installClassicDesign();
installFlatDesign();
// Panel notification (blinking tabs/headers) settings
UIManager.put("DockingDesktop.notificationBlinkCount", 2);
UIManager.put("DockingDesktop.notificationColor", Styles.EditorColor.COLOR_NOTIFICATION_MAX.getColor());
ensureTitlebarReadability();
}
@SuppressWarnings("unused")
private static void installClassicDesign() {
UIManager.put("OmegaTStatusArea.border", new MatteBorder(1, 1, 1, 1, Color.BLACK));
UIManager.put("DockViewTitleBar.hide", getIcon("minimize.gif"));
UIManager.put("DockViewTitleBar.hide.rollover", getIcon("minimize.rollover.gif"));
UIManager.put("DockViewTitleBar.hide.pressed", getIcon("minimize.pressed.gif"));
UIManager.put("DockViewTitleBar.maximize", getIcon("maximize.gif"));
UIManager.put("DockViewTitleBar.maximize.rollover", getIcon("maximize.rollover.gif"));
UIManager.put("DockViewTitleBar.maximize.pressed", getIcon("maximize.pressed.gif"));
UIManager.put("DockViewTitleBar.restore", getIcon("restore.gif"));
UIManager.put("DockViewTitleBar.restore.rollover", getIcon("restore.rollover.gif"));
UIManager.put("DockViewTitleBar.restore.pressed", getIcon("restore.pressed.gif"));
UIManager.put("DockViewTitleBar.dock", getIcon("restore.gif"));
UIManager.put("DockViewTitleBar.dock.rollover", getIcon("restore.rollover.gif"));
UIManager.put("DockViewTitleBar.dock.pressed", getIcon("restore.pressed.gif"));
UIManager.put("DockViewTitleBar.float", getIcon("undock.gif"));
UIManager.put("DockViewTitleBar.float.rollover", getIcon("undock.rollover.gif"));
UIManager.put("DockViewTitleBar.float.pressed", getIcon("undock.pressed.gif"));
UIManager.put("DockViewTitleBar.attach", getIcon("dock.gif"));
UIManager.put("DockViewTitleBar.attach.rollover", getIcon("dock.rollover.gif"));
UIManager.put("DockViewTitleBar.attach.pressed", getIcon("dock.pressed.gif"));
UIManager.put("DockViewTitleBar.menu.hide", getIcon("minimize.gif"));
UIManager.put("DockViewTitleBar.menu.maximize", getIcon("maximize.gif"));
UIManager.put("DockViewTitleBar.menu.restore", getIcon("restore.gif"));
UIManager.put("DockViewTitleBar.menu.dock", getIcon("restore.gif"));
UIManager.put("DockViewTitleBar.menu.float", getIcon("undock.gif"));
UIManager.put("DockViewTitleBar.menu.attach", getIcon("dock.gif"));
UIManager.put("DockTabbedPane.menu.hide", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.menu.maximize", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.menu.float", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.menu.closeAll", getIcon("empty.gif"));
UIManager.put("DockTabbedPane.menu.closeAllOther", getIcon("empty.gif"));
UIManager.put("DragControler.detachCursor", getIcon("undock.gif").getImage());
}
private static void ensureTitlebarReadability() {
// to ensure DockViewTitleBar title readability
Color textColor = UIManager.getColor("InternalFrame.inactiveTitleForeground");
Color backColor = UIManager.getColor("Panel.background");
if (textColor != null && backColor != null) { // One of these could be null
if (textColor.equals(backColor)) {
float[] hsb = Color.RGBtoHSB(textColor.getRed(),
textColor.getGreen(), textColor.getBlue(), null);
float brightness = hsb[2]; // darkest 0.0f <--> 1.0f brightest
if (brightness >= 0.5f) {
brightness -= 0.5f; // to darker
} else {
brightness += 0.5f; // to brighter
}
int rgb = Color.HSBtoRGB(hsb[0], hsb[1], brightness);
ColorUIResource res = new ColorUIResource(rgb);
UIManager.put("InternalFrame.inactiveTitleForeground", res);
}
}
UIManager.put("DockingDesktop.notificationBlinkCount", 2);
UIManager.put("DockingDesktop.notificationColor", Styles.EditorColor.COLOR_NOTIFICATION_MAX.getColor());
}
private static void installFlatDesign() {
// Colors
// #EEEEEE on Metal & OS X LAF
Color standardBgColor = UIManager.getColor("Panel.background");
// #EEEEEE -> #F6F6F6; Lighter than standard background
Color activeTitleBgColor = adjustRGB(standardBgColor, 0xF6 - 0xEE);
// #EEEEEE -> #DEDEDE; Darkest background
Color bottomAreaBgColor = adjustRGB(standardBgColor, 0xDE - 0xEE);
// #EEEEEE -> #9B9B9B; Standard border. Darker than standard background.
Color borderColor = adjustRGB(standardBgColor, 0x9B - 0xEE);
UIManager.put("OmegaTBorder.color", borderColor);
// #EEEEEE -> #575757; Darkest border
Color statusAreaColor = adjustRGB(standardBgColor, 0x57 - 0xEE);
// General highlight & shadow used in a lot of places
UIManager.put("VLDocking.highlight", activeTitleBgColor);
UIManager.put("VLDocking.shadow", statusAreaColor);
// Main window main area
int outside = 5;
UIManager.put("DockingDesktop.border", new EmptyBorder(outside, outside, outside, outside));
// Docked, visible panels get two borders if we're not careful:
// 1. Drawn by VLDocking. Surrounds panel content AND header. Set this to empty margin instead.
int panel = 2;
UIManager.put("DockView.singleDockableBorder", new EmptyBorder(panel, panel, panel, panel));
int maxPanel = outside + panel;
UIManager.put("DockView.maximizedDockableBorder", new EmptyBorder(maxPanel, maxPanel, maxPanel, maxPanel));
// 2. Drawn by OmegaT-defined Dockables. Make this a 1px line.
UIManager.put("OmegaTDockablePanel.border", new MatteBorder(1, 1, 1, 1, borderColor));
// GTK+ LAF has a default border on the viewport. Disable this.
UIManager.put("OmegaTDockablePanelViewport.border", new EmptyBorder(0, 0, 0, 0));
// Use proportionally sized internal margin for text document-like panels
UIManager.put("OmegaTDockablePanel.isProportionalMargins", true);
// Tabbed docked, visible panels are surrounded by LAF-specific chrome, but the surrounding
// colors don't appear to be available through the API. These values are from visual inspection.
if (Platform.isMacOSX()) {
UIManager.put("DockView.tabbedDockableBorder", new MatteBorder(0, 5, 5, 5, new Color(0xE6E6E6)));
} else if (isWindowsLAF() && !isWindowsClassicLAF()) {
UIManager.put("DockView.tabbedDockableBorder", new MatteBorder(2, 5, 5, 5, Color.WHITE));
} else {
UIManager.put("DockView.tabbedDockableBorder", new MatteBorder(5, 5, 5, 5, standardBgColor));
}
// Windows 8+ is very square.
int cornerRadius = isFlatWindows() ? 0 : 8;
// Panel title bars
Color activeTitleText = UIManager.getColor("Label.foreground");
// #000000 -> #808080; GTK+ has Color.WHITE for Label.disabledForeground
Color inactiveTitleText = adjustRGB(activeTitleText, 0x80);
UIManager.put("DockViewTitleBar.border",
new RoundedCornerBorder(cornerRadius, borderColor, RoundedCornerBorder.SIDE_TOP));
// Windows 7 "Classic" has Color.WHITE for this
UIManager.put("InternalFrame.activeTitleForeground", activeTitleText);
UIManager.put("InternalFrame.activeTitleBackground", activeTitleBgColor);
UIManager.put("InternalFrame.inactiveTitleForeground", inactiveTitleText);
UIManager.put("InternalFrame.inactiveTitleBackground", standardBgColor);
// Disable gradient on pane title bars
UIManager.put("DockViewTitleBar.disableCustomPaint", true);
// Main window bottom area
// AutoHideButtonPanel is where minimized panel tabs go. Use compound border to give left/right margins.
UIManager.put("AutoHideButtonPanel.bottomBorder", new CompoundBorder(
new MatteBorder(1, 0, 0, 0, borderColor),
new EmptyBorder(0, 2 * outside, 0, 2 * outside)));
UIManager.put("AutoHideButtonPanel.background", bottomAreaBgColor);
UIManager.put("AutoHideButton.expandBorderBottom",
new RoundedCornerBorder(cornerRadius, borderColor, RoundedCornerBorder.SIDE_BOTTOM));
UIManager.put("AutoHideButton.background", standardBgColor);
// OmegaT-defined status box in lower right
UIManager.put("OmegaTStatusArea.border", new MatteBorder(1, 1, 1, 1, statusAreaColor));
// Lowermost section margins
UIManager.put("OmegaTMainWindowBottomMargin.border", new EmptyBorder(0, 2 * outside, outside, 2 * outside));
UIManager.put("OmegaTEditorFilter.border", new MatteBorder(1, 1, 0, 1, borderColor));
// Undocked panel
UIManager.put("activeCaption", Color.WHITE);
UIManager.put("activeCaptionBorder", borderColor);
UIManager.put("inactiveCaption", standardBgColor);
UIManager.put("inactiveCaptionBorder", borderColor);
// Icons
UIManager.put("DockViewTitleBar.maximize", getIcon("appbar.app.tall.inactive.png"));
UIManager.put("DockViewTitleBar.maximize.rollover", getIcon("appbar.app.tall.png"));
UIManager.put("DockViewTitleBar.maximize.pressed", getIcon("appbar.app.tall.pressed.png"));
UIManager.put("DockViewTitleBar.restore", getIcon("appbar.window.restore.inactive.png"));
UIManager.put("DockViewTitleBar.restore.rollover", getIcon("appbar.window.restore.png"));
UIManager.put("DockViewTitleBar.restore.pressed", getIcon("appbar.window.restore.pressed.png"));
UIManager.put("DockViewTitleBar.hide", getIcon("appbar.hide.inactive.png"));
UIManager.put("DockViewTitleBar.hide.rollover", getIcon("appbar.hide.png"));
UIManager.put("DockViewTitleBar.hide.pressed", getIcon("appbar.hide.pressed.png"));
UIManager.put("DockViewTitleBar.float", getIcon("appbar.fullscreen.inactive.png"));
UIManager.put("DockViewTitleBar.float.rollover", getIcon("appbar.fullscreen.png"));
UIManager.put("DockViewTitleBar.float.pressed", getIcon("appbar.fullscreen.pressed.png"));
UIManager.put("DockViewTitleBar.dock", getIcon("appbar.window.restore.inactive.png"));
UIManager.put("DockViewTitleBar.dock.rollover", getIcon("appbar.window.restore.png"));
UIManager.put("DockViewTitleBar.dock.pressed", getIcon("appbar.window.restore.pressed.png"));
UIManager.put("DockViewTitleBar.attach", getIcon("appbar.dock.window.inactive.png"));
UIManager.put("DockViewTitleBar.attach.rollover", getIcon("appbar.dock.window.png"));
UIManager.put("DockViewTitleBar.attach.pressed", getIcon("appbar.dock.window.pressed.png"));
UIManager.put("DockViewTitleBar.menu.hide", getIcon("appbar.hide.png"));
UIManager.put("DockViewTitleBar.menu.maximize", getIcon("appbar.app.tall.png"));
UIManager.put("DockViewTitleBar.menu.restore", getIcon("appbar.window.restore.png"));
UIManager.put("DockViewTitleBar.menu.dock", getIcon("appbar.window.restore.png"));
UIManager.put("DockViewTitleBar.menu.float", getIcon("appbar.fullscreen.png"));
UIManager.put("DockViewTitleBar.menu.attach", getIcon("appbar.dock.window.png"));
UIManager.put("DockTabbedPane.menu.hide", getIcon("appbar.hide.png"));
UIManager.put("DockTabbedPane.menu.maximize", getIcon("appbar.app.tall.png"));
UIManager.put("DockTabbedPane.menu.float", getIcon("appbar.fullscreen.png"));
// Windows only accepts a 32x32 cursor image with no semitransparency, so you basically
// need a special image just for that.
UIManager.put("DragControler.detachCursor", ResourcesUtil.getBundledImage("appbar.fullscreen.cursor32x32.png"));
// Use more native-looking icons on OS X
if (Platform.isMacOSX()) {
UIManager.put("DockViewTitleBar.maximize", getIcon("appbar.fullscreen.corners.inactive.png"));
UIManager.put("DockViewTitleBar.maximize.rollover", getIcon("appbar.fullscreen.corners.png"));
UIManager.put("DockViewTitleBar.maximize.pressed", getIcon("appbar.fullscreen.corners.pressed.png"));
UIManager.put("DockViewTitleBar.restore", getIcon("appbar.restore.corners.inactive.png"));
UIManager.put("DockViewTitleBar.restore.rollover", getIcon("appbar.restore.corners.png"));
UIManager.put("DockViewTitleBar.restore.pressed", getIcon("appbar.restore.corners.pressed.png"));
UIManager.put("DockViewTitleBar.hide", getIcon("appbar.minus.inactive.png"));
UIManager.put("DockViewTitleBar.hide.rollover", getIcon("appbar.minus.png"));
UIManager.put("DockViewTitleBar.hide.pressed", getIcon("appbar.minus.pressed.png"));
UIManager.put("DockViewTitleBar.menu.hide", getIcon("appbar.minus.png"));
UIManager.put("DockViewTitleBar.menu.maximize", getIcon("appbar.fullscreen.corners.png"));
UIManager.put("DockViewTitleBar.menu.restore", getIcon("appbar.restore.corners.png"));
UIManager.put("DockTabbedPane.menu.hide", getIcon("appbar.minus.png"));
UIManager.put("DockTabbedPane.menu.maximize", getIcon("appbar.fullscreen.corners.png"));
UIManager.put("DragControler.detachCursor", ResourcesUtil.getBundledImage("appbar.fullscreen.png"));
}
}
/**
* Adjust a color by adding some constant to its RGB values, clamping to the
* range 0-255.
*/
private static Color adjustRGB(Color color, int adjustment) {
Color result = new Color(Math.max(0, Math.min(255, color.getRed() + adjustment)),
Math.max(0, Math.min(255, color.getGreen() + adjustment)),
Math.max(0, Math.min(255, color.getBlue() + adjustment)));
return result;
}
// Windows Classic LAF detection from http://stackoverflow.com/a/4386821/448068
private static boolean isWindowsLAF() {
return UIManager.getLookAndFeel().getID().equals("Windows");
}
private static boolean isWindowsClassicLAF() {
return isWindowsLAF() && !(Boolean) Toolkit.getDefaultToolkit().getDesktopProperty("win.xpstyle.themeActive");
}
// This check fails to detect Windows 10 correctly on Java 1.8 prior to u60.
// See: https://bugs.openjdk.java.net/browse/JDK-8066504
private static boolean isFlatWindows() {
return System.getProperty("os.name").startsWith("Windows")
&& System.getProperty("os.version").matches("6\\.[23]|10\\..*");
}
/**
* Load icon from classpath.
*
* @param iconName
* icon file name
* @return icon instance
*/
private static ImageIcon getIcon(String iconName) {
Image image = ResourcesUtil.getBundledImage(iconName);
return image == null ? null : new ImageIcon(image);
}
/**
* Removes first, last and duplicate separators from menu.
*/
public static void removeUnusedMenuSeparators(final JPopupMenu menu) {
if (menu.getComponentCount() > 0 && menu.getComponent(0) instanceof JSeparator) {
// remove first separator
menu.remove(0);
}
if (menu.getComponentCount() > 0
&& menu.getComponent(menu.getComponentCount() - 1) instanceof JSeparator) {
// remove last separator
menu.remove(menu.getComponentCount() - 1);
}
for (int i = 0; i < menu.getComponentCount() - 1; i++) {
if (menu.getComponent(i) instanceof JSeparator && menu.getComponent(i + 1) instanceof JSeparator) {
// remove duplicate separators
menu.remove(i);
}
}
}
/**
* Ensure that any "closed" Dockables are made visible.
*/
public static void ensureDockablesVisible(DockingDesktop desktop) {
for (DockableState state : desktop.getDockables()) {
if (state.isClosed()) {
// VLDocking says this is how you re-show a closed Dockable,
// but it prints a stack trace. So just ignore it?
desktop.addDockable(state.getDockable());
}
}
}
/**
* Traverse the given container's parents until either an instance of
* DockingDesktop is found, or null is found.
*
* @param c
* The container to search
* @return Either the parent DockingDesktop, or null
*/
public static DockingDesktop getDesktop(Container c) {
while (c != null && !(c instanceof DockingDesktop)) {
c = c.getParent(); // find dockable desktop
}
return (DockingDesktop) c;
}
}