OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.ui.component.combo;
15
 
16
import java.awt.Color;
17
import java.awt.Component;
18
import java.awt.Dimension;
19
import java.awt.Font;
20
import java.awt.Point;
21
import java.awt.event.ActionEvent;
22
import java.awt.event.MouseAdapter;
23
import java.awt.event.MouseEvent;
24
import java.awt.event.MouseMotionAdapter;
25
 
26
import javax.swing.Action;
27
import javax.swing.BorderFactory;
28
import javax.swing.BoxLayout;
29
import javax.swing.DefaultListCellRenderer;
83 ilm 30
import javax.swing.Icon;
17 ilm 31
import javax.swing.JLabel;
32
import javax.swing.JList;
33
import javax.swing.JPopupMenu;
34
import javax.swing.JScrollPane;
35
import javax.swing.ListModel;
36
import javax.swing.ListSelectionModel;
37
import javax.swing.ScrollPaneConstants;
38
 
39
public class ISearchableComboPopup<T> extends JPopupMenu {
40
 
41
    private static final int MAXROW = 10;
42
 
80 ilm 43
    static public final void setCurrentValueBG(final JList list, final Component rendererComp) {
44
        rendererComp.setBackground(new Color((list.getBackground().getRGB() + list.getSelectionBackground().getRGB()) / 2));
45
    }
46
 
17 ilm 47
    private final JList list;
48
    private int minWitdh = 150;
28 ilm 49
    private int maxVisibleRows;
17 ilm 50
    private final ISearchableCombo<T> text;
51
 
52
    ISearchableComboPopup(final ListModel listModel, final ISearchableCombo<T> text) {
53
        // Liste de la popup
54
        this.list = new JList(listModel);
55
        this.text = text;
28 ilm 56
        this.setMaxVisibleRows(30);
17 ilm 57
        uiInit();
58
        // Listeners
59
        this.list.addMouseMotionListener(new ListMouseMotionHandler());
60
    }
61
 
83 ilm 62
    final void setMaxVisibleRows(final int maxVisibleRows) {
28 ilm 63
        this.maxVisibleRows = maxVisibleRows;
64
    }
80 ilm 65
 
28 ilm 66
    final int getMaxVisibleRows() {
67
        return this.maxVisibleRows;
68
    }
69
 
17 ilm 70
    private ISearchableCombo<T> getCombo() {
71
        return this.text;
72
    }
73
 
74
    private ListModel getListModel() {
75
        return this.list.getModel();
76
    }
77
 
78
    private void uiInit() {
79
        this.list.setFocusable(false);
80
        this.list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
81
        this.list.addMouseListener(new MouseAdapter() {
83 ilm 82
            @Override
83
            public void mouseReleased(final MouseEvent e) {
17 ilm 84
                validateSelection();
85
            }
86
        });
87
        this.list.setCellRenderer(new DefaultListCellRenderer() {
88
            @SuppressWarnings("unchecked")
89
            @Override
83 ilm 90
            public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {
17 ilm 91
                // MAYBE optimize
92
                final JLabel comp = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
93
                comp.setFont(getCombo().getFont());
26 ilm 94
                final String text;
17 ilm 95
                if (value instanceof Action) {
83 ilm 96
                    final Action val = (Action) value;
25 ilm 97
                    comp.setFont(comp.getFont().deriveFont(Font.ITALIC));
83 ilm 98
                    text = (String) val.getValue(Action.NAME);
99
                    comp.setIcon((Icon) val.getValue(Action.SMALL_ICON));
17 ilm 100
                } else {
101
                    final ISearchableComboItem<T> val = (ISearchableComboItem<T>) value;
26 ilm 102
                    text = val.asString();
17 ilm 103
                    comp.setIcon(getCombo().getIcon(val));
104
                    if (getCombo().isEmptyItem(val)) {
105
                        comp.setFont(comp.getFont().deriveFont(Font.ITALIC));
106
                    }
80 ilm 107
                    // works because DefaultListCellRenderer reset the background (which is not the
108
                    // case for DefaultTableCellRenderer)
109
                    if (!isSelected && getCombo().getSelection() == value)
110
                        setCurrentValueBG(list, comp);
17 ilm 111
                }
26 ilm 112
                // otherwise comp has a height of only 2 (the worst thing is, if index = 0, this is
113
                // the blueprint used with VisibleRowCount to compute the height of the JList)
114
                comp.setText(text.isEmpty() ? " " : text);
17 ilm 115
                return comp;
116
            }
117
        });
118
        // Scroller
83 ilm 119
        final JScrollPane scroller = new JScrollPane(this.list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
17 ilm 120
        scroller.setFocusable(false);
121
        scroller.getVerticalScrollBar().setFocusable(false);
80 ilm 122
        scroller.setBorder(BorderFactory.createEmptyBorder());
17 ilm 123
        // Popup
124
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
125
        setBorderPainted(true);
126
        setBorder(BorderFactory.createLineBorder(Color.black));
127
        setOpaque(true);
128
        add(scroller);
129
 
130
        setFocusable(false);
131
    }
132
 
83 ilm 133
    @Override
17 ilm 134
    public Dimension getPreferredSize() {
83 ilm 135
        final Dimension d = super.getPreferredSize();
17 ilm 136
 
137
        int width = d.width;
138
        if (width > 500)
139
            width = 500;
140
        width = Math.max(width, this.minWitdh) + 2;
141
 
83 ilm 142
        final Dimension newD = new Dimension(width, d.height);
17 ilm 143
        return newD;
144
    }
145
 
83 ilm 146
    public void setMinWith(final int i) {
17 ilm 147
        this.minWitdh = i;
148
    }
149
 
150
    protected class ListMouseMotionHandler extends MouseMotionAdapter {
83 ilm 151
        @Override
152
        public void mouseMoved(final MouseEvent anEvent) {
17 ilm 153
            updateListBoxSelection(anEvent.getPoint());
154
        }
155
    }
156
 
83 ilm 157
    protected void updateListBoxSelection(final Point location) {
17 ilm 158
        this.updateListBoxSelection(location, true);
159
    }
160
 
83 ilm 161
    private void updateListBoxSelection(final Point location, final boolean shouldScroll) {
17 ilm 162
        if (this.list == null)
163
            return;
164
        int index = this.list.locationToIndex(location);
165
        if (index == -1) {
166
            if (location.y < 0)
167
                index = 0;
168
            else
169
                index = this.getListModel().getSize() - 1;
170
        }
171
        if (this.list.getSelectedIndex() != index) {
172
            // MAYBE optimize (already faster than setSelectedIndex())
173
            this.list.getSelectionModel().setSelectionInterval(index, index);
174
            if (shouldScroll)
175
                this.list.ensureIndexIsVisible(index);
176
        }
177
    }
178
 
179
    public void selectNext() {
83 ilm 180
        final int i = this.list.getSelectedIndex() + 1;
17 ilm 181
        if (i < this.getListModel().getSize()) {
182
            this.list.setSelectedIndex(i);
183
            this.list.ensureIndexIsVisible(i);
184
        }
185
    }
186
 
187
    public void selectNextPage() {
83 ilm 188
        final int i = Math.min(MAXROW + Math.max(this.list.getSelectedIndex(), 0), this.getListModel().getSize() - 1);
17 ilm 189
        if (i < this.getListModel().getSize()) {
190
            this.list.setSelectedIndex(i);
191
            this.list.ensureIndexIsVisible(i);
192
        }
193
    }
194
 
195
    public void selectPrevious() {
83 ilm 196
        final int i = this.list.getSelectedIndex() - 1;
17 ilm 197
        if (i >= 0) {
198
            this.list.setSelectedIndex(i);
199
            this.list.ensureIndexIsVisible(i);
200
        } else {
83 ilm 201
            this.close();
17 ilm 202
        }
203
    }
204
 
205
    public void selectPreviousPage() {
83 ilm 206
        final int i = Math.max(0, this.list.getSelectedIndex() - MAXROW);
17 ilm 207
        this.list.setSelectedIndex(i);
208
        this.list.ensureIndexIsVisible(i);
209
    }
210
 
211
    final void validateSelection() {
212
        final Object sel = this.list.getSelectedValue();
213
        // if no selection, don't change the combo
214
        if (sel != null) {
215
            if (sel instanceof ISearchableComboItem) {
25 ilm 216
                // don't call setValue() with sel.getOriginal() to handle list with the same item
217
                // multiple times.
218
                this.getCombo().setValue((ISearchableComboItem<T>) sel);
17 ilm 219
            } else if (sel instanceof Action) {
220
                ((Action) sel).actionPerformed(new ActionEvent(this.getCombo(), ActionEvent.ACTION_PERFORMED, this.getCombo().getName()));
221
            } else
222
                throw new IllegalStateException("unknown selection: " + sel);
83 ilm 223
            this.close();
17 ilm 224
        }
225
    }
226
 
227
    public void open() {
25 ilm 228
        // JList always displays visibleRowCount even when fewer items exists
229
        // so if we put a high number we get a big blank popup
230
        // handle this in open() and not with a SimpleListDataListener since :
231
        // 1. open() is called only once whereas add is called multiple times in
232
        // ISearchableCombo.setMatchingCompletions().
233
        // 2. open() is always called when the list is modified.
234
        final int size = getListModel().getSize();
235
        // rowCount == 0 looks like a bug so show 3 empty rows
28 ilm 236
        final int rowCount = size == 0 ? 3 : Math.min(size, this.maxVisibleRows);
25 ilm 237
        if (this.list.getVisibleRowCount() != rowCount) {
238
            // checking if rowCount changes doesn't work (one reason is probably that we're
239
            // called before Swing and so setVisible displays an empty list)
240
            this.list.setVisibleRowCount(rowCount);
241
            if (this.isShowing()) {
242
                // since "visible row count" is not dynamic
83 ilm 243
                close();
25 ilm 244
            }
245
        }
17 ilm 246
        // si on est pas déjà affiché
247
        // afficher même qd pas d'items : si l'user clique il faut qu'il voit la liste même vide
248
        if (!this.isShowing())
249
            this.show(this.getCombo(), 0, this.getCombo().getBounds().height);
250
        this.list.setSelectedValue(this.getCombo().getSelection(), true);
251
    }
252
 
253
    public void close() {
254
        this.setVisible(false);
255
    }
256
 
257
}