Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaComboBoxPopup.java
41154 views
1
/*
2
* Copyright (c) 2011, 2017, 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.Component;
29
import java.awt.Dimension;
30
import java.awt.GraphicsConfiguration;
31
import java.awt.GraphicsDevice;
32
import java.awt.GraphicsEnvironment;
33
import java.awt.Insets;
34
import java.awt.Point;
35
import java.awt.Rectangle;
36
import java.awt.Toolkit;
37
import java.awt.event.InputEvent;
38
import java.awt.event.MouseEvent;
39
40
import javax.swing.Box;
41
import javax.swing.JComboBox;
42
import javax.swing.JList;
43
import javax.swing.ListCellRenderer;
44
import javax.swing.SwingUtilities;
45
import javax.swing.plaf.basic.BasicComboPopup;
46
47
import sun.lwawt.macosx.CPlatformWindow;
48
49
@SuppressWarnings("serial") // Superclass is not serializable across versions
50
final class AquaComboBoxPopup extends BasicComboPopup {
51
static final int FOCUS_RING_PAD_LEFT = 6;
52
static final int FOCUS_RING_PAD_RIGHT = 6;
53
static final int FOCUS_RING_PAD_BOTTOM = 5;
54
55
protected Component topStrut;
56
protected Component bottomStrut;
57
protected boolean isPopDown = false;
58
59
public AquaComboBoxPopup(final JComboBox<Object> cBox) {
60
super(cBox);
61
}
62
63
@Override
64
protected void configurePopup() {
65
super.configurePopup();
66
67
setBorderPainted(false);
68
setBorder(null);
69
updateContents(false);
70
71
// TODO: CPlatformWindow?
72
putClientProperty(CPlatformWindow.WINDOW_FADE_OUT, Integer.valueOf(150));
73
}
74
75
public void updateContents(final boolean remove) {
76
// for more background on this issue, see AquaMenuBorder.getBorderInsets()
77
78
isPopDown = isPopdown();
79
if (isPopDown) {
80
if (remove) {
81
if (topStrut != null) {
82
this.remove(topStrut);
83
}
84
if (bottomStrut != null) {
85
this.remove(bottomStrut);
86
}
87
} else {
88
add(scroller);
89
}
90
} else {
91
if (topStrut == null) {
92
topStrut = Box.createVerticalStrut(4);
93
bottomStrut = Box.createVerticalStrut(4);
94
}
95
96
if (remove) remove(scroller);
97
98
this.add(topStrut);
99
this.add(scroller);
100
this.add(bottomStrut);
101
}
102
}
103
104
protected Dimension getBestPopupSizeForRowCount(final int maxRowCount) {
105
final int currentElementCount = comboBox.getModel().getSize();
106
final int rowCount = Math.min(maxRowCount, currentElementCount);
107
108
final Dimension popupSize = new Dimension();
109
final ListCellRenderer<Object> renderer = list.getCellRenderer();
110
111
for (int i = 0; i < rowCount; i++) {
112
final Object value = list.getModel().getElementAt(i);
113
final Component c = renderer.getListCellRendererComponent(list, value, i, false, false);
114
115
final Dimension prefSize = c.getPreferredSize();
116
popupSize.height += prefSize.height;
117
popupSize.width = Math.max(prefSize.width, popupSize.width);
118
}
119
120
popupSize.width += 10;
121
122
return popupSize;
123
}
124
125
protected boolean shouldScroll() {
126
return comboBox.getItemCount() > comboBox.getMaximumRowCount();
127
}
128
129
protected boolean isPopdown() {
130
return shouldScroll() || AquaComboBoxUI.isPopdown(comboBox);
131
}
132
133
@Override
134
public void show() {
135
final int startItemCount = comboBox.getItemCount();
136
137
final Rectangle popupBounds = adjustPopupAndGetBounds();
138
if (popupBounds == null) return; // null means don't show
139
140
comboBox.firePopupMenuWillBecomeVisible();
141
show(comboBox, popupBounds.x, popupBounds.y);
142
143
// hack for <rdar://problem/4905531> JComboBox does not fire popupWillBecomeVisible if item count is 0
144
final int afterShowItemCount = comboBox.getItemCount();
145
if (afterShowItemCount == 0) {
146
hide();
147
return;
148
}
149
150
if (startItemCount != afterShowItemCount) {
151
final Rectangle newBounds = adjustPopupAndGetBounds();
152
list.setSize(newBounds.width, newBounds.height);
153
pack();
154
155
final Point newLoc = comboBox.getLocationOnScreen();
156
setLocation(newLoc.x + newBounds.x, newLoc.y + newBounds.y);
157
}
158
// end hack
159
160
list.requestFocusInWindow();
161
}
162
163
@Override
164
@SuppressWarnings("serial") // anonymous class
165
protected JList<Object> createList() {
166
return new JList<Object>(comboBox.getModel()) {
167
@Override
168
@SuppressWarnings("deprecation")
169
public void processMouseEvent(MouseEvent e) {
170
if (e.isMetaDown()) {
171
e = new MouseEvent((Component) e.getSource(), e.getID(),
172
e.getWhen(),
173
e.getModifiers() ^ InputEvent.META_MASK,
174
e.getX(), e.getY(), e.getXOnScreen(),
175
e.getYOnScreen(), e.getClickCount(),
176
e.isPopupTrigger(), MouseEvent.NOBUTTON);
177
}
178
super.processMouseEvent(e);
179
}
180
};
181
}
182
183
protected Rectangle adjustPopupAndGetBounds() {
184
if (isPopDown != isPopdown()) {
185
updateContents(true);
186
}
187
188
final Dimension popupSize = getBestPopupSizeForRowCount(comboBox.getMaximumRowCount());
189
final Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height);
190
if (popupBounds == null) return null; // returning null means don't show anything
191
192
final Dimension realPopupSize = popupBounds.getSize();
193
scroller.setMaximumSize(realPopupSize);
194
scroller.setPreferredSize(realPopupSize);
195
scroller.setMinimumSize(realPopupSize);
196
list.invalidate();
197
198
final int selectedIndex = comboBox.getSelectedIndex();
199
if (selectedIndex == -1) {
200
list.clearSelection();
201
} else {
202
list.setSelectedIndex(selectedIndex);
203
}
204
list.ensureIndexIsVisible(list.getSelectedIndex());
205
206
return popupBounds;
207
}
208
209
// Get the bounds of the screen where the menu should appear
210
// p is the origin of the combo box in screen bounds
211
Rectangle getBestScreenBounds(final Point p) {
212
//System.err.println("GetBestScreenBounds p: "+ p.x + ", " + p.y);
213
final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
214
final GraphicsDevice[] gs = ge.getScreenDevices();
215
for (final GraphicsDevice gd : gs) {
216
final GraphicsConfiguration[] gc = gd.getConfigurations();
217
for (final GraphicsConfiguration element0 : gc) {
218
final Rectangle gcBounds = element0.getBounds();
219
if (gcBounds.contains(p)) {
220
return getAvailableScreenArea(gcBounds, element0);
221
}
222
}
223
}
224
225
// Hmm. Origin's off screen, but is any part on?
226
final Rectangle comboBoxBounds = comboBox.getBounds();
227
comboBoxBounds.setLocation(p);
228
for (final GraphicsDevice gd : gs) {
229
final GraphicsConfiguration[] gc = gd.getConfigurations();
230
for (final GraphicsConfiguration element0 : gc) {
231
final Rectangle gcBounds = element0.getBounds();
232
if (gcBounds.intersects(comboBoxBounds)) {
233
return getAvailableScreenArea(gcBounds, element0);
234
}
235
}
236
}
237
238
return null;
239
}
240
241
private Rectangle getAvailableScreenArea(Rectangle bounds,
242
GraphicsConfiguration gc) {
243
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
244
return new Rectangle(bounds.x + insets.left, bounds.y + insets.top,
245
bounds.width - insets.left - insets.right,
246
bounds.height - insets.top - insets.bottom);
247
}
248
249
private int getComboBoxEdge(int py, boolean bottom) {
250
int offset = bottom ? 9 : -9;
251
// if py is less than new y we have a clipped combo, so leave it alone.
252
return Math.min((py / 2) + offset, py);
253
}
254
255
@Override
256
protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
257
final int itemCount = comboBox.getModel().getSize();
258
final boolean isPopdown = isPopdown();
259
final boolean isTableCellEditor = AquaComboBoxUI.isTableCellEditor(comboBox);
260
if (isPopdown && !isTableCellEditor) {
261
// place the popup just below the button, which is
262
// near the center of a large combo box
263
py = getComboBoxEdge(py, true);
264
}
265
266
// px & py are relative to the combo box
267
268
// **** Common calculation - applies to the scrolling and menu-style ****
269
final Point p = new Point(0, 0);
270
SwingUtilities.convertPointToScreen(p, comboBox);
271
//System.err.println("First Converting from point to screen: 0,0 is now " + p.x + ", " + p.y);
272
final Rectangle scrBounds = getBestScreenBounds(p);
273
//System.err.println("BestScreenBounds is " + scrBounds);
274
275
// If the combo box is totally off screen, do whatever super does
276
if (scrBounds == null) return super.computePopupBounds(px, py, pw, ph);
277
278
// line up with the bottom of the text field/button (or top, if we have to go above it)
279
// and left edge if left-to-right, right edge if right-to-left
280
final Insets comboBoxInsets = comboBox.getInsets();
281
final Rectangle comboBoxBounds = comboBox.getBounds();
282
283
if (shouldScroll()) {
284
pw += 15;
285
}
286
287
if (isPopdown) {
288
pw += 4;
289
}
290
291
// the popup should be wide enough for the items but not wider than the screen it's on
292
final int minWidth = comboBoxBounds.width - (comboBoxInsets.left + comboBoxInsets.right);
293
pw = Math.max(minWidth, pw);
294
295
final boolean leftToRight = AquaUtils.isLeftToRight(comboBox);
296
if (leftToRight) {
297
px += comboBoxInsets.left;
298
if (!isPopDown) px -= FOCUS_RING_PAD_LEFT;
299
} else {
300
px = comboBoxBounds.width - pw - comboBoxInsets.right;
301
if (!isPopDown) px += FOCUS_RING_PAD_RIGHT;
302
}
303
py -= (comboBoxInsets.bottom); //sja fix was +kInset
304
305
// Make sure it's all on the screen - shift it by the amount it's off
306
p.x += px;
307
p.y += py; // Screen location of px & py
308
if (p.x < scrBounds.x) {
309
px = px + (scrBounds.x - p.x);
310
}
311
if (p.y < scrBounds.y) {
312
py = py + (scrBounds.y - p.y);
313
}
314
315
final Point top = new Point(0, 0);
316
SwingUtilities.convertPointFromScreen(top, comboBox);
317
//System.err.println("Converting from point to screen: 0,0 is now " + top.x + ", " + top.y);
318
319
// Since the popup is at zero in this coord space, the maxWidth == the X coord of the screen right edge
320
// (it might be wider than the screen, if the combo is off the left edge)
321
final int maxWidth = Math.min(scrBounds.width, top.x + scrBounds.x + scrBounds.width) - 2; // subtract some buffer space
322
323
pw = Math.min(maxWidth, pw);
324
if (pw < minWidth) {
325
px -= (minWidth - pw);
326
pw = minWidth;
327
}
328
329
// this is a popup window, and will continue calculations below
330
if (!isPopdown) {
331
// popup windows are slightly inset from the combo end-cap
332
pw -= 6;
333
return computePopupBoundsForMenu(px, py, pw, ph, itemCount, scrBounds);
334
}
335
336
// don't attempt to inset table cell editors
337
if (!isTableCellEditor) {
338
pw -= (FOCUS_RING_PAD_LEFT + FOCUS_RING_PAD_RIGHT);
339
if (leftToRight) {
340
px += FOCUS_RING_PAD_LEFT;
341
}
342
}
343
344
final Rectangle r = new Rectangle(px, py, pw, ph);
345
if (r.y + r.height < top.y + scrBounds.y + scrBounds.height) {
346
return r;
347
}
348
// Check whether it goes below the bottom of the screen, if so flip it
349
int newY = getComboBoxEdge(comboBoxBounds.height, false) - ph - comboBoxInsets.top;
350
if (newY > top.y + scrBounds.y) {
351
return new Rectangle(px, newY, r.width, r.height);
352
} else {
353
// There are no place at top, move popup to the center of the screen
354
r.y = top.y + scrBounds.y + Math.max(0, (scrBounds.height - ph) / 2 );
355
r.height = Math.min(scrBounds.height, ph);
356
}
357
return r;
358
}
359
360
// The one to use when itemCount <= maxRowCount. Size never adjusts for arrows
361
// We want it positioned so the selected item is right above the combo box
362
protected Rectangle computePopupBoundsForMenu(final int px, final int py,
363
final int pw, final int ph,
364
final int itemCount,
365
final Rectangle scrBounds) {
366
//System.err.println("computePopupBoundsForMenu: " + px + "," + py + " " + pw + "," + ph);
367
//System.err.println("itemCount: " +itemCount +" src: "+ scrBounds);
368
int elementSize = 0; //kDefaultItemSize;
369
if (list != null && itemCount > 0) {
370
final Rectangle cellBounds = list.getCellBounds(0, 0);
371
if (cellBounds != null) elementSize = cellBounds.height;
372
}
373
374
int offsetIndex = comboBox.getSelectedIndex();
375
if (offsetIndex < 0) offsetIndex = 0;
376
list.setSelectedIndex(offsetIndex);
377
378
final int selectedLocation = elementSize * offsetIndex;
379
380
final Point top = new Point(0, scrBounds.y);
381
final Point bottom = new Point(0, scrBounds.y + scrBounds.height - 20); // Allow some slack
382
SwingUtilities.convertPointFromScreen(top, comboBox);
383
SwingUtilities.convertPointFromScreen(bottom, comboBox);
384
385
final Rectangle popupBounds = new Rectangle(px, py, pw, ph);// Relative to comboBox
386
387
final int theRest = ph - selectedLocation;
388
389
// If the popup fits on the screen and the selection appears under the mouse w/o scrolling, cool!
390
// If the popup won't fit on the screen, adjust its position but not its size
391
// and rewrite this to support arrows - JLists always move the contents so they all show
392
393
// Test to see if it extends off the screen
394
final boolean extendsOffscreenAtTop = selectedLocation > -top.y;
395
final boolean extendsOffscreenAtBottom = theRest > bottom.y;
396
397
if (extendsOffscreenAtTop) {
398
popupBounds.y = top.y + 1;
399
// Round it so the selection lines up with the combobox
400
popupBounds.y = (popupBounds.y / elementSize) * elementSize;
401
} else if (extendsOffscreenAtBottom) {
402
// Provide blank space at top for off-screen stuff to scroll into
403
popupBounds.y = bottom.y - popupBounds.height; // popupBounds.height has already been adjusted to fit
404
} else { // fits - position it so the selectedLocation is under the mouse
405
popupBounds.y = -selectedLocation;
406
}
407
408
// Center the selected item on the combobox
409
final int height = comboBox.getHeight();
410
final Insets insets = comboBox.getInsets();
411
final int buttonSize = height - (insets.top + insets.bottom);
412
final int diff = (buttonSize - elementSize) / 2 + insets.top;
413
popupBounds.y += diff - FOCUS_RING_PAD_BOTTOM;
414
415
return popupBounds;
416
}
417
}
418
419