diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ui/JSystemFileChooser.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ui/JSystemFileChooser.java index 53e908306..77869574a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ui/JSystemFileChooser.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/ui/JSystemFileChooser.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.util.ui; -import java.awt.Color; import java.io.File; import javax.swing.JFileChooser; import javax.swing.LookAndFeel; diff --git a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/button/BEButtonUI.java b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/button/BEButtonUI.java index 32cdebbb9..4bd82f98e 100644 --- a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/button/BEButtonUI.java +++ b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/button/BEButtonUI.java @@ -17,14 +17,13 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; -import java.awt.Rectangle; import javax.swing.AbstractButton; import javax.swing.ButtonModel; +import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JRadioButton; -import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.LookAndFeel; import javax.swing.UIManager; @@ -36,9 +35,13 @@ import javax.swing.plaf.InsetsUIResource; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicButtonUI; import javax.swing.text.JTextComponent; -import org.jackhuang.hmcl.laf.BEUtils; +import org.jackhuang.hmcl.laf.utils.AnimationController; import org.jackhuang.hmcl.laf.utils.Icon9Factory; +import org.jackhuang.hmcl.laf.utils.Skin; +import org.jackhuang.hmcl.laf.utils.TMSchema; +import org.jackhuang.hmcl.laf.utils.TMSchema.Part; +import org.jackhuang.hmcl.laf.utils.TMSchema.State; /** * JButton的UI实现类. @@ -47,7 +50,7 @@ import org.jackhuang.hmcl.laf.utils.Icon9Factory; * @version 1.0 * @see com.sun.java.swing.plaf.windows.WindowsButtonUI */ -public class BEButtonUI extends BasicButtonUI { +public class BEButtonUI extends BasicButtonUI implements Skin { private static final Icon9Factory ICON_9 = new Icon9Factory("button"); @@ -130,29 +133,6 @@ public class BEButtonUI extends BasicButtonUI { return focusColor; } - // ******************************** - // Paint Methods - // ******************************** - @Override - protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect) { - // focus painted same color as text on Basic?? - int width = b.getWidth(); - int height = b.getHeight(); - g.setColor(getFocusColor()); - - //** modified by jb2011:绘制虚线方法改成可以设置虚线步进的方法,步进设为2则更好看一点 -// BasicGraphicsUtils.drawDashedRect(g, dashedRectGapX, dashedRectGapY, -// width - dashedRectGapWidth, height - dashedRectGapHeight); - // 绘制虚线框 - BEUtils.drawDashedRect(g, dashedRectGapX, dashedRectGapY, - width - dashedRectGapWidth, height - dashedRectGapHeight); - // 绘制虚线框的半透明白色立体阴影(半透明的用处在于若隐若现的效果比纯白要来的柔和的多) - g.setColor(new Color(255, 255, 255, 50)); - // 立体阴影就是向右下偏移一个像素实现的 - BEUtils.drawDashedRect(g, dashedRectGapX + 1, dashedRectGapY + 1, - width - dashedRectGapWidth, height - dashedRectGapHeight); - } - // ******************************** // Layout Methods // ******************************** @@ -223,7 +203,7 @@ public class BEButtonUI extends BasicButtonUI { dh -= (insets.top + insets.bottom); } - if (toolbar) + /*if (toolbar) //此状态下JToggleButton和JButton使用各自的背景实现,2012-10-16前无论是不是JToggleButton都是使用该种实是不太合理的 if (model.isRollover() || model.isPressed()) if (c instanceof JToggleButton) @@ -248,10 +228,105 @@ public class BEButtonUI extends BasicButtonUI { else key = "normal"; ICON_9.get(key).draw((Graphics2D) g, dx, dy, dw, dh); - } + }*/ + AnimationController.paintSkin(c, INSTANCE, g, dx, dy, dw, dh, getXPButtonState(b)); } } + @Override + public Part getPart(JComponent c) { + return getXPButtonType((AbstractButton) c); + } + + static Part getXPButtonType(AbstractButton b) { + if (b instanceof JCheckBox) + return Part.BP_CHECKBOX; + if (b instanceof JRadioButton) + return Part.BP_RADIOBUTTON; + boolean toolbar = (b.getParent() instanceof JToolBar); + return toolbar ? Part.TP_BUTTON : Part.BP_PUSHBUTTON; + } + + static State getXPButtonState(AbstractButton b) { + Part part = getXPButtonType(b); + ButtonModel model = b.getModel(); + State state = State.NORMAL; + switch (part) { + case BP_RADIOBUTTON: + case BP_CHECKBOX: + if (!model.isEnabled()) + state = (model.isSelected()) ? State.CHECKEDDISABLED + : State.UNCHECKEDDISABLED; + else if (model.isPressed() && model.isArmed()) + state = (model.isSelected()) ? State.CHECKEDPRESSED + : State.UNCHECKEDPRESSED; + else if (model.isRollover()) + state = (model.isSelected()) ? State.CHECKEDHOT + : State.UNCHECKEDHOT; + else + state = (model.isSelected()) ? State.CHECKEDNORMAL + : State.UNCHECKEDNORMAL; + break; + case BP_PUSHBUTTON: + case TP_BUTTON: + boolean toolbar = (b.getParent() instanceof JToolBar); + if (toolbar) { + if (model.isArmed() && model.isPressed()) + state = State.PRESSED; + else if (!model.isEnabled()) + state = State.DISABLED; + else if (model.isSelected() && model.isRollover()) + state = State.HOTCHECKED; + else if (model.isSelected()) + state = State.CHECKED; + else if (model.isRollover()) + state = State.HOT; + else if (b.hasFocus()) + state = State.HOT; + } else + if ((model.isArmed() && model.isPressed()) + || model.isSelected()) + state = State.PRESSED; + else if (!model.isEnabled()) + state = State.DISABLED; + else if (model.isRollover() || model.isPressed()) + state = State.HOT; + else if (b instanceof JButton + && ((JButton) b).isDefaultButton()) + state = State.DEFAULTED; + break; + default: + state = State.NORMAL; + } + + return state; + } + + @Override + public void paintSkinRaw(Graphics g, int dx, int dy, int dw, int dh, TMSchema.State state) { + String key; + switch (state) { + case SELECTED: + key = "selected"; + break; + case PRESSED: + key = "pressed"; + break; + case DISABLED: + key = "disabled"; + break; + case HOT: + key = "rollover"; + break; + case NORMAL: + key = "normal"; + break; + default: + return; + } + ICON_9.get(key).draw((Graphics2D) g, dx, dy, dw, dh); + } + /** * returns - b.getBorderInsets(c) if border is opaque - null if border is * completely non-opaque - somewhere inbetween if border is compound and diff --git a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/combox/BEComboBoxUI.java b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/combox/BEComboBoxUI.java index 3a7a5b2fd..46de2a4ae 100644 --- a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/combox/BEComboBoxUI.java +++ b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/combox/BEComboBoxUI.java @@ -37,7 +37,11 @@ import javax.swing.plaf.basic.BasicComboBoxEditor; import javax.swing.plaf.basic.BasicComboBoxUI; import javax.swing.plaf.basic.BasicComboPopup; import javax.swing.plaf.basic.ComboPopup; +import org.jackhuang.hmcl.laf.utils.AnimationController; import org.jackhuang.hmcl.laf.utils.Icon9Factory; +import org.jackhuang.hmcl.laf.utils.Skin; +import org.jackhuang.hmcl.laf.utils.TMSchema; +import org.jackhuang.hmcl.laf.utils.TMSchema.State; /** * JComboBox的UI实现类. @@ -47,7 +51,7 @@ import org.jackhuang.hmcl.laf.utils.Icon9Factory; * @see com.sun.java.swing.plaf.windows.WindowsComboBoxUI */ public class BEComboBoxUI extends BasicComboBoxUI - implements org.jackhuang.hmcl.laf.BeautyEyeLNFHelper.__UseParentPaintSurported, MouseListener { + implements org.jackhuang.hmcl.laf.BeautyEyeLNFHelper.__UseParentPaintSurported, MouseListener, Skin { private static final Icon9Factory ICON_9 = new Icon9Factory("combo"); private static final Dimension BTN_SIZE = new Dimension(17, 20); @@ -164,10 +168,50 @@ public class BEComboBoxUI extends BasicComboBoxUI */ @Override public void paintCurrentValueBackground(Graphics g, Rectangle bounds, boolean hasFocus) { - ICON_9.getWithComboState("", comboBox.isEnabled(), mouseDown, mouseInside) - .draw((Graphics2D) g, bounds.x, bounds.y, bounds.width, bounds.height); + AnimationController.paintSkin(comboBox, this, g, bounds.x, bounds.y, bounds.width, bounds.height, getXPComboBoxState(comboBox)); } + @Override + public TMSchema.Part getPart(JComponent c) { + return TMSchema.Part.CP_COMBOBOX; + } + + State getXPComboBoxState(JComponent c) { + State state = State.NORMAL; + if (!c.isEnabled()) { + state = State.DISABLED; + } else if (isPopupVisible(comboBox)) { + state = State.PRESSED; + } else if (mouseInside) { + state = State.HOT; + } + return state; + } + + @Override + public void paintSkinRaw(Graphics g, int x, int y, int w, int h, TMSchema.State state) { + String key; + switch (state) { + case PRESSED: + key = "pressed"; + break; + case DISABLED: + key = "disabled"; + break; + case HOT: + key = "rollover"; + break; + case NORMAL: + key = "normal"; + break; + default: + return; + } + ICON_9.get(key).draw((Graphics2D) g, x, y, w, h); + } + + + //* copy from BasicComboBoxUI and modified by jb2011 /** * Creates the default renderer that will be used in a non-editiable combo diff --git a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/popup/TranslucentPopupFactory.java b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/popup/TranslucentPopupFactory.java index 375c5cbb3..5808fc5da 100644 --- a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/popup/TranslucentPopupFactory.java +++ b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/popup/TranslucentPopupFactory.java @@ -149,10 +149,8 @@ public class TranslucentPopupFactory extends PopupFactory { Component c = getComponent(); Window window = SwingUtilities.getWindowAncestor(c); - if (c instanceof JWindow) { + if (c instanceof JWindow) ((Window) c).dispose(); - c = null; - } // If our parent is a DefaultFrame, we need to dispose it, too. if (window instanceof DefaultFrame) window.dispose(); diff --git a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/AnimationController.java b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/AnimationController.java new file mode 100644 index 000000000..ad1f4d01e --- /dev/null +++ b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/AnimationController.java @@ -0,0 +1,383 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * 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 3 of the License, or + * (at your option) 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, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.laf.utils; + +import java.awt.AlphaComposite; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeEvent; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.Map; +import java.util.WeakHashMap; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import org.jackhuang.hmcl.laf.BeautyEyeLookAndFeel; + +import org.jackhuang.hmcl.laf.utils.TMSchema.State; +import static org.jackhuang.hmcl.laf.utils.TMSchema.State.*; +import org.jackhuang.hmcl.laf.utils.TMSchema.Part; + +/** + * A class to help mimic Vista theme animations. The only kind of + * animation it handles for now is 'transition' animation (this seems + * to be the only animation which Vista theme can do). This is when + * one picture fadein over another one in some period of time. + * According to + * https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=86852&SiteID=4 + * The animations are all linear. + * + * This class has a number of responsibilities. + * + * + * @author Igor Kushnirskiy + */ +public class AnimationController implements ActionListener, PropertyChangeListener { + + private static AnimationController INSTANCE = new AnimationController(); + private static final String ANIMATION_CONTROLLER_KEY = "BeautyEye.AnimationController"; + private final Map> animationStateMap + = new WeakHashMap<>(); + + //this timer is used to cause repaint on animated components + //30 repaints per second should give smooth animation affect + private final Timer timer = new Timer(1000 / 30, this); + + private static synchronized AnimationController getAnimationController(JComponent c) { + if (c.getClientProperty(ANIMATION_CONTROLLER_KEY) == null) + c.putClientProperty(ANIMATION_CONTROLLER_KEY, new AnimationController()); + return (AnimationController) c.getClientProperty(ANIMATION_CONTROLLER_KEY); + } + + private AnimationController() { + timer.setRepeats(true); + timer.setCoalesce(true); + //we need to dispose the controller on l&f change + UIManager.addPropertyChangeListener(this); + } + + private static void triggerAnimation(JComponent c, + Part part, State newState) { + if (c instanceof javax.swing.JTabbedPane + || part == Part.TP_BUTTON) + //idk: we can not handle tabs animation because + //the same (component,part) is used to handle all the tabs + //and we can not track the states + //Vista theme might have transition duration for toolbar buttons + //but native application does not seem to animate them + return; + AnimationController controller + = AnimationController.getAnimationController(c); + State oldState = controller.getState(c, part); + if (oldState != newState) { + controller.putState(c, part, newState); + if (newState == State.DEFAULTED) + // it seems for DEFAULTED button state Vista does animation from + // HOT + oldState = State.HOT; + if (oldState != null) { + long duration; + //if (newState == State.DEFAULTED) { + //Only button might have DEFAULTED state + //idk: do not know how to get the value from Vista + //one second seems plausible value + duration = 500; + /*} else { + XPStyle xp = XPStyle.getXP(); + duration = (xp != null) + ? xp.getThemeTransitionDuration( + c, part, + normalizeState(oldState), + normalizeState(newState), + Prop.TRANSITIONDURATIONS) + : 1000; + }*/ + controller.startAnimation(c, part, oldState, newState, duration); + } + } + } + + // for scrollbar up, down, left and right button pictures are + // defined by states. It seems that theme has duration defined + // only for up button states thus we doing this translation here. + private static State normalizeState(State state) { + State rv; + switch (state) { + case DOWNPRESSED: + case LEFTPRESSED: + case RIGHTPRESSED: + rv = UPPRESSED; + break; + + case DOWNDISABLED: + case LEFTDISABLED: + case RIGHTDISABLED: + rv = UPDISABLED; + break; + + case DOWNHOT: + case LEFTHOT: + case RIGHTHOT: + rv = UPHOT; + break; + + case DOWNNORMAL: + case LEFTNORMAL: + case RIGHTNORMAL: + rv = UPNORMAL; + break; + + default: + rv = state; + break; + } + return rv; + } + + private synchronized State getState(JComponent component, Part part) { + State rv = null; + Object tmpObject + = component.getClientProperty(PartUIClientPropertyKey.getKey(part)); + if (tmpObject instanceof State) + rv = (State) tmpObject; + return rv; + } + + private synchronized void putState(JComponent component, Part part, + State state) { + component.putClientProperty(PartUIClientPropertyKey.getKey(part), + state); + } + + private synchronized void startAnimation(JComponent component, + Part part, + State startState, + State endState, + long millis) { + boolean isForwardAndReverse = false; + if (endState == State.DEFAULTED) + isForwardAndReverse = true; + Map map = animationStateMap.get(component); + if (millis <= 0) { + if (map != null) { + map.remove(part); + if (map.isEmpty()) + animationStateMap.remove(component); + } + return; + } + if (map == null) { + map = new EnumMap<>(Part.class); + animationStateMap.put(component, map); + } + map.put(part, + new AnimationState(startState, millis, isForwardAndReverse)); + if (!timer.isRunning()) + timer.start(); + } + + public static void paintSkin(JComponent component, Skin skin, + Graphics g, int dx, int dy, int dw, int dh, State state) { + triggerAnimation(component, skin.getPart(component), state); + AnimationController controller = getAnimationController(component); + synchronized (controller) { + AnimationState animationState = null; + Map map + = controller.animationStateMap.get(component); + if (map != null) + animationState = map.get(skin.getPart(component)); + if (animationState != null) + animationState.paintSkin(skin, g, dx, dy, dw, dh, state); + else + skin.paintSkinRaw(g, dx, dy, dw, dh, state); + } + } + + @Override + public synchronized void propertyChange(PropertyChangeEvent e) { + if ("lookAndFeel".equals(e.getPropertyName()) + && !(e.getNewValue() instanceof BeautyEyeLookAndFeel)) + dispose(); + } + + @Override + public synchronized void actionPerformed(ActionEvent e) { + java.util.List componentsToRemove = null; + java.util.List partsToRemove = null; + for (JComponent component : animationStateMap.keySet()) { + component.repaint(); + if (partsToRemove != null) + partsToRemove.clear(); + Map map = animationStateMap.get(component); + if (!component.isShowing() + || map == null + || map.isEmpty()) { + if (componentsToRemove == null) + componentsToRemove = new ArrayList<>(); + componentsToRemove.add(component); + continue; + } + for (Part part : map.keySet()) + if (map.get(part).isDone()) { + if (partsToRemove == null) + partsToRemove = new ArrayList<>(); + partsToRemove.add(part); + } + if (partsToRemove != null) + if (partsToRemove.size() == map.size()) { + //animation is done for the component + if (componentsToRemove == null) + componentsToRemove = new ArrayList<>(); + componentsToRemove.add(component); + } else + for (Part part : partsToRemove) + map.remove(part); + } + if (componentsToRemove != null) + for (JComponent component : componentsToRemove) + animationStateMap.remove(component); + if (animationStateMap.isEmpty()) + timer.stop(); + } + + private synchronized void dispose() { + timer.stop(); + UIManager.removePropertyChangeListener(this); + INSTANCE = null; + } + + private static class AnimationState { + + private final State startState; + + //animation duration in nanoseconds + private final long duration; + + //animatin start time in nanoseconds + private long startTime; + + //direction the alpha value is changing + //forward - from 0 to 1 + //!forward - from 1 to 0 + private boolean isForward = true; + + //if isForwardAndReverse the animation continually goes + //forward and reverse. alpha value is changing from 0 to 1 then + //from 1 to 0 and so forth + private final boolean isForwardAndReverse; + + private float progress; + + AnimationState(final State startState, + final long milliseconds, + boolean isForwardAndReverse) { + assert startState != null && milliseconds > 0; + assert SwingUtilities.isEventDispatchThread(); + + this.startState = startState; + this.duration = milliseconds * 1000000; + this.startTime = System.nanoTime(); + this.isForwardAndReverse = isForwardAndReverse; + progress = 0f; + } + + private void updateProgress() { + assert SwingUtilities.isEventDispatchThread(); + + if (isDone()) + return; + long currentTime = System.nanoTime(); + + progress = ((float) (currentTime - startTime)) + / duration; + progress = Math.max(progress, 0); //in case time was reset + if (progress >= 1) { + progress = 1; + if (isForwardAndReverse) { + startTime = currentTime; + progress = 0; + isForward = !isForward; + } + } + } + + void paintSkin(Skin skin, Graphics _g, + int dx, int dy, int dw, int dh, State state) { + assert SwingUtilities.isEventDispatchThread(); + + updateProgress(); + if (!isDone()) { + Graphics2D g = (Graphics2D) _g.create(); + skin.paintSkinRaw(g, dx, dy, dw, dh, startState); + float alpha; + if (isForward) + alpha = progress; + else + alpha = 1 - progress; + g.setComposite(AlphaComposite.SrcOver.derive(alpha)); + skin.paintSkinRaw(g, dx, dy, dw, dh, state); + g.dispose(); + } else + skin.paintSkinRaw(_g, dx, dy, dw, dh, state); + } + + boolean isDone() { + assert SwingUtilities.isEventDispatchThread(); + + return progress >= 1; + } + } + + private static class PartUIClientPropertyKey { + + private static final Map MAP + = new EnumMap<>(Part.class); + + static synchronized PartUIClientPropertyKey getKey(Part part) { + PartUIClientPropertyKey rv = MAP.get(part); + if (rv == null) { + rv = new PartUIClientPropertyKey(part); + MAP.put(part, rv); + } + return rv; + } + + private final Part part; + + private PartUIClientPropertyKey(Part part) { + this.part = part; + } + + @Override + public String toString() { + return part.toString(); + } + } +} diff --git a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/Skin.java b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/Skin.java new file mode 100644 index 000000000..fab7bcffd --- /dev/null +++ b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/Skin.java @@ -0,0 +1,31 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * 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 3 of the License, or + * (at your option) 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, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.laf.utils; + +import java.awt.Graphics; +import javax.swing.JComponent; + +/** + * + * @author huang + */ +public interface Skin { + void paintSkinRaw(Graphics g, int x, int y, int w, int h, TMSchema.State state); + + TMSchema.Part getPart(JComponent c); +} diff --git a/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/TMSchema.java b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/TMSchema.java new file mode 100644 index 000000000..9182ad31a --- /dev/null +++ b/HMCLaF/src/main/java/org/jackhuang/hmcl/laf/utils/TMSchema.java @@ -0,0 +1,580 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * 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 3 of the License, or + * (at your option) 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, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.laf.utils; +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. + * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + +/* + *

These classes are designed to be used while the + * corresponding LookAndFeel class has been installed + * (UIManager.setLookAndFeel(new XXXLookAndFeel())). + * Using them while a different LookAndFeel is installed + * may produce unexpected results, including exceptions. + * Additionally, changing the LookAndFeel + * maintained by the UIManager without updating the + * corresponding ComponentUI of any + * JComponents may also produce unexpected results, + * such as the wrong colors showing up, and is generally not + * encouraged. + * + */ + +import java.awt.*; +import java.util.*; + +import javax.swing.*; + +/** + * Implements Windows Parts and their States and Properties for the Windows Look and Feel. + * + * See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/userex/topics/partsandstates.asp + * See tmschema.h (or vssym32.h & vsstyle.h for MS Vista) + * + * @author Leif Samuelsson + */ +public class TMSchema { + + /** + * An enumeration of the various Windows controls (also known as + * components, or top-level parts) + */ + public static enum Control { + BUTTON, + COMBOBOX, + EDIT, + HEADER, + LISTBOX, + LISTVIEW, + MENU, + PROGRESS, + REBAR, + SCROLLBAR, + SPIN, + TAB, + TOOLBAR, + TRACKBAR, + TREEVIEW, + WINDOW + } + + + /** + * An enumeration of the Windows compoent parts + */ + public static enum Part { + MENU (Control.MENU, 0), // Special case, not in native + MP_BARBACKGROUND (Control.MENU, 7), + MP_BARITEM (Control.MENU, 8), + MP_POPUPBACKGROUND (Control.MENU, 9), + MP_POPUPBORDERS (Control.MENU, 10), + MP_POPUPCHECK (Control.MENU, 11), + MP_POPUPCHECKBACKGROUND (Control.MENU, 12), + MP_POPUPGUTTER (Control.MENU, 13), + MP_POPUPITEM (Control.MENU, 14), + MP_POPUPSEPARATOR (Control.MENU, 15), + MP_POPUPSUBMENU (Control.MENU, 16), + + BP_PUSHBUTTON (Control.BUTTON, 1), + BP_RADIOBUTTON(Control.BUTTON, 2), + BP_CHECKBOX (Control.BUTTON, 3), + BP_GROUPBOX (Control.BUTTON, 4), + + CP_COMBOBOX (Control.COMBOBOX, 0), + CP_DROPDOWNBUTTON(Control.COMBOBOX, 1), + CP_BACKGROUND (Control.COMBOBOX, 2), + CP_TRANSPARENTBACKGROUND (Control.COMBOBOX, 3), + CP_BORDER (Control.COMBOBOX, 4), + CP_READONLY (Control.COMBOBOX, 5), + CP_DROPDOWNBUTTONRIGHT (Control.COMBOBOX, 6), + CP_DROPDOWNBUTTONLEFT (Control.COMBOBOX, 7), + CP_CUEBANNER (Control.COMBOBOX, 8), + + + EP_EDIT (Control.EDIT, 0), + EP_EDITTEXT(Control.EDIT, 1), + + HP_HEADERITEM(Control.HEADER, 1), + HP_HEADERSORTARROW(Control.HEADER, 4), + + LBP_LISTBOX(Control.LISTBOX, 0), + + LVP_LISTVIEW(Control.LISTVIEW, 0), + + PP_PROGRESS (Control.PROGRESS, 0), + PP_BAR (Control.PROGRESS, 1), + PP_BARVERT (Control.PROGRESS, 2), + PP_CHUNK (Control.PROGRESS, 3), + PP_CHUNKVERT(Control.PROGRESS, 4), + + RP_GRIPPER (Control.REBAR, 1), + RP_GRIPPERVERT(Control.REBAR, 2), + + SBP_SCROLLBAR (Control.SCROLLBAR, 0), + SBP_ARROWBTN (Control.SCROLLBAR, 1), + SBP_THUMBBTNHORZ (Control.SCROLLBAR, 2), + SBP_THUMBBTNVERT (Control.SCROLLBAR, 3), + SBP_LOWERTRACKHORZ(Control.SCROLLBAR, 4), + SBP_UPPERTRACKHORZ(Control.SCROLLBAR, 5), + SBP_LOWERTRACKVERT(Control.SCROLLBAR, 6), + SBP_UPPERTRACKVERT(Control.SCROLLBAR, 7), + SBP_GRIPPERHORZ (Control.SCROLLBAR, 8), + SBP_GRIPPERVERT (Control.SCROLLBAR, 9), + SBP_SIZEBOX (Control.SCROLLBAR, 10), + + SPNP_UP (Control.SPIN, 1), + SPNP_DOWN(Control.SPIN, 2), + + TABP_TABITEM (Control.TAB, 1), + TABP_TABITEMLEFTEDGE (Control.TAB, 2), + TABP_TABITEMRIGHTEDGE(Control.TAB, 3), + TABP_PANE (Control.TAB, 9), + + TP_TOOLBAR (Control.TOOLBAR, 0), + TP_BUTTON (Control.TOOLBAR, 1), + TP_SEPARATOR (Control.TOOLBAR, 5), + TP_SEPARATORVERT (Control.TOOLBAR, 6), + + TKP_TRACK (Control.TRACKBAR, 1), + TKP_TRACKVERT (Control.TRACKBAR, 2), + TKP_THUMB (Control.TRACKBAR, 3), + TKP_THUMBBOTTOM(Control.TRACKBAR, 4), + TKP_THUMBTOP (Control.TRACKBAR, 5), + TKP_THUMBVERT (Control.TRACKBAR, 6), + TKP_THUMBLEFT (Control.TRACKBAR, 7), + TKP_THUMBRIGHT (Control.TRACKBAR, 8), + TKP_TICS (Control.TRACKBAR, 9), + TKP_TICSVERT (Control.TRACKBAR, 10), + + TVP_TREEVIEW(Control.TREEVIEW, 0), + TVP_GLYPH (Control.TREEVIEW, 2), + + WP_WINDOW (Control.WINDOW, 0), + WP_CAPTION (Control.WINDOW, 1), + WP_MINCAPTION (Control.WINDOW, 3), + WP_MAXCAPTION (Control.WINDOW, 5), + WP_FRAMELEFT (Control.WINDOW, 7), + WP_FRAMERIGHT (Control.WINDOW, 8), + WP_FRAMEBOTTOM (Control.WINDOW, 9), + WP_SYSBUTTON (Control.WINDOW, 13), + WP_MDISYSBUTTON (Control.WINDOW, 14), + WP_MINBUTTON (Control.WINDOW, 15), + WP_MDIMINBUTTON (Control.WINDOW, 16), + WP_MAXBUTTON (Control.WINDOW, 17), + WP_CLOSEBUTTON (Control.WINDOW, 18), + WP_MDICLOSEBUTTON (Control.WINDOW, 20), + WP_RESTOREBUTTON (Control.WINDOW, 21), + WP_MDIRESTOREBUTTON(Control.WINDOW, 22); + + private final Control control; + private final int value; + + private Part(Control control, int value) { + this.control = control; + this.value = value; + } + + public int getValue() { + return value; + } + + public String getControlName(Component component) { + String str = ""; + if (component instanceof JComponent) { + JComponent c = (JComponent)component; + String subAppName = (String)c.getClientProperty("XPStyle.subAppName"); + if (subAppName != null) { + str = subAppName + "::"; + } + } + return str + control.toString(); + } + + public String toString() { + return control.toString()+"."+name(); + } + } + + + /** + * An enumeration of the possible component states + */ + public static enum State { + ACTIVE, + ASSIST, + BITMAP, + CHECKED, + CHECKEDDISABLED, + CHECKEDHOT, + CHECKEDNORMAL, + CHECKEDPRESSED, + CHECKMARKNORMAL, + CHECKMARKDISABLED, + BULLETNORMAL, + BULLETDISABLED, + CLOSED, + DEFAULTED, + DISABLED, + DISABLEDHOT, + DISABLEDPUSHED, + DOWNDISABLED, + DOWNHOT, + DOWNNORMAL, + DOWNPRESSED, + FOCUSED, + HOT, + HOTCHECKED, + ICONHOT, + ICONNORMAL, + ICONPRESSED, + ICONSORTEDHOT, + ICONSORTEDNORMAL, + ICONSORTEDPRESSED, + INACTIVE, + INACTIVENORMAL, // See note 1 + INACTIVEHOT, // See note 1 + INACTIVEPUSHED, // See note 1 + INACTIVEDISABLED, // See note 1 + LEFTDISABLED, + LEFTHOT, + LEFTNORMAL, + LEFTPRESSED, + MIXEDDISABLED, + MIXEDHOT, + MIXEDNORMAL, + MIXEDPRESSED, + NORMAL, + PRESSED, + OPENED, + PUSHED, + READONLY, + RIGHTDISABLED, + RIGHTHOT, + RIGHTNORMAL, + RIGHTPRESSED, + SELECTED, + UNCHECKEDDISABLED, + UNCHECKEDHOT, + UNCHECKEDNORMAL, + UNCHECKEDPRESSED, + UPDISABLED, + UPHOT, + UPNORMAL, + UPPRESSED, + HOVER, + UPHOVER, + DOWNHOVER, + LEFTHOVER, + RIGHTHOVER, + SORTEDDOWN, + SORTEDHOT, + SORTEDNORMAL, + SORTEDPRESSED, + SORTEDUP; + + + /** + * A map of allowed states for each Part + */ + private static EnumMap stateMap; + + private static synchronized void initStates() { + stateMap = new EnumMap(Part.class); + + stateMap.put(Part.EP_EDITTEXT, + new State[] { + NORMAL, HOT, SELECTED, DISABLED, FOCUSED, READONLY, ASSIST + }); + + stateMap.put(Part.BP_PUSHBUTTON, + new State[] { NORMAL, HOT, PRESSED, DISABLED, DEFAULTED }); + + stateMap.put(Part.BP_RADIOBUTTON, + new State[] { + UNCHECKEDNORMAL, UNCHECKEDHOT, UNCHECKEDPRESSED, UNCHECKEDDISABLED, + CHECKEDNORMAL, CHECKEDHOT, CHECKEDPRESSED, CHECKEDDISABLED + }); + + stateMap.put(Part.BP_CHECKBOX, + new State[] { + UNCHECKEDNORMAL, UNCHECKEDHOT, UNCHECKEDPRESSED, UNCHECKEDDISABLED, + CHECKEDNORMAL, CHECKEDHOT, CHECKEDPRESSED, CHECKEDDISABLED, + MIXEDNORMAL, MIXEDHOT, MIXEDPRESSED, MIXEDDISABLED + }); + + State[] comboBoxStates = new State[] { NORMAL, HOT, PRESSED, DISABLED }; + stateMap.put(Part.CP_COMBOBOX, comboBoxStates); + stateMap.put(Part.CP_DROPDOWNBUTTON, comboBoxStates); + stateMap.put(Part.CP_BACKGROUND, comboBoxStates); + stateMap.put(Part.CP_TRANSPARENTBACKGROUND, comboBoxStates); + stateMap.put(Part.CP_BORDER, comboBoxStates); + stateMap.put(Part.CP_READONLY, comboBoxStates); + stateMap.put(Part.CP_DROPDOWNBUTTONRIGHT, comboBoxStates); + stateMap.put(Part.CP_DROPDOWNBUTTONLEFT, comboBoxStates); + stateMap.put(Part.CP_CUEBANNER, comboBoxStates); + + stateMap.put(Part.HP_HEADERITEM, new State[] { NORMAL, HOT, PRESSED, + SORTEDNORMAL, SORTEDHOT, SORTEDPRESSED, + ICONNORMAL, ICONHOT, ICONPRESSED, + ICONSORTEDNORMAL, ICONSORTEDHOT, ICONSORTEDPRESSED }); + + stateMap.put(Part.HP_HEADERSORTARROW, + new State[] {SORTEDDOWN, SORTEDUP}); + + State[] scrollBarStates = new State[] { NORMAL, HOT, PRESSED, DISABLED, HOVER }; + stateMap.put(Part.SBP_SCROLLBAR, scrollBarStates); + stateMap.put(Part.SBP_THUMBBTNVERT, scrollBarStates); + stateMap.put(Part.SBP_THUMBBTNHORZ, scrollBarStates); + stateMap.put(Part.SBP_GRIPPERVERT, scrollBarStates); + stateMap.put(Part.SBP_GRIPPERHORZ, scrollBarStates); + + stateMap.put(Part.SBP_ARROWBTN, + new State[] { + UPNORMAL, UPHOT, UPPRESSED, UPDISABLED, + DOWNNORMAL, DOWNHOT, DOWNPRESSED, DOWNDISABLED, + LEFTNORMAL, LEFTHOT, LEFTPRESSED, LEFTDISABLED, + RIGHTNORMAL, RIGHTHOT, RIGHTPRESSED, RIGHTDISABLED, + UPHOVER, DOWNHOVER, LEFTHOVER, RIGHTHOVER + }); + + + State[] spinnerStates = new State[] { NORMAL, HOT, PRESSED, DISABLED }; + stateMap.put(Part.SPNP_UP, spinnerStates); + stateMap.put(Part.SPNP_DOWN, spinnerStates); + + stateMap.put(Part.TVP_GLYPH, new State[] { CLOSED, OPENED }); + + State[] frameButtonStates = new State[] { + NORMAL, HOT, PUSHED, DISABLED, // See note 1 + INACTIVENORMAL, INACTIVEHOT, INACTIVEPUSHED, INACTIVEDISABLED, + }; + // Note 1: The INACTIVE frame button states apply when the frame + // is inactive. They are not defined in tmschema.h + + // Fix for 6316538: Vista has five frame button states + /*if (ThemeReader.getInt(Control.WINDOW.toString(), + Part.WP_CLOSEBUTTON.getValue(), 1, + Prop.IMAGECOUNT.getValue()) == 10) { + frameButtonStates = new State[] { + NORMAL, HOT, PUSHED, DISABLED, null, + INACTIVENORMAL, INACTIVEHOT, INACTIVEPUSHED, INACTIVEDISABLED, null + }; + }*/ + + stateMap.put(Part.WP_MINBUTTON, frameButtonStates); + stateMap.put(Part.WP_MAXBUTTON, frameButtonStates); + stateMap.put(Part.WP_RESTOREBUTTON, frameButtonStates); + stateMap.put(Part.WP_CLOSEBUTTON, frameButtonStates); + + // States for Slider (trackbar) + stateMap.put(Part.TKP_TRACK, new State[] { NORMAL }); + stateMap.put(Part.TKP_TRACKVERT, new State[] { NORMAL }); + + State[] sliderThumbStates = + new State[] { NORMAL, HOT, PRESSED, FOCUSED, DISABLED }; + stateMap.put(Part.TKP_THUMB, sliderThumbStates); + stateMap.put(Part.TKP_THUMBBOTTOM, sliderThumbStates); + stateMap.put(Part.TKP_THUMBTOP, sliderThumbStates); + stateMap.put(Part.TKP_THUMBVERT, sliderThumbStates); + stateMap.put(Part.TKP_THUMBRIGHT, sliderThumbStates); + + // States for Tabs + State[] tabStates = new State[] { NORMAL, HOT, SELECTED, DISABLED, FOCUSED }; + stateMap.put(Part.TABP_TABITEM, tabStates); + stateMap.put(Part.TABP_TABITEMLEFTEDGE, tabStates); + stateMap.put(Part.TABP_TABITEMRIGHTEDGE, tabStates); + + + stateMap.put(Part.TP_BUTTON, + new State[] { + NORMAL, HOT, PRESSED, DISABLED, CHECKED, HOTCHECKED + }); + + State[] frameStates = new State[] { ACTIVE, INACTIVE }; + stateMap.put(Part.WP_WINDOW, frameStates); + stateMap.put(Part.WP_FRAMELEFT, frameStates); + stateMap.put(Part.WP_FRAMERIGHT, frameStates); + stateMap.put(Part.WP_FRAMEBOTTOM, frameStates); + + State[] captionStates = new State[] { ACTIVE, INACTIVE, DISABLED }; + stateMap.put(Part.WP_CAPTION, captionStates); + stateMap.put(Part.WP_MINCAPTION, captionStates); + stateMap.put(Part.WP_MAXCAPTION, captionStates); + + stateMap.put(Part.MP_BARBACKGROUND, + new State[] { ACTIVE, INACTIVE }); + stateMap.put(Part.MP_BARITEM, + new State[] { NORMAL, HOT, PUSHED, + DISABLED, DISABLEDHOT, DISABLEDPUSHED }); + stateMap.put(Part.MP_POPUPCHECK, + new State[] { CHECKMARKNORMAL, CHECKMARKDISABLED, + BULLETNORMAL, BULLETDISABLED }); + stateMap.put(Part.MP_POPUPCHECKBACKGROUND, + new State[] { DISABLEDPUSHED, NORMAL, BITMAP }); + stateMap.put(Part.MP_POPUPITEM, + new State[] { NORMAL, HOT, DISABLED, DISABLEDHOT }); + stateMap.put(Part.MP_POPUPSUBMENU, + new State[] { NORMAL, DISABLED }); + + } + + + public static synchronized int getValue(Part part, State state) { + if (stateMap == null) { + initStates(); + } + + Enum[] states = stateMap.get(part); + if (states != null) { + for (int i = 0; i < states.length; i++) { + if (state == states[i]) { + return i + 1; + } + } + } + + if (state == null || state == State.NORMAL) { + return 1; + } + + return 0; + } + + } + + + /** + * An enumeration of the possible component attributes and the + * corresponding value type + */ + public static enum Prop { + COLOR(Color.class, 204), + SIZE(Dimension.class, 207), + + FLATMENUS(Boolean.class, 1001), + + BORDERONLY(Boolean.class, 2203), // only draw the border area of the image + + IMAGECOUNT(Integer.class, 2401), // the number of state images in an imagefile + BORDERSIZE(Integer.class, 2403), // the size of the border line for bgtype=BorderFill + + PROGRESSCHUNKSIZE(Integer.class, 2411), // size of progress control chunks + PROGRESSSPACESIZE(Integer.class, 2412), // size of progress control spaces + + TEXTSHADOWOFFSET(Point.class, 3402), // where char shadows are drawn, relative to orig. chars + + NORMALSIZE(Dimension.class, 3409), // size of dest rect that exactly source + + + SIZINGMARGINS ( Insets.class, 3601), // margins used for 9-grid sizing + CONTENTMARGINS(Insets.class, 3602), // margins that define where content can be placed + CAPTIONMARGINS(Insets.class, 3603), // margins that define where caption text can be placed + + BORDERCOLOR(Color.class, 3801), // color of borders for BorderFill + FILLCOLOR ( Color.class, 3802), // color of bg fill + TEXTCOLOR ( Color.class, 3803), // color text is drawn in + + TEXTSHADOWCOLOR(Color.class, 3818), // color of text shadow + + BGTYPE(Integer.class, 4001), // basic drawing type for each part + + TEXTSHADOWTYPE(Integer.class, 4010), // type of shadow to draw with text + + TRANSITIONDURATIONS(Integer.class, 6000); + + private final Class type; + private final int value; + + private Prop(Class type, int value) { + this.type = type; + this.value = value; + } + + public int getValue() { + return value; + } + + public String toString() { + return name()+"["+type.getName()+"] = "+value; + } + } + + + /** + * An enumeration of attribute values for some Props + */ + public static enum TypeEnum { + BT_IMAGEFILE (Prop.BGTYPE, "imagefile", 0), + BT_BORDERFILL(Prop.BGTYPE, "borderfill", 1), + + TST_NONE(Prop.TEXTSHADOWTYPE, "none", 0), + TST_SINGLE(Prop.TEXTSHADOWTYPE, "single", 1), + TST_CONTINUOUS(Prop.TEXTSHADOWTYPE, "continuous", 2); + + + private TypeEnum(Prop prop, String enumName, int value) { + this.prop = prop; + this.enumName = enumName; + this.value = value; + } + + private final Prop prop; + private final String enumName; + private final int value; + + public String toString() { + return prop+"="+enumName+"="+value; + } + + String getName() { + return enumName; + } + + + static TypeEnum getTypeEnum(Prop prop, int enumval) { + for (TypeEnum e : TypeEnum.values()) { + if (e.prop == prop && e.value == enumval) { + return e; + } + } + return null; + } + } +} +