1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.apple.laf;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.beans.*;
  31 
  32 import javax.swing.*;
  33 import javax.swing.border.Border;
  34 import javax.swing.event.MouseInputListener;
  35 import javax.swing.plaf.ComponentUI;
  36 import javax.swing.plaf.basic.BasicListUI;
  37 
  38 import apple.laf.JRSUIConstants.*;
  39 
  40 /**
  41  * A Mac L&F implementation of JList
  42  *
  43  * All this does is look for a ThemeBorder and invalidate it when the focus changes
  44  */
  45 public class AquaListUI extends BasicListUI {
  46     public static ComponentUI createUI(final JComponent c) {
  47         return new AquaListUI();
  48     }
  49 
  50     /**
  51      * Creates the focus listener to repaint the focus ring
  52      */
  53     protected FocusListener createFocusListener() {
  54         return new AquaListUI.FocusHandler();
  55     }
  56 
  57     /**
  58      * Creates a delegate that implements MouseInputListener.
  59      */
  60     protected MouseInputListener createMouseInputListener() {
  61         return new AquaListUI.MouseInputHandler();
  62     }
  63 
  64     protected void installKeyboardActions() {
  65         super.installKeyboardActions();
  66         list.getActionMap().put("aquaHome", new AquaHomeEndAction(true));
  67         list.getActionMap().put("aquaEnd", new AquaHomeEndAction(false));
  68     }
  69 
  70     @SuppressWarnings("serial") // Superclass is not serializable across versions
  71     static class AquaHomeEndAction extends AbstractAction {
  72         private boolean fHomeAction = false;
  73 
  74         protected AquaHomeEndAction(final boolean isHomeAction) {
  75             fHomeAction = isHomeAction;
  76         }
  77 
  78         /**
  79          * For a Home action, scrolls to the top. Otherwise, scroll to the end.
  80          */
  81         public void actionPerformed(final ActionEvent e) {
  82             final JList list = (JList)e.getSource();
  83 
  84             if (fHomeAction) {
  85                 list.ensureIndexIsVisible(0);
  86             } else {
  87                 final int size = list.getModel().getSize();
  88                 list.ensureIndexIsVisible(size - 1);
  89             }
  90         }
  91     }
  92 
  93     /**
  94      * This inner class is marked "public" due to a compiler bug. This class should be treated as a
  95      * "protected" inner class. Instantiate it only within subclasses of BasicListUI.
  96      */
  97     class FocusHandler extends BasicListUI.FocusHandler {
  98         public void focusGained(final FocusEvent e) {
  99             super.focusGained(e);
 100             AquaBorder.repaintBorder(getComponent());
 101         }
 102 
 103         public void focusLost(final FocusEvent e) {
 104             super.focusLost(e);
 105             AquaBorder.repaintBorder(getComponent());
 106         }
 107     }
 108 
 109     protected PropertyChangeListener createPropertyChangeListener() {
 110         return new AquaPropertyChangeHandler();
 111     }
 112 
 113     class AquaPropertyChangeHandler extends PropertyChangeHandler {
 114         public void propertyChange(final PropertyChangeEvent e) {
 115             final String prop = e.getPropertyName();
 116             if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) {
 117                 AquaBorder.repaintBorder(getComponent());
 118                 AquaFocusHandler.swapSelectionColors("List", getComponent(), e.getNewValue());
 119             } else {
 120                 super.propertyChange(e);
 121             }
 122         }
 123     }
 124 
 125     // TODO: Using default handler for now, need to handle cmd-key
 126 
 127     // Replace the mouse event with one that returns the cmd-key state when asked
 128     // for the control-key state, which super assumes is what everyone does to discontiguously extend selections
 129     class MouseInputHandler extends BasicListUI.MouseInputHandler {
 130         /*public void mousePressed(final MouseEvent e) {
 131             super.mousePressed(new SelectionMouseEvent(e));
 132         }
 133         public void mouseDragged(final MouseEvent e) {
 134             super.mouseDragged(new SelectionMouseEvent(e));
 135         }*/
 136     }
 137 
 138     JList getComponent() {
 139         return list;
 140     }
 141 
 142     // this is used for blinking combobox popup selections when they are selected
 143     protected void repaintCell(final Object value, final int selectedIndex, final boolean selected) {
 144         final Rectangle rowBounds = getCellBounds(list, selectedIndex, selectedIndex);
 145         if (rowBounds == null) return;
 146 
 147         final ListCellRenderer renderer = list.getCellRenderer();
 148         if (renderer == null) return;
 149 
 150         final Component rendererComponent = renderer.getListCellRendererComponent(list, value, selectedIndex, selected, true);
 151         if (rendererComponent == null) return;
 152 
 153         final AquaComboBoxRenderer aquaRenderer = renderer instanceof AquaComboBoxRenderer ? (AquaComboBoxRenderer)renderer : null;
 154         if (aquaRenderer != null) aquaRenderer.setDrawCheckedItem(false);
 155         rendererPane.paintComponent(list.getGraphics().create(), rendererComponent, list, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height, true);
 156         if (aquaRenderer != null) aquaRenderer.setDrawCheckedItem(true);
 157     }
 158 
 159     /*
 160     Insert note on JIDESoft naughtiness
 161     */
 162     public static Border getSourceListBackgroundPainter() {
 163         final AquaBorder border = new ComponentPainter();
 164         border.painter.state.set(Widget.GRADIENT);
 165         border.painter.state.set(Variant.GRADIENT_SIDE_BAR);
 166         return border;
 167     }
 168 
 169     public static Border getSourceListSelectionBackgroundPainter() {
 170         final AquaBorder border = new ComponentPainter();
 171         border.painter.state.set(Widget.GRADIENT);
 172         border.painter.state.set(Variant.GRADIENT_SIDE_BAR_SELECTION);
 173         return border;
 174     }
 175 
 176     public static Border getSourceListFocusedSelectionBackgroundPainter() {
 177         final AquaBorder border = new ComponentPainter();
 178         border.painter.state.set(Widget.GRADIENT);
 179         border.painter.state.set(Variant.GRADIENT_SIDE_BAR_FOCUSED_SELECTION);
 180         return border;
 181     }
 182 
 183     public static Border getListEvenBackgroundPainter() {
 184         final AquaBorder border = new ComponentPainter();
 185         border.painter.state.set(Widget.GRADIENT);
 186         border.painter.state.set(Variant.GRADIENT_LIST_BACKGROUND_EVEN);
 187         return border;
 188     }
 189 
 190     public static Border getListOddBackgroundPainter() {
 191         final AquaBorder border = new ComponentPainter();
 192         border.painter.state.set(Widget.GRADIENT);
 193         border.painter.state.set(Variant.GRADIENT_LIST_BACKGROUND_ODD);
 194         return border;
 195     }
 196 
 197     static class ComponentPainter extends AquaBorder.Default {
 198         public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
 199             final JComponent jc = c instanceof JComponent ? (JComponent)c : null;
 200             if (jc != null && !AquaFocusHandler.isActive(jc)) {
 201                 painter.state.set(State.INACTIVE);
 202             } else {
 203                 painter.state.set(State.ACTIVE);
 204             }
 205             super.paintBorder(c, g, x, y, w, h);
 206         }
 207     }
 208 }