1 /* Copyright (C) 2005-2011 Fabio Riccardi */
2 
3 package com.lightcrafts.ui.operation.zone;
4 
5 import org.jvnet.substance.SubstanceLookAndFeel;
6 
7 import javax.swing.*;
8 import java.awt.*;
9 import java.awt.event.*;
10 import java.net.URL;
11 import java.util.Map;
12 import java.util.WeakHashMap;
13 
14 /** A fixed-height spacer JComponent that is useful between GradientFills.
15   * It can be moved in response to mouse drag events, it has a control to
16   * stick and unstick, and it updates a ZoneModel as it moves.
17   */
18 
19 class Spacer extends JPanel
20     implements ZoneModelListener, FocusListener, MouseListener
21 {
22     final static int SpacerHeight = 18;
23     final static int SpacerOutcrop = 22;
24 
25     private final static Cursor SliderCursor =
26         Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
27 
28     private final static Cursor ClickCursor =
29         Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
30 
31     private ZoneModel model;
32     private int index;
33     private JButton unstickButton;  // If stuck, a button to unstick.
34 
35     // The set of spacers in a ZoneWidget share knobs.
36     static class SpacerHandle {
37         Spacer spacer;
38     }
39     private SpacerHandle knobHandle;
40     private KnobPainter knobPainter;
41 
Spacer( ZoneModel model, int index, SpacerHandle knobHandle )42     Spacer(
43         ZoneModel model, int index, SpacerHandle knobHandle
44     ) {
45         this.model = model;
46         this.index = index;
47         this.knobHandle = knobHandle;
48 
49         knobPainter = new KnobPainter(this);
50 
51         setOpaque(false);
52         setCursor(SliderCursor);
53 
54         initKeyMaps();
55 
56         Icon icon = getIcon("unstick");
57         Icon pressedIcon = getIcon("unstickPressed");
58         Icon highlightIcon = getIcon("unstickHighlight");
59         unstickButton = new JButton(icon);
60         unstickButton.setSize(
61             icon.getIconWidth(), icon.getIconHeight()
62         );
63         unstickButton.setPressedIcon(pressedIcon);
64         unstickButton.setRolloverEnabled(true);
65         unstickButton.setRolloverIcon(highlightIcon);
66         unstickButton.setBorder(null);
67         unstickButton.putClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY, Boolean.TRUE);
68         unstickButton.setBorderPainted(false);
69         unstickButton.setRolloverEnabled(true);
70         unstickButton.setOpaque(false);
71         unstickButton.setCursor(ClickCursor);
72         unstickButton.setFocusable(false);
73         unstickButton.addActionListener(
74             new ActionListener() {
75                 public void actionPerformed(ActionEvent event) {
76                     Spacer.this.model.removePoint(Spacer.this.index);
77                 }
78             }
79         );
80         if (model.containsPoint(index)) {
81             add(unstickButton);
82         }
83         model.addZoneModelListener(this);
84 
85         setFocusable(true);
86         addFocusListener(this);
87         addMouseListener(this);
88     }
89 
getIndex()90     int getIndex() {
91         return index;
92     }
93 
getOutcrop()94     int getOutcrop() {
95         if (isStuck()) {
96             return SpacerOutcrop;
97         }
98         return 0;
99     }
100 
doLayout()101     public void doLayout() {
102         if (isStuck()) {
103             Dimension size = getSize();
104             Dimension unStickSize = unstickButton.getPreferredSize();
105             int x = size.width - unStickSize.width;
106             int y = size.height / 2 - unStickSize.height / 2;
107             unstickButton.setSize(unStickSize);
108             unstickButton.setLocation(x, y);
109         }
110     }
111 
zoneModelBatchStart(ZoneModelEvent event)112     public void zoneModelBatchStart(ZoneModelEvent event) {
113     }
114 
zoneModelChanged(ZoneModelEvent event)115     public void zoneModelChanged(ZoneModelEvent event) {
116         if ((! isStuck()) && model.containsPoint(index)) {
117             add(unstickButton);
118             revalidate();
119             repaint();
120         }
121         else if (isStuck() && ! model.containsPoint(index)) {
122             remove(unstickButton);
123             revalidate();
124             repaint();
125         }
126     }
127 
zoneModelBatchEnd(ZoneModelEvent event)128     public void zoneModelBatchEnd(ZoneModelEvent event) {
129     }
130 
focusGained(FocusEvent e)131     public void focusGained(FocusEvent e) {
132         setFocusedKnob();
133     }
134 
focusLost(FocusEvent e)135     public void focusLost(FocusEvent e) {
136         repaint();
137     }
138 
mouseClicked(MouseEvent e)139     public void mouseClicked(MouseEvent e) {
140     }
141 
mousePressed(MouseEvent e)142     public void mousePressed(MouseEvent e) {
143         requestFocusInWindow();
144     }
145 
mouseReleased(MouseEvent e)146     public void mouseReleased(MouseEvent e) {
147     }
148 
mouseEntered(MouseEvent e)149     public void mouseEntered(MouseEvent e) {
150         if (knobHandle.spacer != null) {
151             knobHandle.spacer.knobPainter.knobOff(false);
152         }
153         knobHandle.spacer = this;
154         knobPainter.knobOn(false);
155     }
156 
mouseExited(MouseEvent e)157     public void mouseExited(MouseEvent e) {
158 //        if (knobHandle.spacer == this) {
159 //            knobPainter.knobOff(false);
160 //        }
161 //        knobHandle.spacer = null;
162     }
163 
paintComponent(Graphics graphics)164     protected void paintComponent(Graphics graphics) {
165         super.paintComponent(graphics);
166 
167         Graphics2D g = (Graphics2D) graphics;
168 
169         // If we are disabled, we don't draw our arrow, focus, and unstick
170         // decorations:
171         if (! isEnabled()) {
172             return;
173         }
174         Color oldColor = g.getColor();
175         RenderingHints oldHints = g.getRenderingHints();
176         g.setColor(Color.black);
177         g.setRenderingHint(
178             RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON
179         );
180         if (isStuck()) {
181             paintHorizontalLine(g);
182         }
183         g.setRenderingHints(oldHints);
184         g.setColor(oldColor);
185 
186         knobPainter.paint(g);
187     }
188 
moveTo(int newY)189     void moveTo(int newY) {
190         Point p = getLocation();
191         setLocation(p.x, newY);
192         try {
193             updateModel();
194         }
195         catch (IllegalArgumentException e) {
196             // Can't move Spacers past each other:
197             setLocation(p);
198         }
199     }
200 
updateModel()201     void updateModel() {
202         double value = ComponentScaler.componentToScale(this, SpacerHeight / 2);
203         model.setPoint(index, value);
204     }
205 
setFocusedKnob()206     void setFocusedKnob() {
207         if (knobHandle.spacer != null) {
208             knobHandle.spacer.knobPainter.knobOff(true);
209         }
210         knobHandle.spacer = this;
211         knobPainter.knobOn(true);
212     }
213 
isStuck()214     private boolean isStuck() {
215         return (unstickButton.getParent() != null);
216     }
217 
paintHorizontalLine(Graphics g)218     private void paintHorizontalLine(Graphics g) {
219         Dimension size = getSize();
220         int y = size.height / 2;
221         g.drawLine(0, y, size.width, y);
222     }
223 
initKeyMaps()224     private void initKeyMaps() {
225         // Down-arrow nudges this Spacer downward:
226         registerKeyboardAction(
227             new AbstractAction() {
228                 public void actionPerformed(ActionEvent event) {
229                     Point p = getLocation();
230                     moveTo(p.y + 1);
231                 }
232             },
233             KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0),
234             WHEN_FOCUSED
235         );
236         // Up-arrow nudges this Spacer upward:
237         registerKeyboardAction(
238             new AbstractAction() {
239                 public void actionPerformed(ActionEvent event) {
240                     Point p = getLocation();
241                     moveTo(p.y - 1);
242                 }
243             },
244             KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0),
245             WHEN_FOCUSED
246         );
247         // Space sticks this Spacer where it is:
248         registerKeyboardAction(
249             new AbstractAction() {
250                 public void actionPerformed(ActionEvent event) {
251                     Point p = getLocation();
252                     moveTo(p.y);
253                 }
254             },
255             KeyStroke.getKeyStroke(KeyEvent.VK_X, 0),
256             WHEN_FOCUSED
257         );
258         // Delete unsticks this Spacer:
259         registerKeyboardAction(
260             new AbstractAction() {
261                 public void actionPerformed(ActionEvent event) {
262                     model.removePoint(index);
263                 }
264             },
265             KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
266             WHEN_FOCUSED
267         );
268         // Backspace is same as Delete:
269         registerKeyboardAction(
270             new AbstractAction() {
271                 public void actionPerformed(ActionEvent event) {
272                     model.removePoint(index);
273                 }
274             },
275             KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0),
276             WHEN_FOCUSED
277         );
278     }
279 
280     // Loading resources over and over again turns out to be really expensive
281     // at document initialization, use a cache.
282     private static Map<String, Icon> IconCache =
283         new WeakHashMap<String, Icon>();
284 
getIcon(String name)285     private static Icon getIcon(String name) {
286         String path = "resources/" + name + ".png";
287         synchronized (IconCache) {
288             Icon theIcon = IconCache.get(path);
289             if (theIcon == null) {
290                 URL url = Spacer.class.getResource(path);
291                 if (url != null) {
292                     theIcon = new ImageIcon(url);
293                     IconCache.put(path, theIcon);
294                 }
295             }
296             return theIcon;
297         }
298     }
299 }
300