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