001 /*
002 * $Id: BasicTaskPaneUI.java,v 1.7 2005/11/17 00:40:31 rbair Exp $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021 package org.jdesktop.swingx.plaf.basic;
022
023 import java.awt.Color;
024 import java.awt.Component;
025 import java.awt.Cursor;
026 import java.awt.Dimension;
027 import java.awt.Graphics;
028 import java.awt.Insets;
029 import java.awt.Rectangle;
030 import java.awt.event.ActionEvent;
031 import java.awt.event.FocusEvent;
032 import java.awt.event.FocusListener;
033 import java.awt.event.MouseEvent;
034 import java.beans.PropertyChangeEvent;
035 import java.beans.PropertyChangeListener;
036
037 import javax.swing.AbstractAction;
038 import javax.swing.Action;
039 import javax.swing.ActionMap;
040 import javax.swing.BorderFactory;
041 import javax.swing.Icon;
042 import javax.swing.InputMap;
043 import javax.swing.JComponent;
044 import javax.swing.JLabel;
045 import javax.swing.LookAndFeel;
046 import javax.swing.SwingUtilities;
047 import javax.swing.UIManager;
048 import javax.swing.border.Border;
049 import javax.swing.border.CompoundBorder;
050 import javax.swing.event.MouseInputAdapter;
051 import javax.swing.event.MouseInputListener;
052 import javax.swing.plaf.ActionMapUIResource;
053 import javax.swing.plaf.ComponentUI;
054 import javax.swing.plaf.basic.BasicGraphicsUtils;
055
056 import org.jdesktop.swingx.JXCollapsiblePane;
057 import org.jdesktop.swingx.JXHyperlink;
058 import org.jdesktop.swingx.JXTaskPane;
059 import org.jdesktop.swingx.icon.EmptyIcon;
060 import org.jdesktop.swingx.plaf.TaskPaneUI;
061
062 /**
063 * Base implementation of the <code>JXTaskPane</code> UI.
064 *
065 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
066 */
067 public class BasicTaskPaneUI extends TaskPaneUI {
068
069 private static FocusListener focusListener = new RepaintOnFocus();
070
071 public static ComponentUI createUI(JComponent c) {
072 return new BasicTaskPaneUI();
073 }
074
075 protected static int TITLE_HEIGHT = 25;
076 protected static int ROUND_HEIGHT = 5;
077
078 protected JXTaskPane group;
079
080 protected boolean mouseOver;
081 protected MouseInputListener mouseListener;
082
083 protected PropertyChangeListener propertyListener;
084
085 public void installUI(JComponent c) {
086 super.installUI(c);
087 group = (JXTaskPane)c;
088
089 installDefaults();
090 installListeners();
091 installKeyboardActions();
092 }
093
094 protected void installDefaults() {
095 group.setOpaque(true);
096 group.setBorder(createPaneBorder());
097 ((JComponent)group.getContentPane()).setBorder(createContentPaneBorder());
098
099 LookAndFeel.installColorsAndFont(
100 group,
101 "TaskPane.background",
102 "TaskPane.foreground",
103 "TaskPane.font");
104
105 LookAndFeel.installColorsAndFont(
106 (JComponent)group.getContentPane(),
107 "TaskPane.background",
108 "TaskPane.foreground",
109 "TaskPane.font");
110 }
111
112 protected void installListeners() {
113 mouseListener = createMouseInputListener();
114 group.addMouseMotionListener(mouseListener);
115 group.addMouseListener(mouseListener);
116
117 group.addFocusListener(focusListener);
118 propertyListener = createPropertyListener();
119 group.addPropertyChangeListener(propertyListener);
120 }
121
122 protected void installKeyboardActions() {
123 InputMap inputMap = (InputMap)UIManager.get("TaskPane.focusInputMap");
124 if (inputMap != null) {
125 SwingUtilities.replaceUIInputMap(
126 group,
127 JComponent.WHEN_FOCUSED,
128 inputMap);
129 }
130
131 ActionMap map = getActionMap();
132 if (map != null) {
133 SwingUtilities.replaceUIActionMap(group, map);
134 }
135 }
136
137 ActionMap getActionMap() {
138 ActionMap map = new ActionMapUIResource();
139 map.put("toggleExpanded", new ToggleExpandedAction());
140 return map;
141 }
142
143 public void uninstallUI(JComponent c) {
144 uninstallListeners();
145 super.uninstallUI(c);
146 }
147
148 protected void uninstallListeners() {
149 group.removeMouseListener(mouseListener);
150 group.removeMouseMotionListener(mouseListener);
151 group.removeFocusListener(focusListener);
152 group.removePropertyChangeListener(propertyListener);
153 }
154
155 protected MouseInputListener createMouseInputListener() {
156 return new ToggleListener();
157 }
158
159 protected PropertyChangeListener createPropertyListener() {
160 return new ChangeListener();
161 }
162
163 protected boolean isInBorder(MouseEvent event) {
164 return event.getY() < getTitleHeight();
165 }
166
167 protected final int getTitleHeight() {
168 return TITLE_HEIGHT;
169 }
170
171 protected Border createPaneBorder() {
172 return new PaneBorder();
173 }
174
175 @Override
176 public Dimension getPreferredSize(JComponent c) {
177 Component component = group.getComponent(0);
178 if (!(component instanceof JXCollapsiblePane)) {
179 // something wrong in this JXTaskPane
180 return super.getPreferredSize(c);
181 }
182
183 JXCollapsiblePane collapsible = (JXCollapsiblePane)component;
184 Dimension dim = collapsible.getPreferredSize();
185
186 Border groupBorder = group.getBorder();
187 if (groupBorder instanceof PaneBorder) {
188 Dimension border = ((PaneBorder)groupBorder).getPreferredSize(group);
189 dim.width = Math.max(dim.width, border.width);
190 dim.height += border.height;
191 } else {
192 dim.height += getTitleHeight();
193 }
194
195 return dim;
196 }
197
198 protected Border createContentPaneBorder() {
199 Color borderColor = UIManager.getColor("TaskPane.borderColor");
200 return new CompoundBorder(new ContentPaneBorder(borderColor), BorderFactory
201 .createEmptyBorder(10, 10, 10, 10));
202 }
203
204 public Component createAction(Action action) {
205 JXHyperlink button = new JXHyperlink(action);
206 button.setOpaque(false);
207 button.setBorder(null);
208 button.setBorderPainted(false);
209 button.setFocusPainted(true);
210 button.setForeground(UIManager.getColor("TaskPane.titleForeground"));
211 return button;
212 }
213
214 protected void ensureVisible() {
215 SwingUtilities.invokeLater(new Runnable() {
216 public void run() {
217 group.scrollRectToVisible(
218 new Rectangle(group.getWidth(), group.getHeight()));
219 }
220 });
221 }
222
223 static class RepaintOnFocus implements FocusListener {
224 public void focusGained(FocusEvent e) {
225 e.getComponent().repaint();
226 }
227 public void focusLost(FocusEvent e) {
228 e.getComponent().repaint();
229 }
230 }
231
232 class ChangeListener implements PropertyChangeListener {
233 public void propertyChange(PropertyChangeEvent evt) {
234 // if group is expanded but not animated
235 // or if animated has reached expanded state
236 // scroll to visible if scrollOnExpand is enabled
237 if ((JXTaskPane.EXPANDED_CHANGED_KEY.equals(evt.getPropertyName())
238 && Boolean.TRUE.equals(evt.getNewValue()) && !group.isAnimated())
239 || (JXCollapsiblePane.ANIMATION_STATE_KEY.equals(evt.getPropertyName()) && "expanded"
240 .equals(evt.getNewValue()))) {
241 if (group.isScrollOnExpand()) {
242 ensureVisible();
243 }
244 }
245 }
246 }
247
248 class ToggleListener extends MouseInputAdapter {
249 public void mouseEntered(MouseEvent e) {
250 if (isInBorder(e)) {
251 e.getComponent().setCursor(
252 Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
253 } else {
254 mouseOver = false;
255 group.repaint();
256 }
257 }
258 public void mouseExited(MouseEvent e) {
259 e.getComponent().setCursor(Cursor.getDefaultCursor());
260 mouseOver = false;
261 group.repaint();
262 }
263 public void mouseMoved(MouseEvent e) {
264 if (isInBorder(e)) {
265 e.getComponent().setCursor(
266 Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
267 mouseOver = true;
268 group.repaint();
269 } else {
270 e.getComponent().setCursor(Cursor.getDefaultCursor());
271 mouseOver = false;
272 group.repaint();
273 }
274 }
275 public void mouseReleased(MouseEvent e) {
276 if (isInBorder(e)) {
277 group.setExpanded(!group.isExpanded());
278 }
279 }
280 }
281
282 class ToggleExpandedAction extends AbstractAction {
283 public ToggleExpandedAction() {
284 super("toggleExpanded");
285 }
286 public void actionPerformed(ActionEvent e) {
287 group.setExpanded(!group.isExpanded());
288 }
289 public boolean isEnabled() {
290 return group.isVisible();
291 }
292 }
293
294 protected static class ChevronIcon implements Icon {
295 boolean up = true;
296 public ChevronIcon(boolean up) {
297 this.up = up;
298 }
299 public int getIconHeight() {
300 return 3;
301 }
302 public int getIconWidth() {
303 return 6;
304 }
305 public void paintIcon(Component c, Graphics g, int x, int y) {
306 if (up) {
307 g.drawLine(x + 3, y, x, y + 3);
308 g.drawLine(x + 3, y, x + 6, y + 3);
309 } else {
310 g.drawLine(x, y, x + 3, y + 3);
311 g.drawLine(x + 3, y + 3, x + 6, y);
312 }
313 }
314 }
315
316 /**
317 * The border around the content pane
318 */
319 protected static class ContentPaneBorder implements Border {
320 Color color;
321 public ContentPaneBorder(Color color) {
322 this.color = color;
323 }
324 public Insets getBorderInsets(Component c) {
325 return new Insets(0, 1, 1, 1);
326 }
327 public boolean isBorderOpaque() {
328 return true;
329 }
330 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
331 g.setColor(color);
332 g.drawLine(x, y, x, y + height - 1);
333 g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
334 g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
335 }
336 }
337
338 /**
339 * The border of the taskpane group paints the "text", the "icon", the
340 * "expanded" status and the "special" type.
341 *
342 */
343 protected class PaneBorder implements Border {
344
345 protected Color borderColor;
346 protected Color titleForeground;
347 protected Color specialTitleBackground;
348 protected Color specialTitleForeground;
349 protected Color titleBackgroundGradientStart;
350 protected Color titleBackgroundGradientEnd;
351
352 protected Color titleOver;
353 protected Color specialTitleOver;
354
355 protected JLabel label;
356
357 public PaneBorder() {
358 borderColor = UIManager.getColor("TaskPane.borderColor");
359
360 titleForeground = UIManager.getColor("TaskPane.titleForeground");
361
362 specialTitleBackground = UIManager
363 .getColor("TaskPane.specialTitleBackground");
364 specialTitleForeground = UIManager
365 .getColor("TaskPane.specialTitleForeground");
366
367 titleBackgroundGradientStart = UIManager
368 .getColor("TaskPane.titleBackgroundGradientStart");
369 titleBackgroundGradientEnd = UIManager
370 .getColor("TaskPane.titleBackgroundGradientEnd");
371
372 titleOver = UIManager.getColor("TaskPane.titleOver");
373 if (titleOver == null) {
374 titleOver = specialTitleBackground.brighter();
375 }
376 specialTitleOver = UIManager.getColor("TaskPane.specialTitleOver");
377 if (specialTitleOver == null) {
378 specialTitleOver = specialTitleBackground.brighter();
379 }
380
381 label = new JLabel();
382 label.setOpaque(false);
383 label.setIconTextGap(8);
384 }
385
386 public Insets getBorderInsets(Component c) {
387 return new Insets(getTitleHeight(), 0, 0, 0);
388 }
389
390 public boolean isBorderOpaque() {
391 return true;
392 }
393
394 /**
395 * Calculates the preferred border size, its size so all its content fits.
396 */
397 public Dimension getPreferredSize(JXTaskPane group) {
398 // calculate the title width so it is fully visible
399 // it starts with the title width
400 configureLabel(group);
401 Dimension dim = label.getPreferredSize();
402 // add the title left offset
403 dim.width += 3;
404 // add the controls width
405 dim.width += TITLE_HEIGHT;
406 // and some space between label and controls
407 dim.width += 3;
408
409 dim.height = getTitleHeight();
410 return dim;
411 }
412
413 protected void paintTitleBackground(JXTaskPane group, Graphics g) {
414 if (group.isSpecial()) {
415 g.setColor(specialTitleBackground);
416 } else {
417 g.setColor(titleBackgroundGradientStart);
418 }
419 g.fillRect(0, 0, group.getWidth(), getTitleHeight() - 1);
420 }
421
422 protected void paintTitle(
423 JXTaskPane group,
424 Graphics g,
425 Color textColor,
426 int x,
427 int y,
428 int width,
429 int height) {
430 configureLabel(group);
431 label.setForeground(textColor);
432 g.translate(x, y);
433 label.setBounds(0, 0, width, height);
434 label.paint(g);
435 g.translate(-x, -y);
436 }
437
438 protected void configureLabel(JXTaskPane group) {
439 label.applyComponentOrientation(group.getComponentOrientation());
440 label.setFont(group.getFont());
441 label.setText(group.getTitle());
442 label.setIcon(
443 group.getIcon() == null ? new EmptyIcon() : group.getIcon());
444 }
445
446 protected void paintExpandedControls(JXTaskPane group, Graphics g, int x,
447 int y, int width, int height) {}
448
449 protected Color getPaintColor(JXTaskPane group) {
450 Color paintColor;
451 if (isMouseOverBorder()) {
452 if (mouseOver) {
453 if (group.isSpecial()) {
454 paintColor = specialTitleOver;
455 } else {
456 paintColor = titleOver;
457 }
458 } else {
459 if (group.isSpecial()) {
460 paintColor = specialTitleForeground;
461 } else {
462 paintColor = titleForeground;
463 }
464 }
465 } else {
466 if (group.isSpecial()) {
467 paintColor = specialTitleForeground;
468 } else {
469 paintColor = titleForeground;
470 }
471 }
472 return paintColor;
473 }
474
475 public void paintBorder(
476 Component c,
477 Graphics g,
478 int x,
479 int y,
480 int width,
481 int height) {
482
483 JXTaskPane group = (JXTaskPane)c;
484
485 // calculate position of title and toggle controls
486 int controlWidth = TITLE_HEIGHT - 2 * ROUND_HEIGHT;
487 int controlX = group.getWidth() - TITLE_HEIGHT;
488 int controlY = ROUND_HEIGHT - 1;
489 int titleX = 3;
490 int titleY = 0;
491 int titleWidth = group.getWidth() - getTitleHeight() - 3;
492 int titleHeight = getTitleHeight();
493
494 if (!group.getComponentOrientation().isLeftToRight()) {
495 controlX = group.getWidth() - controlX - controlWidth;
496 titleX = group.getWidth() - titleX - titleWidth;
497 }
498
499 // paint the title background
500 paintTitleBackground(group, g);
501
502 // paint the the toggles
503 paintExpandedControls(group, g, controlX, controlY, controlWidth,
504 controlWidth);
505
506 // paint the title text and icon
507 Color paintColor = getPaintColor(group);
508
509 // focus painted same color as text
510 if (group.hasFocus()) {
511 paintFocus(g,
512 paintColor,
513 3,
514 3,
515 width - 6,
516 getTitleHeight() - 6);
517 }
518
519 paintTitle(
520 group,
521 g,
522 paintColor,
523 titleX,
524 titleY,
525 titleWidth,
526 titleHeight);
527 }
528
529 protected void paintRectAroundControls(JXTaskPane group, Graphics g, int x,
530 int y, int width, int height, Color highColor, Color lowColor) {
531 if (mouseOver) {
532 int x2 = x + width;
533 int y2 = y + height;
534 g.setColor(highColor);
535 g.drawLine(x, y, x2, y);
536 g.drawLine(x, y, x, y2);
537 g.setColor(lowColor);
538 g.drawLine(x2, y, x2, y2);
539 g.drawLine(x, y2, x2, y2);
540 }
541 }
542
543 protected void paintOvalAroundControls(JXTaskPane group, Graphics g, int x,
544 int y, int width, int height) {
545 if (group.isSpecial()) {
546 g.setColor(specialTitleBackground.brighter());
547 g.drawOval(
548 x,
549 y,
550 width,
551 height);
552 } else {
553 g.setColor(titleBackgroundGradientStart);
554 g.fillOval(
555 x,
556 y,
557 width,
558 height);
559
560 g.setColor(titleBackgroundGradientEnd.darker());
561 g.drawOval(
562 x,
563 y,
564 width,
565 width);
566 }
567 }
568
569 protected void paintChevronControls(JXTaskPane group, Graphics g, int x,
570 int y, int width, int height) {
571 ChevronIcon chevron;
572 if (group.isExpanded()) {
573 chevron = new ChevronIcon(true);
574 } else {
575 chevron = new ChevronIcon(false);
576 }
577 int chevronX = x + width / 2 - chevron.getIconWidth() / 2;
578 int chevronY = y + (height / 2 - chevron.getIconHeight());
579 chevron.paintIcon(group, g, chevronX, chevronY);
580 chevron.paintIcon(
581 group,
582 g,
583 chevronX,
584 chevronY + chevron.getIconHeight() + 1);
585 }
586
587 protected void paintFocus(Graphics g, Color paintColor, int x, int y, int width, int height) {
588 g.setColor(paintColor);
589 BasicGraphicsUtils.drawDashedRect(
590 g,
591 x,
592 y,
593 width,
594 height);
595 }
596
597 /**
598 * Default implementation returns false.
599 *
600 * @return true if this border wants to display things differently when the
601 * mouse is over it
602 */
603 protected boolean isMouseOverBorder() {
604 return false;
605 }
606 }
607
608 }