Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/swing/SwingUtilities2.java
38829 views
1
/*
2
* Copyright (c) 2002, 2019, 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 sun.swing;
27
28
import java.security.*;
29
import java.lang.reflect.*;
30
import java.awt.*;
31
import static java.awt.RenderingHints.*;
32
import java.awt.event.*;
33
import java.awt.font.*;
34
import java.awt.geom.*;
35
import java.awt.print.PrinterGraphics;
36
import java.text.BreakIterator;
37
import java.text.CharacterIterator;
38
import java.text.AttributedCharacterIterator;
39
import java.text.AttributedString;
40
41
import javax.swing.*;
42
import javax.swing.event.TreeModelEvent;
43
import javax.swing.text.Highlighter;
44
import javax.swing.text.JTextComponent;
45
import javax.swing.text.DefaultHighlighter;
46
import javax.swing.text.DefaultCaret;
47
import javax.swing.table.TableCellRenderer;
48
import javax.swing.table.TableColumnModel;
49
import javax.swing.tree.TreeModel;
50
import javax.swing.tree.TreePath;
51
52
import sun.swing.PrintColorUIResource;
53
import sun.swing.ImageIconUIResource;
54
import sun.print.ProxyPrintGraphics;
55
import sun.awt.*;
56
import sun.security.action.GetPropertyAction;
57
import sun.security.util.SecurityConstants;
58
import java.io.*;
59
import java.util.*;
60
import sun.font.FontDesignMetrics;
61
import sun.font.FontUtilities;
62
import sun.java2d.SunGraphicsEnvironment;
63
64
import java.util.concurrent.Callable;
65
import java.util.concurrent.Future;
66
import java.util.concurrent.FutureTask;
67
68
/**
69
* A collection of utility methods for Swing.
70
* <p>
71
* <b>WARNING:</b> While this class is public, it should not be treated as
72
* public API and its API may change in incompatable ways between dot dot
73
* releases and even patch releases. You should not rely on this class even
74
* existing.
75
*
76
*/
77
public class SwingUtilities2 {
78
/**
79
* The <code>AppContext</code> key for our one <code>LAFState</code>
80
* instance.
81
*/
82
public static final Object LAF_STATE_KEY =
83
new StringBuffer("LookAndFeel State");
84
85
public static final Object MENU_SELECTION_MANAGER_LISTENER_KEY =
86
new StringBuffer("MenuSelectionManager listener key");
87
88
// Maintain a cache of CACHE_SIZE fonts and the left side bearing
89
// of the characters falling into the range MIN_CHAR_INDEX to
90
// MAX_CHAR_INDEX. The values in fontCache are created as needed.
91
private static LSBCacheEntry[] fontCache;
92
// Windows defines 6 font desktop properties, we will therefore only
93
// cache the metrics for 6 fonts.
94
private static final int CACHE_SIZE = 6;
95
// nextIndex in fontCache to insert a font into.
96
private static int nextIndex;
97
// LSBCacheEntry used to search in fontCache to see if we already
98
// have an entry for a particular font
99
private static LSBCacheEntry searchKey;
100
101
// getLeftSideBearing will consult all characters that fall in the
102
// range MIN_CHAR_INDEX to MAX_CHAR_INDEX.
103
private static final int MIN_CHAR_INDEX = (int)'W';
104
private static final int MAX_CHAR_INDEX = (int)'W' + 1;
105
106
public static final FontRenderContext DEFAULT_FRC =
107
new FontRenderContext(null, false, false);
108
109
/**
110
* A JComponent client property is used to determine text aa settings.
111
* To avoid having this property persist between look and feels changes
112
* the value of the property is set to null in JComponent.setUI
113
*/
114
public static final Object AA_TEXT_PROPERTY_KEY =
115
new StringBuffer("AATextInfoPropertyKey");
116
117
/**
118
* Attribute key for the content elements. If it is set on an element, the
119
* element is considered to be a line break.
120
*/
121
public static final String IMPLIED_CR = "CR";
122
123
/**
124
* Used to tell a text component, being used as an editor for table
125
* or tree, how many clicks it took to start editing.
126
*/
127
private static final StringBuilder SKIP_CLICK_COUNT =
128
new StringBuilder("skipClickCount");
129
130
/* Presently this class assumes default fractional metrics.
131
* This may need to change to emulate future platform L&Fs.
132
*/
133
public static class AATextInfo {
134
135
private static AATextInfo getAATextInfoFromMap(Map hints) {
136
137
Object aaHint = hints.get(KEY_TEXT_ANTIALIASING);
138
Object contHint = hints.get(KEY_TEXT_LCD_CONTRAST);
139
140
if (aaHint == null ||
141
aaHint == VALUE_TEXT_ANTIALIAS_OFF ||
142
aaHint == VALUE_TEXT_ANTIALIAS_DEFAULT) {
143
return null;
144
} else {
145
return new AATextInfo(aaHint, (Integer)contHint);
146
}
147
}
148
149
public static AATextInfo getAATextInfo(boolean lafCondition) {
150
SunToolkit.setAAFontSettingsCondition(lafCondition);
151
Toolkit tk = Toolkit.getDefaultToolkit();
152
Object map = tk.getDesktopProperty(SunToolkit.DESKTOPFONTHINTS);
153
if (map instanceof Map) {
154
return getAATextInfoFromMap((Map)map);
155
} else {
156
return null;
157
}
158
}
159
160
Object aaHint;
161
Integer lcdContrastHint;
162
FontRenderContext frc;
163
164
/* These are rarely constructed objects, and only when a complete
165
* UI is being updated, so the cost of the tests here is minimal
166
* and saves tests elsewhere.
167
* We test that the values are ones we support/expect.
168
*/
169
public AATextInfo(Object aaHint, Integer lcdContrastHint) {
170
if (aaHint == null) {
171
throw new InternalError("null not allowed here");
172
}
173
if (aaHint == VALUE_TEXT_ANTIALIAS_OFF ||
174
aaHint == VALUE_TEXT_ANTIALIAS_DEFAULT) {
175
throw new InternalError("AA must be on");
176
}
177
this.aaHint = aaHint;
178
this.lcdContrastHint = lcdContrastHint;
179
this.frc = new FontRenderContext(null, aaHint,
180
VALUE_FRACTIONALMETRICS_DEFAULT);
181
}
182
}
183
184
/**
185
* Key used in client properties used to indicate that the
186
* <code>ComponentUI</code> of the JComponent instance should be returned.
187
*/
188
public static final Object COMPONENT_UI_PROPERTY_KEY =
189
new StringBuffer("ComponentUIPropertyKey");
190
191
/** Client Property key for the text maximal offsets for BasicMenuItemUI */
192
public static final StringUIClientPropertyKey BASICMENUITEMUI_MAX_TEXT_OFFSET =
193
new StringUIClientPropertyKey ("maxTextOffset");
194
195
// security stuff
196
private static Field inputEvent_CanAccessSystemClipboard_Field = null;
197
private static final String UntrustedClipboardAccess =
198
"UNTRUSTED_CLIPBOARD_ACCESS_KEY";
199
200
//all access to charsBuffer is to be synchronized on charsBufferLock
201
private static final int CHAR_BUFFER_SIZE = 100;
202
private static final Object charsBufferLock = new Object();
203
private static char[] charsBuffer = new char[CHAR_BUFFER_SIZE];
204
205
static {
206
fontCache = new LSBCacheEntry[CACHE_SIZE];
207
}
208
209
/**
210
* Fill the character buffer cache. Return the buffer length.
211
*/
212
private static int syncCharsBuffer(String s) {
213
int length = s.length();
214
if ((charsBuffer == null) || (charsBuffer.length < length)) {
215
charsBuffer = s.toCharArray();
216
} else {
217
s.getChars(0, length, charsBuffer, 0);
218
}
219
return length;
220
}
221
222
/**
223
* checks whether TextLayout is required to handle characters.
224
*
225
* @param text characters to be tested
226
* @param start start
227
* @param limit limit
228
* @return <tt>true</tt> if TextLayout is required
229
* <tt>false</tt> if TextLayout is not required
230
*/
231
public static final boolean isComplexLayout(char[] text, int start, int limit) {
232
return FontUtilities.isComplexText(text, start, limit);
233
}
234
235
//
236
// WARNING WARNING WARNING WARNING WARNING WARNING
237
// Many of the following methods are invoked from older API.
238
// As this older API was not passed a Component, a null Component may
239
// now be passsed in. For example, SwingUtilities.computeStringWidth
240
// is implemented to call SwingUtilities2.stringWidth, the
241
// SwingUtilities variant does not take a JComponent, as such
242
// SwingUtilities2.stringWidth can be passed a null Component.
243
// In other words, if you add new functionality to these methods you
244
// need to gracefully handle null.
245
//
246
247
/**
248
* Returns whether or not text should be drawn antialiased.
249
*
250
* @param c JComponent to test.
251
* @return Whether or not text should be drawn antialiased for the
252
* specified component.
253
*/
254
public static AATextInfo drawTextAntialiased(JComponent c) {
255
if (c != null) {
256
/* a non-null property implies some form of AA requested */
257
return (AATextInfo)c.getClientProperty(AA_TEXT_PROPERTY_KEY);
258
}
259
// No component, assume aa is off
260
return null;
261
}
262
263
/**
264
* Returns the left side bearing of the first character of string. The
265
* left side bearing is calculated from the passed in
266
* FontMetrics. If the passed in String is less than one
267
* character {@code 0} is returned.
268
*
269
* @param c JComponent that will display the string
270
* @param fm FontMetrics used to measure the String width
271
* @param string String to get the left side bearing for.
272
* @throws NullPointerException if {@code string} is {@code null}
273
*
274
* @return the left side bearing of the first character of string
275
* or {@code 0} if the string is empty
276
*/
277
public static int getLeftSideBearing(JComponent c, FontMetrics fm,
278
String string) {
279
if ((string == null) || (string.length() == 0)) {
280
return 0;
281
}
282
return getLeftSideBearing(c, fm, string.charAt(0));
283
}
284
285
/**
286
* Returns the left side bearing of the first character of string. The
287
* left side bearing is calculated from the passed in FontMetrics.
288
*
289
* @param c JComponent that will display the string
290
* @param fm FontMetrics used to measure the String width
291
* @param firstChar Character to get the left side bearing for.
292
*/
293
public static int getLeftSideBearing(JComponent c, FontMetrics fm,
294
char firstChar) {
295
int charIndex = (int) firstChar;
296
if (charIndex < MAX_CHAR_INDEX && charIndex >= MIN_CHAR_INDEX) {
297
byte[] lsbs = null;
298
299
FontRenderContext frc = getFontRenderContext(c, fm);
300
Font font = fm.getFont();
301
synchronized (SwingUtilities2.class) {
302
LSBCacheEntry entry = null;
303
if (searchKey == null) {
304
searchKey = new LSBCacheEntry(frc, font);
305
} else {
306
searchKey.reset(frc, font);
307
}
308
// See if we already have an entry for this pair
309
for (LSBCacheEntry cacheEntry : fontCache) {
310
if (searchKey.equals(cacheEntry)) {
311
entry = cacheEntry;
312
break;
313
}
314
}
315
if (entry == null) {
316
// No entry for this pair, add it.
317
entry = searchKey;
318
fontCache[nextIndex] = searchKey;
319
searchKey = null;
320
nextIndex = (nextIndex + 1) % CACHE_SIZE;
321
}
322
return entry.getLeftSideBearing(firstChar);
323
}
324
}
325
return 0;
326
}
327
328
/**
329
* Returns the FontMetrics for the current Font of the passed
330
* in Graphics. This method is used when a Graphics
331
* is available, typically when painting. If a Graphics is not
332
* available the JComponent method of the same name should be used.
333
* <p>
334
* Callers should pass in a non-null JComponent, the exception
335
* to this is if a JComponent is not readily available at the time of
336
* painting.
337
* <p>
338
* This does not necessarily return the FontMetrics from the
339
* Graphics.
340
*
341
* @param c JComponent requesting FontMetrics, may be null
342
* @param g Graphics Graphics
343
*/
344
public static FontMetrics getFontMetrics(JComponent c, Graphics g) {
345
return getFontMetrics(c, g, g.getFont());
346
}
347
348
349
/**
350
* Returns the FontMetrics for the specified Font.
351
* This method is used when a Graphics is available, typically when
352
* painting. If a Graphics is not available the JComponent method of
353
* the same name should be used.
354
* <p>
355
* Callers should pass in a non-null JComonent, the exception
356
* to this is if a JComponent is not readily available at the time of
357
* painting.
358
* <p>
359
* This does not necessarily return the FontMetrics from the
360
* Graphics.
361
*
362
* @param c JComponent requesting FontMetrics, may be null
363
* @param c Graphics Graphics
364
* @param font Font to get FontMetrics for
365
*/
366
public static FontMetrics getFontMetrics(JComponent c, Graphics g,
367
Font font) {
368
if (c != null) {
369
// Note: We assume that we're using the FontMetrics
370
// from the widget to layout out text, otherwise we can get
371
// mismatches when printing.
372
return c.getFontMetrics(font);
373
}
374
return Toolkit.getDefaultToolkit().getFontMetrics(font);
375
}
376
377
378
/**
379
* Returns the width of the passed in String.
380
* If the passed String is <code>null</code>, returns zero.
381
*
382
* @param c JComponent that will display the string, may be null
383
* @param fm FontMetrics used to measure the String width
384
* @param string String to get the width of
385
*/
386
public static int stringWidth(JComponent c, FontMetrics fm, String string){
387
if (string == null || string.equals("")) {
388
return 0;
389
}
390
boolean needsTextLayout = ((c != null) &&
391
(c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null));
392
if (needsTextLayout) {
393
synchronized(charsBufferLock) {
394
int length = syncCharsBuffer(string);
395
needsTextLayout = isComplexLayout(charsBuffer, 0, length);
396
}
397
}
398
if (needsTextLayout) {
399
TextLayout layout = createTextLayout(c, string,
400
fm.getFont(), fm.getFontRenderContext());
401
return (int) layout.getAdvance();
402
} else {
403
return fm.stringWidth(string);
404
}
405
}
406
407
408
/**
409
* Clips the passed in String to the space provided.
410
*
411
* @param c JComponent that will display the string, may be null
412
* @param fm FontMetrics used to measure the String width
413
* @param string String to display
414
* @param availTextWidth Amount of space that the string can be drawn in
415
* @return Clipped string that can fit in the provided space.
416
*/
417
public static String clipStringIfNecessary(JComponent c, FontMetrics fm,
418
String string,
419
int availTextWidth) {
420
if ((string == null) || (string.equals(""))) {
421
return "";
422
}
423
int textWidth = SwingUtilities2.stringWidth(c, fm, string);
424
if (textWidth > availTextWidth) {
425
return SwingUtilities2.clipString(c, fm, string, availTextWidth);
426
}
427
return string;
428
}
429
430
431
/**
432
* Clips the passed in String to the space provided. NOTE: this assumes
433
* the string does not fit in the available space.
434
*
435
* @param c JComponent that will display the string, may be null
436
* @param fm FontMetrics used to measure the String width
437
* @param string String to display
438
* @param availTextWidth Amount of space that the string can be drawn in
439
* @return Clipped string that can fit in the provided space.
440
*/
441
public static String clipString(JComponent c, FontMetrics fm,
442
String string, int availTextWidth) {
443
// c may be null here.
444
String clipString = "...";
445
availTextWidth -= SwingUtilities2.stringWidth(c, fm, clipString);
446
if (availTextWidth <= 0) {
447
//can not fit any characters
448
return clipString;
449
}
450
451
boolean needsTextLayout;
452
synchronized (charsBufferLock) {
453
int stringLength = syncCharsBuffer(string);
454
needsTextLayout =
455
isComplexLayout(charsBuffer, 0, stringLength);
456
if (!needsTextLayout) {
457
int width = 0;
458
for (int nChars = 0; nChars < stringLength; nChars++) {
459
width += fm.charWidth(charsBuffer[nChars]);
460
if (width > availTextWidth) {
461
string = string.substring(0, nChars);
462
break;
463
}
464
}
465
}
466
}
467
if (needsTextLayout) {
468
AttributedString aString = new AttributedString(string);
469
if (c != null) {
470
aString.addAttribute(TextAttribute.NUMERIC_SHAPING,
471
c.getClientProperty(TextAttribute.NUMERIC_SHAPING));
472
}
473
LineBreakMeasurer measurer = new LineBreakMeasurer(
474
aString.getIterator(), BreakIterator.getCharacterInstance(),
475
getFontRenderContext(c, fm));
476
string = string.substring(0, measurer.nextOffset(availTextWidth));
477
478
}
479
return string + clipString;
480
}
481
482
483
/**
484
* Draws the string at the specified location.
485
*
486
* @param c JComponent that will display the string, may be null
487
* @param g Graphics to draw the text to
488
* @param text String to display
489
* @param x X coordinate to draw the text at
490
* @param y Y coordinate to draw the text at
491
*/
492
public static void drawString(JComponent c, Graphics g, String text,
493
int x, int y) {
494
// c may be null
495
496
// All non-editable widgets that draw strings call into this
497
// methods. By non-editable that means widgets like JLabel, JButton
498
// but NOT JTextComponents.
499
if ( text == null || text.length() <= 0 ) { //no need to paint empty strings
500
return;
501
}
502
if (isPrinting(g)) {
503
Graphics2D g2d = getGraphics2D(g);
504
if (g2d != null) {
505
/* The printed text must scale linearly with the UI.
506
* Calculate the width on screen, obtain a TextLayout with
507
* advances for the printer graphics FRC, and then justify
508
* it to fit in the screen width. This distributes the spacing
509
* more evenly than directly laying out to the screen advances.
510
*/
511
String trimmedText = trimTrailingSpaces(text);
512
if (!trimmedText.isEmpty()) {
513
float screenWidth = (float) g2d.getFont().getStringBounds
514
(trimmedText, getFontRenderContext(c)).getWidth();
515
TextLayout layout = createTextLayout(c, text, g2d.getFont(),
516
g2d.getFontRenderContext());
517
518
// If text fits the screenWidth, then do not need to justify
519
if (SwingUtilities2.stringWidth(c, g2d.getFontMetrics(),
520
trimmedText) > screenWidth) {
521
layout = layout.getJustifiedLayout(screenWidth);
522
}
523
/* Use alternate print color if specified */
524
Color col = g2d.getColor();
525
if (col instanceof PrintColorUIResource) {
526
g2d.setColor(((PrintColorUIResource)col).getPrintColor());
527
}
528
529
layout.draw(g2d, x, y);
530
531
g2d.setColor(col);
532
}
533
534
return;
535
}
536
}
537
538
// If we get here we're not printing
539
if (g instanceof Graphics2D) {
540
AATextInfo info = drawTextAntialiased(c);
541
Graphics2D g2 = (Graphics2D)g;
542
543
boolean needsTextLayout = ((c != null) &&
544
(c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null));
545
546
if (needsTextLayout) {
547
synchronized(charsBufferLock) {
548
int length = syncCharsBuffer(text);
549
needsTextLayout = isComplexLayout(charsBuffer, 0, length);
550
}
551
}
552
553
if (info != null) {
554
Object oldContrast = null;
555
Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING);
556
if (info.aaHint != oldAAValue) {
557
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, info.aaHint);
558
} else {
559
oldAAValue = null;
560
}
561
if (info.lcdContrastHint != null) {
562
oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST);
563
if (info.lcdContrastHint.equals(oldContrast)) {
564
oldContrast = null;
565
} else {
566
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST,
567
info.lcdContrastHint);
568
}
569
}
570
571
if (needsTextLayout) {
572
TextLayout layout = createTextLayout(c, text, g2.getFont(),
573
g2.getFontRenderContext());
574
layout.draw(g2, x, y);
575
} else {
576
g.drawString(text, x, y);
577
}
578
579
if (oldAAValue != null) {
580
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue);
581
}
582
if (oldContrast != null) {
583
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast);
584
}
585
586
return;
587
}
588
589
if (needsTextLayout){
590
TextLayout layout = createTextLayout(c, text, g2.getFont(),
591
g2.getFontRenderContext());
592
layout.draw(g2, x, y);
593
return;
594
}
595
}
596
597
g.drawString(text, x, y);
598
}
599
600
/**
601
* Draws the string at the specified location underlining the specified
602
* character.
603
*
604
* @param c JComponent that will display the string, may be null
605
* @param g Graphics to draw the text to
606
* @param text String to display
607
* @param underlinedIndex Index of a character in the string to underline
608
* @param x X coordinate to draw the text at
609
* @param y Y coordinate to draw the text at
610
*/
611
public static void drawStringUnderlineCharAt(JComponent c,Graphics g,
612
String text, int underlinedIndex, int x,int y) {
613
if (text == null || text.length() <= 0) {
614
return;
615
}
616
SwingUtilities2.drawString(c, g, text, x, y);
617
int textLength = text.length();
618
if (underlinedIndex >= 0 && underlinedIndex < textLength ) {
619
int underlineRectY = y;
620
int underlineRectHeight = 1;
621
int underlineRectX = 0;
622
int underlineRectWidth = 0;
623
boolean isPrinting = isPrinting(g);
624
boolean needsTextLayout = isPrinting;
625
if (!needsTextLayout) {
626
synchronized (charsBufferLock) {
627
syncCharsBuffer(text);
628
needsTextLayout =
629
isComplexLayout(charsBuffer, 0, textLength);
630
}
631
}
632
if (!needsTextLayout) {
633
FontMetrics fm = g.getFontMetrics();
634
underlineRectX = x +
635
SwingUtilities2.stringWidth(c,fm,
636
text.substring(0,underlinedIndex));
637
underlineRectWidth = fm.charWidth(text.
638
charAt(underlinedIndex));
639
} else {
640
Graphics2D g2d = getGraphics2D(g);
641
if (g2d != null) {
642
TextLayout layout =
643
createTextLayout(c, text, g2d.getFont(),
644
g2d.getFontRenderContext());
645
if (isPrinting) {
646
float screenWidth = (float)g2d.getFont().
647
getStringBounds(text, getFontRenderContext(c)).getWidth();
648
// If text fits the screenWidth, then do not need to justify
649
if (SwingUtilities2.stringWidth(c, g2d.getFontMetrics(),
650
text) > screenWidth) {
651
layout = layout.getJustifiedLayout(screenWidth);
652
}
653
}
654
TextHitInfo leading =
655
TextHitInfo.leading(underlinedIndex);
656
TextHitInfo trailing =
657
TextHitInfo.trailing(underlinedIndex);
658
Shape shape =
659
layout.getVisualHighlightShape(leading, trailing);
660
Rectangle rect = shape.getBounds();
661
underlineRectX = x + rect.x;
662
underlineRectWidth = rect.width;
663
}
664
}
665
g.fillRect(underlineRectX, underlineRectY + 1,
666
underlineRectWidth, underlineRectHeight);
667
}
668
}
669
670
671
/**
672
* A variation of locationToIndex() which only returns an index if the
673
* Point is within the actual bounds of a list item (not just in the cell)
674
* and if the JList has the "List.isFileList" client property set.
675
* Otherwise, this method returns -1.
676
* This is used to make WindowsL&F JFileChooser act like native dialogs.
677
*/
678
public static int loc2IndexFileList(JList list, Point point) {
679
int index = list.locationToIndex(point);
680
if (index != -1) {
681
Object bySize = list.getClientProperty("List.isFileList");
682
if (bySize instanceof Boolean && ((Boolean)bySize).booleanValue() &&
683
!pointIsInActualBounds(list, index, point)) {
684
index = -1;
685
}
686
}
687
return index;
688
}
689
690
691
/**
692
* Returns true if the given point is within the actual bounds of the
693
* JList item at index (not just inside the cell).
694
*/
695
private static boolean pointIsInActualBounds(JList list, int index,
696
Point point) {
697
ListCellRenderer renderer = list.getCellRenderer();
698
ListModel dataModel = list.getModel();
699
Object value = dataModel.getElementAt(index);
700
Component item = renderer.getListCellRendererComponent(list,
701
value, index, false, false);
702
Dimension itemSize = item.getPreferredSize();
703
Rectangle cellBounds = list.getCellBounds(index, index);
704
if (!item.getComponentOrientation().isLeftToRight()) {
705
cellBounds.x += (cellBounds.width - itemSize.width);
706
}
707
cellBounds.width = itemSize.width;
708
709
return cellBounds.contains(point);
710
}
711
712
713
/**
714
* Returns true if the given point is outside the preferredSize of the
715
* item at the given row of the table. (Column must be 0).
716
* Does not check the "Table.isFileList" property. That should be checked
717
* before calling this method.
718
* This is used to make WindowsL&F JFileChooser act like native dialogs.
719
*/
720
public static boolean pointOutsidePrefSize(JTable table, int row, int column, Point p) {
721
if (table.convertColumnIndexToModel(column) != 0 || row == -1) {
722
return true;
723
}
724
TableCellRenderer tcr = table.getCellRenderer(row, column);
725
Object value = table.getValueAt(row, column);
726
Component cell = tcr.getTableCellRendererComponent(table, value, false,
727
false, row, column);
728
Dimension itemSize = cell.getPreferredSize();
729
Rectangle cellBounds = table.getCellRect(row, column, false);
730
cellBounds.width = itemSize.width;
731
cellBounds.height = itemSize.height;
732
733
// See if coords are inside
734
// ASSUME: mouse x,y will never be < cell's x,y
735
assert (p.x >= cellBounds.x && p.y >= cellBounds.y);
736
return p.x > cellBounds.x + cellBounds.width ||
737
p.y > cellBounds.y + cellBounds.height;
738
}
739
740
/**
741
* Set the lead and anchor without affecting selection.
742
*/
743
public static void setLeadAnchorWithoutSelection(ListSelectionModel model,
744
int lead, int anchor) {
745
if (anchor == -1) {
746
anchor = lead;
747
}
748
if (lead == -1) {
749
model.setAnchorSelectionIndex(-1);
750
model.setLeadSelectionIndex(-1);
751
} else {
752
if (model.isSelectedIndex(lead)) {
753
model.addSelectionInterval(lead, lead);
754
} else {
755
model.removeSelectionInterval(lead, lead);
756
}
757
model.setAnchorSelectionIndex(anchor);
758
}
759
}
760
761
/**
762
* Ignore mouse events if the component is null, not enabled, the event
763
* is not associated with the left mouse button, or the event has been
764
* consumed.
765
*/
766
public static boolean shouldIgnore(MouseEvent me, JComponent c) {
767
return c == null || !c.isEnabled()
768
|| !SwingUtilities.isLeftMouseButton(me)
769
|| me.isConsumed();
770
}
771
772
/**
773
* Request focus on the given component if it doesn't already have it
774
* and <code>isRequestFocusEnabled()</code> returns true.
775
*/
776
public static void adjustFocus(JComponent c) {
777
if (!c.hasFocus() && c.isRequestFocusEnabled()) {
778
c.requestFocus();
779
}
780
}
781
782
/**
783
* The following draw functions have the same semantic as the
784
* Graphics methods with the same names.
785
*
786
* this is used for printing
787
*/
788
public static int drawChars(JComponent c, Graphics g,
789
char[] data,
790
int offset,
791
int length,
792
int x,
793
int y) {
794
if ( length <= 0 ) { //no need to paint empty strings
795
return x;
796
}
797
int nextX = x + getFontMetrics(c, g).charsWidth(data, offset, length);
798
if (isPrinting(g)) {
799
Graphics2D g2d = getGraphics2D(g);
800
if (g2d != null) {
801
FontRenderContext deviceFontRenderContext = g2d.
802
getFontRenderContext();
803
FontRenderContext frc = getFontRenderContext(c);
804
if (frc != null &&
805
!isFontRenderContextPrintCompatible
806
(deviceFontRenderContext, frc)) {
807
808
String text = new String(data, offset, length);
809
TextLayout layout = new TextLayout(text, g2d.getFont(),
810
deviceFontRenderContext);
811
String trimmedText = trimTrailingSpaces(text);
812
if (!trimmedText.isEmpty()) {
813
float screenWidth = (float)g2d.getFont().
814
getStringBounds(trimmedText, frc).getWidth();
815
// If text fits the screenWidth, then do not need to justify
816
if (SwingUtilities2.stringWidth(c, g2d.getFontMetrics(),
817
trimmedText) > screenWidth) {
818
layout = layout.getJustifiedLayout(screenWidth);
819
}
820
821
/* Use alternate print color if specified */
822
Color col = g2d.getColor();
823
if (col instanceof PrintColorUIResource) {
824
g2d.setColor(((PrintColorUIResource)col).getPrintColor());
825
}
826
827
layout.draw(g2d,x,y);
828
829
g2d.setColor(col);
830
}
831
832
return nextX;
833
}
834
}
835
}
836
// Assume we're not printing if we get here, or that we are invoked
837
// via Swing text printing which is laid out for the printer.
838
AATextInfo info = drawTextAntialiased(c);
839
if (info != null && (g instanceof Graphics2D)) {
840
Graphics2D g2 = (Graphics2D)g;
841
842
Object oldContrast = null;
843
Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING);
844
if (info.aaHint != null && info.aaHint != oldAAValue) {
845
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, info.aaHint);
846
} else {
847
oldAAValue = null;
848
}
849
if (info.lcdContrastHint != null) {
850
oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST);
851
if (info.lcdContrastHint.equals(oldContrast)) {
852
oldContrast = null;
853
} else {
854
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST,
855
info.lcdContrastHint);
856
}
857
}
858
859
g.drawChars(data, offset, length, x, y);
860
861
if (oldAAValue != null) {
862
g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue);
863
}
864
if (oldContrast != null) {
865
g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast);
866
}
867
}
868
else {
869
g.drawChars(data, offset, length, x, y);
870
}
871
return nextX;
872
}
873
874
/*
875
* see documentation for drawChars
876
* returns the advance
877
*/
878
public static float drawString(JComponent c, Graphics g,
879
AttributedCharacterIterator iterator,
880
int x,
881
int y) {
882
883
float retVal;
884
boolean isPrinting = isPrinting(g);
885
Color col = g.getColor();
886
887
if (isPrinting) {
888
/* Use alternate print color if specified */
889
if (col instanceof PrintColorUIResource) {
890
g.setColor(((PrintColorUIResource)col).getPrintColor());
891
}
892
}
893
894
Graphics2D g2d = getGraphics2D(g);
895
if (g2d == null) {
896
g.drawString(iterator,x,y); //for the cases where advance
897
//matters it should not happen
898
retVal = x;
899
900
} else {
901
FontRenderContext frc;
902
if (isPrinting) {
903
frc = getFontRenderContext(c);
904
if (frc.isAntiAliased() || frc.usesFractionalMetrics()) {
905
frc = new FontRenderContext(frc.getTransform(), false, false);
906
}
907
} else if ((frc = getFRCProperty(c)) != null) {
908
/* frc = frc; ! */
909
} else {
910
frc = g2d.getFontRenderContext();
911
}
912
TextLayout layout;
913
if (isPrinting) {
914
FontRenderContext deviceFRC = g2d.getFontRenderContext();
915
if (!isFontRenderContextPrintCompatible(frc, deviceFRC)) {
916
layout = new TextLayout(iterator, deviceFRC);
917
AttributedCharacterIterator trimmedIt =
918
getTrimmedTrailingSpacesIterator(iterator);
919
if (trimmedIt != null) {
920
float screenWidth = new TextLayout(trimmedIt, frc).
921
getAdvance();
922
layout = layout.getJustifiedLayout(screenWidth);
923
}
924
} else {
925
layout = new TextLayout(iterator, frc);
926
}
927
} else {
928
layout = new TextLayout(iterator, frc);
929
}
930
layout.draw(g2d, x, y);
931
retVal = layout.getAdvance();
932
}
933
934
if (isPrinting) {
935
g.setColor(col);
936
}
937
938
return retVal;
939
}
940
941
/**
942
* This method should be used for drawing a borders over a filled rectangle.
943
* Draws vertical line, using the current color, between the points {@code
944
* (x, y1)} and {@code (x, y2)} in graphics context's coordinate system.
945
* Note: it use {@code Graphics.fillRect()} internally.
946
*
947
* @param g Graphics to draw the line to.
948
* @param x the <i>x</i> coordinate.
949
* @param y1 the first point's <i>y</i> coordinate.
950
* @param y2 the second point's <i>y</i> coordinate.
951
*/
952
public static void drawVLine(Graphics g, int x, int y1, int y2) {
953
if (y2 < y1) {
954
final int temp = y2;
955
y2 = y1;
956
y1 = temp;
957
}
958
g.fillRect(x, y1, 1, y2 - y1 + 1);
959
}
960
961
/**
962
* This method should be used for drawing a borders over a filled rectangle.
963
* Draws horizontal line, using the current color, between the points {@code
964
* (x1, y)} and {@code (x2, y)} in graphics context's coordinate system.
965
* Note: it use {@code Graphics.fillRect()} internally.
966
*
967
* @param g Graphics to draw the line to.
968
* @param x1 the first point's <i>x</i> coordinate.
969
* @param x2 the second point's <i>x</i> coordinate.
970
* @param y the <i>y</i> coordinate.
971
*/
972
public static void drawHLine(Graphics g, int x1, int x2, int y) {
973
if (x2 < x1) {
974
final int temp = x2;
975
x2 = x1;
976
x1 = temp;
977
}
978
g.fillRect(x1, y, x2 - x1 + 1, 1);
979
}
980
981
/**
982
* This method should be used for drawing a borders over a filled rectangle.
983
* Draws the outline of the specified rectangle. The left and right edges of
984
* the rectangle are at {@code x} and {@code x + w}. The top and bottom
985
* edges are at {@code y} and {@code y + h}. The rectangle is drawn using
986
* the graphics context's current color. Note: it use {@code
987
* Graphics.fillRect()} internally.
988
*
989
* @param g Graphics to draw the rectangle to.
990
* @param x the <i>x</i> coordinate of the rectangle to be drawn.
991
* @param y the <i>y</i> coordinate of the rectangle to be drawn.
992
* @param w the w of the rectangle to be drawn.
993
* @param h the h of the rectangle to be drawn.
994
* @see SwingUtilities2#drawVLine(java.awt.Graphics, int, int, int)
995
* @see SwingUtilities2#drawHLine(java.awt.Graphics, int, int, int)
996
*/
997
public static void drawRect(Graphics g, int x, int y, int w, int h) {
998
if (w < 0 || h < 0) {
999
return;
1000
}
1001
1002
if (h == 0 || w == 0) {
1003
g.fillRect(x, y, w + 1, h + 1);
1004
} else {
1005
g.fillRect(x, y, w, 1);
1006
g.fillRect(x + w, y, 1, h);
1007
g.fillRect(x + 1, y + h, w, 1);
1008
g.fillRect(x, y + 1, 1, h);
1009
}
1010
}
1011
1012
private static TextLayout createTextLayout(JComponent c, String s,
1013
Font f, FontRenderContext frc) {
1014
Object shaper = (c == null ?
1015
null : c.getClientProperty(TextAttribute.NUMERIC_SHAPING));
1016
if (shaper == null) {
1017
return new TextLayout(s, f, frc);
1018
} else {
1019
Map<TextAttribute, Object> a = new HashMap<TextAttribute, Object>();
1020
a.put(TextAttribute.FONT, f);
1021
a.put(TextAttribute.NUMERIC_SHAPING, shaper);
1022
return new TextLayout(s, a, frc);
1023
}
1024
}
1025
1026
/*
1027
* Checks if two given FontRenderContexts are compatible for printing.
1028
* We can't just use equals as we want to exclude from the comparison :
1029
* + whether AA is set as irrelevant for printing and shouldn't affect
1030
* printed metrics anyway
1031
* + any translation component in the transform of either FRC, as it
1032
* does not affect metrics.
1033
* Compatible means no special handling needed for text painting
1034
*/
1035
private static boolean
1036
isFontRenderContextPrintCompatible(FontRenderContext frc1,
1037
FontRenderContext frc2) {
1038
1039
if (frc1 == frc2) {
1040
return true;
1041
}
1042
1043
if (frc1 == null || frc2 == null) { // not supposed to happen
1044
return false;
1045
}
1046
1047
if (frc1.getFractionalMetricsHint() !=
1048
frc2.getFractionalMetricsHint()) {
1049
return false;
1050
}
1051
1052
/* If both are identity, return true */
1053
if (!frc1.isTransformed() && !frc2.isTransformed()) {
1054
return true;
1055
}
1056
1057
/* That's the end of the cheap tests, need to get and compare
1058
* the transform matrices. We don't care about the translation
1059
* components, so return true if they are otherwise identical.
1060
*/
1061
double[] mat1 = new double[4];
1062
double[] mat2 = new double[4];
1063
frc1.getTransform().getMatrix(mat1);
1064
frc2.getTransform().getMatrix(mat2);
1065
return
1066
mat1[0] == mat2[0] &&
1067
mat1[1] == mat2[1] &&
1068
mat1[2] == mat2[2] &&
1069
mat1[3] == mat2[3];
1070
}
1071
1072
/*
1073
* Tries it best to get Graphics2D out of the given Graphics
1074
* returns null if can not derive it.
1075
*/
1076
public static Graphics2D getGraphics2D(Graphics g) {
1077
if (g instanceof Graphics2D) {
1078
return (Graphics2D) g;
1079
} else if (g instanceof ProxyPrintGraphics) {
1080
return (Graphics2D)(((ProxyPrintGraphics)g).getGraphics());
1081
} else {
1082
return null;
1083
}
1084
}
1085
1086
/*
1087
* Returns FontRenderContext associated with Component.
1088
* FontRenderContext from Component.getFontMetrics is associated
1089
* with the component.
1090
*
1091
* Uses Component.getFontMetrics to get the FontRenderContext from.
1092
* see JComponent.getFontMetrics and TextLayoutStrategy.java
1093
*/
1094
public static FontRenderContext getFontRenderContext(Component c) {
1095
assert c != null;
1096
if (c == null) {
1097
return DEFAULT_FRC;
1098
} else {
1099
return c.getFontMetrics(c.getFont()).getFontRenderContext();
1100
}
1101
}
1102
1103
/**
1104
* A convenience method to get FontRenderContext.
1105
* Returns the FontRenderContext for the passed in FontMetrics or
1106
* for the passed in Component if FontMetrics is null
1107
*/
1108
private static FontRenderContext getFontRenderContext(Component c, FontMetrics fm) {
1109
assert fm != null || c!= null;
1110
return (fm != null) ? fm.getFontRenderContext()
1111
: getFontRenderContext(c);
1112
}
1113
1114
/*
1115
* This method is to be used only for JComponent.getFontMetrics.
1116
* In all other places to get FontMetrics we need to use
1117
* JComponent.getFontMetrics.
1118
*
1119
*/
1120
public static FontMetrics getFontMetrics(JComponent c, Font font) {
1121
FontRenderContext frc = getFRCProperty(c);
1122
if (frc == null) {
1123
frc = DEFAULT_FRC;
1124
}
1125
return FontDesignMetrics.getMetrics(font, frc);
1126
}
1127
1128
1129
/* Get any FontRenderContext associated with a JComponent
1130
* - may return null
1131
*/
1132
private static FontRenderContext getFRCProperty(JComponent c) {
1133
if (c != null) {
1134
AATextInfo info =
1135
(AATextInfo)c.getClientProperty(AA_TEXT_PROPERTY_KEY);
1136
if (info != null) {
1137
return info.frc;
1138
}
1139
}
1140
return null;
1141
}
1142
1143
/*
1144
* returns true if the Graphics is print Graphics
1145
* false otherwise
1146
*/
1147
static boolean isPrinting(Graphics g) {
1148
return (g instanceof PrinterGraphics || g instanceof PrintGraphics);
1149
}
1150
1151
private static String trimTrailingSpaces(String s) {
1152
int i = s.length() - 1;
1153
while(i >= 0 && Character.isWhitespace(s.charAt(i))) {
1154
i--;
1155
}
1156
return s.substring(0, i + 1);
1157
}
1158
1159
private static AttributedCharacterIterator getTrimmedTrailingSpacesIterator
1160
(AttributedCharacterIterator iterator) {
1161
int curIdx = iterator.getIndex();
1162
1163
char c = iterator.last();
1164
while(c != CharacterIterator.DONE && Character.isWhitespace(c)) {
1165
c = iterator.previous();
1166
}
1167
1168
if (c != CharacterIterator.DONE) {
1169
int endIdx = iterator.getIndex();
1170
1171
if (endIdx == iterator.getEndIndex() - 1) {
1172
iterator.setIndex(curIdx);
1173
return iterator;
1174
} else {
1175
AttributedString trimmedText = new AttributedString(iterator,
1176
iterator.getBeginIndex(), endIdx + 1);
1177
return trimmedText.getIterator();
1178
}
1179
} else {
1180
return null;
1181
}
1182
}
1183
1184
/**
1185
* Determines whether the SelectedTextColor should be used for painting text
1186
* foreground for the specified highlight.
1187
*
1188
* Returns true only if the highlight painter for the specified highlight
1189
* is the swing painter (whether inner class of javax.swing.text.DefaultHighlighter
1190
* or com.sun.java.swing.plaf.windows.WindowsTextUI) and its background color
1191
* is null or equals to the selection color of the text component.
1192
*
1193
* This is a hack for fixing both bugs 4761990 and 5003294
1194
*/
1195
public static boolean useSelectedTextColor(Highlighter.Highlight h, JTextComponent c) {
1196
Highlighter.HighlightPainter painter = h.getPainter();
1197
String painterClass = painter.getClass().getName();
1198
if (painterClass.indexOf("javax.swing.text.DefaultHighlighter") != 0 &&
1199
painterClass.indexOf("com.sun.java.swing.plaf.windows.WindowsTextUI") != 0) {
1200
return false;
1201
}
1202
try {
1203
DefaultHighlighter.DefaultHighlightPainter defPainter =
1204
(DefaultHighlighter.DefaultHighlightPainter) painter;
1205
if (defPainter.getColor() != null &&
1206
!defPainter.getColor().equals(c.getSelectionColor())) {
1207
return false;
1208
}
1209
} catch (ClassCastException e) {
1210
return false;
1211
}
1212
return true;
1213
}
1214
1215
/**
1216
* LSBCacheEntry is used to cache the left side bearing (lsb) for
1217
* a particular <code>Font</code> and <code>FontRenderContext</code>.
1218
* This only caches characters that fall in the range
1219
* <code>MIN_CHAR_INDEX</code> to <code>MAX_CHAR_INDEX</code>.
1220
*/
1221
private static class LSBCacheEntry {
1222
// Used to indicate a particular entry in lsb has not been set.
1223
private static final byte UNSET = Byte.MAX_VALUE;
1224
// Used in creating a GlyphVector to get the lsb
1225
private static final char[] oneChar = new char[1];
1226
1227
private byte[] lsbCache;
1228
private Font font;
1229
private FontRenderContext frc;
1230
1231
1232
public LSBCacheEntry(FontRenderContext frc, Font font) {
1233
lsbCache = new byte[MAX_CHAR_INDEX - MIN_CHAR_INDEX];
1234
reset(frc, font);
1235
1236
}
1237
1238
public void reset(FontRenderContext frc, Font font) {
1239
this.font = font;
1240
this.frc = frc;
1241
for (int counter = lsbCache.length - 1; counter >= 0; counter--) {
1242
lsbCache[counter] = UNSET;
1243
}
1244
}
1245
1246
public int getLeftSideBearing(char aChar) {
1247
int index = aChar - MIN_CHAR_INDEX;
1248
assert (index >= 0 && index < (MAX_CHAR_INDEX - MIN_CHAR_INDEX));
1249
byte lsb = lsbCache[index];
1250
if (lsb == UNSET) {
1251
oneChar[0] = aChar;
1252
GlyphVector gv = font.createGlyphVector(frc, oneChar);
1253
lsb = (byte) gv.getGlyphPixelBounds(0, frc, 0f, 0f).x;
1254
if (lsb < 0) {
1255
/* HRGB/HBGR LCD glyph images will always have a pixel
1256
* on the left used in colour fringe reduction.
1257
* Text rendering positions this correctly but here
1258
* we are using the glyph image to adjust that position
1259
* so must account for it.
1260
*/
1261
Object aaHint = frc.getAntiAliasingHint();
1262
if (aaHint == VALUE_TEXT_ANTIALIAS_LCD_HRGB ||
1263
aaHint == VALUE_TEXT_ANTIALIAS_LCD_HBGR) {
1264
lsb++;
1265
}
1266
}
1267
lsbCache[index] = lsb;
1268
}
1269
return lsb;
1270
1271
1272
}
1273
1274
public boolean equals(Object entry) {
1275
if (entry == this) {
1276
return true;
1277
}
1278
if (!(entry instanceof LSBCacheEntry)) {
1279
return false;
1280
}
1281
LSBCacheEntry oEntry = (LSBCacheEntry) entry;
1282
return (font.equals(oEntry.font) &&
1283
frc.equals(oEntry.frc));
1284
}
1285
1286
public int hashCode() {
1287
int result = 17;
1288
if (font != null) {
1289
result = 37 * result + font.hashCode();
1290
}
1291
if (frc != null) {
1292
result = 37 * result + frc.hashCode();
1293
}
1294
return result;
1295
}
1296
}
1297
1298
/*
1299
* here goes the fix for 4856343 [Problem with applet interaction
1300
* with system selection clipboard]
1301
*
1302
* NOTE. In case isTrustedContext() no checking
1303
* are to be performed
1304
*/
1305
1306
/**
1307
* checks the security permissions for accessing system clipboard
1308
*
1309
* for untrusted context (see isTrustedContext) checks the
1310
* permissions for the current event being handled
1311
*
1312
*/
1313
public static boolean canAccessSystemClipboard() {
1314
boolean canAccess = false;
1315
if (!GraphicsEnvironment.isHeadless()) {
1316
SecurityManager sm = System.getSecurityManager();
1317
if (sm == null) {
1318
canAccess = true;
1319
} else {
1320
try {
1321
sm.checkPermission(SecurityConstants.AWT.ACCESS_CLIPBOARD_PERMISSION);
1322
canAccess = true;
1323
} catch (SecurityException e) {
1324
}
1325
if (canAccess && ! isTrustedContext()) {
1326
canAccess = canCurrentEventAccessSystemClipboard(true);
1327
}
1328
}
1329
}
1330
return canAccess;
1331
}
1332
/**
1333
* Returns true if EventQueue.getCurrentEvent() has the permissions to
1334
* access the system clipboard
1335
*/
1336
public static boolean canCurrentEventAccessSystemClipboard() {
1337
return isTrustedContext()
1338
|| canCurrentEventAccessSystemClipboard(false);
1339
}
1340
1341
/**
1342
* Returns true if the given event has permissions to access the
1343
* system clipboard
1344
*
1345
* @param e AWTEvent to check
1346
*/
1347
public static boolean canEventAccessSystemClipboard(AWTEvent e) {
1348
return isTrustedContext()
1349
|| canEventAccessSystemClipboard(e, false);
1350
}
1351
1352
/**
1353
* returns canAccessSystemClipboard field from InputEvent
1354
*
1355
* @param ie InputEvent to get the field from
1356
*/
1357
private static synchronized boolean inputEvent_canAccessSystemClipboard(InputEvent ie) {
1358
if (inputEvent_CanAccessSystemClipboard_Field == null) {
1359
inputEvent_CanAccessSystemClipboard_Field =
1360
AccessController.doPrivileged(
1361
new java.security.PrivilegedAction<Field>() {
1362
public Field run() {
1363
try {
1364
Field field = InputEvent.class.
1365
getDeclaredField("canAccessSystemClipboard");
1366
field.setAccessible(true);
1367
return field;
1368
} catch (SecurityException e) {
1369
} catch (NoSuchFieldException e) {
1370
}
1371
return null;
1372
}
1373
});
1374
}
1375
if (inputEvent_CanAccessSystemClipboard_Field == null) {
1376
return false;
1377
}
1378
boolean ret = false;
1379
try {
1380
ret = inputEvent_CanAccessSystemClipboard_Field.
1381
getBoolean(ie);
1382
} catch(IllegalAccessException e) {
1383
}
1384
return ret;
1385
}
1386
1387
/**
1388
* Returns true if the given event is corrent gesture for
1389
* accessing clipboard
1390
*
1391
* @param ie InputEvent to check
1392
*/
1393
1394
private static boolean isAccessClipboardGesture(InputEvent ie) {
1395
boolean allowedGesture = false;
1396
if (ie instanceof KeyEvent) { //we can validate only keyboard gestures
1397
KeyEvent ke = (KeyEvent)ie;
1398
int keyCode = ke.getKeyCode();
1399
int keyModifiers = ke.getModifiers();
1400
switch(keyCode) {
1401
case KeyEvent.VK_C:
1402
case KeyEvent.VK_V:
1403
case KeyEvent.VK_X:
1404
allowedGesture = (keyModifiers == InputEvent.CTRL_MASK);
1405
break;
1406
case KeyEvent.VK_INSERT:
1407
allowedGesture = (keyModifiers == InputEvent.CTRL_MASK ||
1408
keyModifiers == InputEvent.SHIFT_MASK);
1409
break;
1410
case KeyEvent.VK_COPY:
1411
case KeyEvent.VK_PASTE:
1412
case KeyEvent.VK_CUT:
1413
allowedGesture = true;
1414
break;
1415
case KeyEvent.VK_DELETE:
1416
allowedGesture = ( keyModifiers == InputEvent.SHIFT_MASK);
1417
break;
1418
}
1419
}
1420
return allowedGesture;
1421
}
1422
1423
/**
1424
* Returns true if e has the permissions to
1425
* access the system clipboard and if it is allowed gesture (if
1426
* checkGesture is true)
1427
*
1428
* @param e AWTEvent to check
1429
* @param checkGesture boolean
1430
*/
1431
private static boolean canEventAccessSystemClipboard(AWTEvent e,
1432
boolean checkGesture) {
1433
if (EventQueue.isDispatchThread()) {
1434
/*
1435
* Checking event permissions makes sense only for event
1436
* dispathing thread
1437
*/
1438
if (e instanceof InputEvent
1439
&& (! checkGesture || isAccessClipboardGesture((InputEvent)e))) {
1440
return inputEvent_canAccessSystemClipboard((InputEvent)e);
1441
} else {
1442
return false;
1443
}
1444
} else {
1445
return true;
1446
}
1447
}
1448
1449
/**
1450
* Utility method that throws SecurityException if SecurityManager is set
1451
* and modifiers are not public
1452
*
1453
* @param modifiers a set of modifiers
1454
*/
1455
public static void checkAccess(int modifiers) {
1456
if (System.getSecurityManager() != null
1457
&& !Modifier.isPublic(modifiers)) {
1458
throw new SecurityException("Resource is not accessible");
1459
}
1460
}
1461
1462
/**
1463
* Returns true if EventQueue.getCurrentEvent() has the permissions to
1464
* access the system clipboard and if it is allowed gesture (if
1465
* checkGesture true)
1466
*
1467
* @param checkGesture boolean
1468
*/
1469
private static boolean canCurrentEventAccessSystemClipboard(boolean
1470
checkGesture) {
1471
AWTEvent event = EventQueue.getCurrentEvent();
1472
return canEventAccessSystemClipboard(event, checkGesture);
1473
}
1474
1475
/**
1476
* see RFE 5012841 [Per AppContect security permissions] for the
1477
* details
1478
*
1479
*/
1480
private static boolean isTrustedContext() {
1481
return (System.getSecurityManager() == null)
1482
|| (AppContext.getAppContext().
1483
get(UntrustedClipboardAccess) == null);
1484
}
1485
1486
public static String displayPropertiesToCSS(Font font, Color fg) {
1487
StringBuffer rule = new StringBuffer("body {");
1488
if (font != null) {
1489
rule.append(" font-family: ");
1490
rule.append(font.getFamily());
1491
rule.append(" ; ");
1492
rule.append(" font-size: ");
1493
rule.append(font.getSize());
1494
rule.append("pt ;");
1495
if (font.isBold()) {
1496
rule.append(" font-weight: 700 ; ");
1497
}
1498
if (font.isItalic()) {
1499
rule.append(" font-style: italic ; ");
1500
}
1501
}
1502
if (fg != null) {
1503
rule.append(" color: #");
1504
if (fg.getRed() < 16) {
1505
rule.append('0');
1506
}
1507
rule.append(Integer.toHexString(fg.getRed()));
1508
if (fg.getGreen() < 16) {
1509
rule.append('0');
1510
}
1511
rule.append(Integer.toHexString(fg.getGreen()));
1512
if (fg.getBlue() < 16) {
1513
rule.append('0');
1514
}
1515
rule.append(Integer.toHexString(fg.getBlue()));
1516
rule.append(" ; ");
1517
}
1518
rule.append(" }");
1519
return rule.toString();
1520
}
1521
1522
/**
1523
* Utility method that creates a <code>UIDefaults.LazyValue</code> that
1524
* creates an <code>ImageIcon</code> <code>UIResource</code> for the
1525
* specified image file name. The image is loaded using
1526
* <code>getResourceAsStream</code>, starting with a call to that method
1527
* on the base class parameter. If it cannot be found, searching will
1528
* continue through the base class' inheritance hierarchy, up to and
1529
* including <code>rootClass</code>.
1530
*
1531
* @param baseClass the first class to use in searching for the resource
1532
* @param rootClass an ancestor of <code>baseClass</code> to finish the
1533
* search at
1534
* @param imageFile the name of the file to be found
1535
* @return a lazy value that creates the <code>ImageIcon</code>
1536
* <code>UIResource</code> for the image,
1537
* or null if it cannot be found
1538
*/
1539
public static Object makeIcon(final Class<?> baseClass,
1540
final Class<?> rootClass,
1541
final String imageFile) {
1542
1543
return new UIDefaults.LazyValue() {
1544
public Object createValue(UIDefaults table) {
1545
/* Copy resource into a byte array. This is
1546
* necessary because several browsers consider
1547
* Class.getResource a security risk because it
1548
* can be used to load additional classes.
1549
* Class.getResourceAsStream just returns raw
1550
* bytes, which we can convert to an image.
1551
*/
1552
byte[] buffer =
1553
java.security.AccessController.doPrivileged(
1554
new java.security.PrivilegedAction<byte[]>() {
1555
public byte[] run() {
1556
try {
1557
InputStream resource = null;
1558
Class<?> srchClass = baseClass;
1559
1560
while (srchClass != null) {
1561
resource = srchClass.getResourceAsStream(imageFile);
1562
1563
if (resource != null || srchClass == rootClass) {
1564
break;
1565
}
1566
1567
srchClass = srchClass.getSuperclass();
1568
}
1569
1570
if (resource == null) {
1571
return null;
1572
}
1573
1574
BufferedInputStream in =
1575
new BufferedInputStream(resource);
1576
ByteArrayOutputStream out =
1577
new ByteArrayOutputStream(1024);
1578
byte[] buffer = new byte[1024];
1579
int n;
1580
while ((n = in.read(buffer)) > 0) {
1581
out.write(buffer, 0, n);
1582
}
1583
in.close();
1584
out.flush();
1585
return out.toByteArray();
1586
} catch (IOException ioe) {
1587
System.err.println(ioe.toString());
1588
}
1589
return null;
1590
}
1591
});
1592
1593
if (buffer == null) {
1594
return null;
1595
}
1596
if (buffer.length == 0) {
1597
System.err.println("warning: " + imageFile +
1598
" is zero-length");
1599
return null;
1600
}
1601
1602
return new ImageIconUIResource(buffer);
1603
}
1604
};
1605
}
1606
1607
/* Used to help decide if AA text rendering should be used, so
1608
* this local display test should be additionally qualified
1609
* against whether we have XRender support on both ends of the wire,
1610
* as with that support remote performance may be good enough to turn
1611
* on by default. An additional complication there is XRender does not
1612
* appear capable of performing gamma correction needed for LCD text.
1613
*/
1614
public static boolean isLocalDisplay() {
1615
boolean isLocal;
1616
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
1617
if (ge instanceof SunGraphicsEnvironment) {
1618
isLocal = ((SunGraphicsEnvironment) ge).isDisplayLocal();
1619
} else {
1620
isLocal = true;
1621
}
1622
return isLocal;
1623
}
1624
1625
/**
1626
* Returns an integer from the defaults table. If <code>key</code> does
1627
* not map to a valid <code>Integer</code>, or can not be convered from
1628
* a <code>String</code> to an integer, the value 0 is returned.
1629
*
1630
* @param key an <code>Object</code> specifying the int.
1631
* @return the int
1632
*/
1633
public static int getUIDefaultsInt(Object key) {
1634
return getUIDefaultsInt(key, 0);
1635
}
1636
1637
/**
1638
* Returns an integer from the defaults table that is appropriate
1639
* for the given locale. If <code>key</code> does not map to a valid
1640
* <code>Integer</code>, or can not be convered from a <code>String</code>
1641
* to an integer, the value 0 is returned.
1642
*
1643
* @param key an <code>Object</code> specifying the int. Returned value
1644
* is 0 if <code>key</code> is not available,
1645
* @param l the <code>Locale</code> for which the int is desired
1646
* @return the int
1647
*/
1648
public static int getUIDefaultsInt(Object key, Locale l) {
1649
return getUIDefaultsInt(key, l, 0);
1650
}
1651
1652
/**
1653
* Returns an integer from the defaults table. If <code>key</code> does
1654
* not map to a valid <code>Integer</code>, or can not be convered from
1655
* a <code>String</code> to an integer, <code>default</code> is
1656
* returned.
1657
*
1658
* @param key an <code>Object</code> specifying the int. Returned value
1659
* is 0 if <code>key</code> is not available,
1660
* @param defaultValue Returned value if <code>key</code> is not available,
1661
* or is not an Integer
1662
* @return the int
1663
*/
1664
public static int getUIDefaultsInt(Object key, int defaultValue) {
1665
return getUIDefaultsInt(key, null, defaultValue);
1666
}
1667
1668
/**
1669
* Returns an integer from the defaults table that is appropriate
1670
* for the given locale. If <code>key</code> does not map to a valid
1671
* <code>Integer</code>, or can not be convered from a <code>String</code>
1672
* to an integer, <code>default</code> is returned.
1673
*
1674
* @param key an <code>Object</code> specifying the int. Returned value
1675
* is 0 if <code>key</code> is not available,
1676
* @param l the <code>Locale</code> for which the int is desired
1677
* @param defaultValue Returned value if <code>key</code> is not available,
1678
* or is not an Integer
1679
* @return the int
1680
*/
1681
public static int getUIDefaultsInt(Object key, Locale l, int defaultValue) {
1682
Object value = UIManager.get(key, l);
1683
1684
if (value instanceof Integer) {
1685
return ((Integer)value).intValue();
1686
}
1687
if (value instanceof String) {
1688
try {
1689
return Integer.parseInt((String)value);
1690
} catch (NumberFormatException nfe) {}
1691
}
1692
return defaultValue;
1693
}
1694
1695
// At this point we need this method here. But we assume that there
1696
// will be a common method for this purpose in the future releases.
1697
public static Component compositeRequestFocus(Component component) {
1698
if (component instanceof Container) {
1699
Container container = (Container)component;
1700
if (container.isFocusCycleRoot()) {
1701
FocusTraversalPolicy policy = container.getFocusTraversalPolicy();
1702
Component comp = policy.getDefaultComponent(container);
1703
if (comp!=null) {
1704
comp.requestFocus();
1705
return comp;
1706
}
1707
}
1708
Container rootAncestor = container.getFocusCycleRootAncestor();
1709
if (rootAncestor!=null) {
1710
FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy();
1711
Component comp = policy.getComponentAfter(rootAncestor, container);
1712
1713
if (comp!=null && SwingUtilities.isDescendingFrom(comp, container)) {
1714
comp.requestFocus();
1715
return comp;
1716
}
1717
}
1718
}
1719
if (component.isFocusable()) {
1720
component.requestFocus();
1721
return component;
1722
}
1723
return null;
1724
}
1725
1726
/**
1727
* Change focus to the visible component in {@code JTabbedPane}.
1728
* This is not a general-purpose method and is here only to permit
1729
* sharing code.
1730
*/
1731
public static boolean tabbedPaneChangeFocusTo(Component comp) {
1732
if (comp != null) {
1733
if (comp.isFocusTraversable()) {
1734
SwingUtilities2.compositeRequestFocus(comp);
1735
return true;
1736
} else if (comp instanceof JComponent
1737
&& ((JComponent)comp).requestDefaultFocus()) {
1738
1739
return true;
1740
}
1741
}
1742
1743
return false;
1744
}
1745
1746
/**
1747
* Submits a value-returning task for execution on the EDT and
1748
* returns a Future representing the pending results of the task.
1749
*
1750
* @param task the task to submit
1751
* @return a Future representing pending completion of the task
1752
* @throws NullPointerException if the task is null
1753
*/
1754
public static <V> Future<V> submit(Callable<V> task) {
1755
if (task == null) {
1756
throw new NullPointerException();
1757
}
1758
FutureTask<V> future = new FutureTask<V>(task);
1759
execute(future);
1760
return future;
1761
}
1762
1763
/**
1764
* Submits a Runnable task for execution on the EDT and returns a
1765
* Future representing that task.
1766
*
1767
* @param task the task to submit
1768
* @param result the result to return upon successful completion
1769
* @return a Future representing pending completion of the task,
1770
* and whose <tt>get()</tt> method will return the given
1771
* result value upon completion
1772
* @throws NullPointerException if the task is null
1773
*/
1774
public static <V> Future<V> submit(Runnable task, V result) {
1775
if (task == null) {
1776
throw new NullPointerException();
1777
}
1778
FutureTask<V> future = new FutureTask<V>(task, result);
1779
execute(future);
1780
return future;
1781
}
1782
1783
/**
1784
* Sends a Runnable to the EDT for the execution.
1785
*/
1786
private static void execute(Runnable command) {
1787
SwingUtilities.invokeLater(command);
1788
}
1789
1790
/**
1791
* Sets the {@code SKIP_CLICK_COUNT} client property on the component
1792
* if it is an instance of {@code JTextComponent} with a
1793
* {@code DefaultCaret}. This property, used for text components acting
1794
* as editors in a table or tree, tells {@code DefaultCaret} how many
1795
* clicks to skip before starting selection.
1796
*/
1797
public static void setSkipClickCount(Component comp, int count) {
1798
if (comp instanceof JTextComponent
1799
&& ((JTextComponent) comp).getCaret() instanceof DefaultCaret) {
1800
1801
((JTextComponent) comp).putClientProperty(SKIP_CLICK_COUNT, count);
1802
}
1803
}
1804
1805
/**
1806
* Return the MouseEvent's click count, possibly reduced by the value of
1807
* the component's {@code SKIP_CLICK_COUNT} client property. Clears
1808
* the {@code SKIP_CLICK_COUNT} property if the mouse event's click count
1809
* is 1. In order for clearing of the property to work correctly, there
1810
* must be a mousePressed implementation on the caller with this
1811
* call as the first line.
1812
*/
1813
public static int getAdjustedClickCount(JTextComponent comp, MouseEvent e) {
1814
int cc = e.getClickCount();
1815
1816
if (cc == 1) {
1817
comp.putClientProperty(SKIP_CLICK_COUNT, null);
1818
} else {
1819
Integer sub = (Integer) comp.getClientProperty(SKIP_CLICK_COUNT);
1820
if (sub != null) {
1821
return cc - sub;
1822
}
1823
}
1824
1825
return cc;
1826
}
1827
1828
/**
1829
* Used by the {@code liesIn} method to return which section
1830
* the point lies in.
1831
*
1832
* @see #liesIn
1833
*/
1834
public enum Section {
1835
1836
/** The leading section */
1837
LEADING,
1838
1839
/** The middle section */
1840
MIDDLE,
1841
1842
/** The trailing section */
1843
TRAILING
1844
}
1845
1846
/**
1847
* This method divides a rectangle into two or three sections along
1848
* the specified axis and determines which section the given point
1849
* lies in on that axis; used by drag and drop when calculating drop
1850
* locations.
1851
* <p>
1852
* For two sections, the rectangle is divided equally and the method
1853
* returns whether the point lies in {@code Section.LEADING} or
1854
* {@code Section.TRAILING}. For horizontal divisions, the calculation
1855
* respects component orientation.
1856
* <p>
1857
* For three sections, if the rectangle is greater than or equal to
1858
* 30 pixels in length along the axis, the calculation gives 10 pixels
1859
* to each of the leading and trailing sections and the remainder to the
1860
* middle. For smaller sizes, the rectangle is divided equally into three
1861
* sections.
1862
* <p>
1863
* Note: This method assumes that the point is within the bounds of
1864
* the given rectangle on the specified axis. However, in cases where
1865
* it isn't, the results still have meaning: {@code Section.MIDDLE}
1866
* remains the same, {@code Section.LEADING} indicates that the point
1867
* is in or somewhere before the leading section, and
1868
* {@code Section.TRAILING} indicates that the point is in or somewhere
1869
* after the trailing section.
1870
*
1871
* @param rect the rectangle
1872
* @param p the point the check
1873
* @param horizontal {@code true} to use the horizontal axis,
1874
* or {@code false} for the vertical axis
1875
* @param ltr {@code true} for left to right orientation,
1876
* or {@code false} for right to left orientation;
1877
* only used for horizontal calculations
1878
* @param three {@code true} for three sections,
1879
* or {@code false} for two
1880
*
1881
* @return the {@code Section} where the point lies
1882
*
1883
* @throws NullPointerException if {@code rect} or {@code p} are
1884
* {@code null}
1885
*/
1886
private static Section liesIn(Rectangle rect, Point p, boolean horizontal,
1887
boolean ltr, boolean three) {
1888
1889
/* beginning of the rectangle on the axis */
1890
int p0;
1891
1892
/* point on the axis we're interested in */
1893
int pComp;
1894
1895
/* length of the rectangle on the axis */
1896
int length;
1897
1898
/* value of ltr if horizontal, else true */
1899
boolean forward;
1900
1901
if (horizontal) {
1902
p0 = rect.x;
1903
pComp = p.x;
1904
length = rect.width;
1905
forward = ltr;
1906
} else {
1907
p0 = rect.y;
1908
pComp = p.y;
1909
length = rect.height;
1910
forward = true;
1911
}
1912
1913
if (three) {
1914
int boundary = (length >= 30) ? 10 : length / 3;
1915
1916
if (pComp < p0 + boundary) {
1917
return forward ? Section.LEADING : Section.TRAILING;
1918
} else if (pComp >= p0 + length - boundary) {
1919
return forward ? Section.TRAILING : Section.LEADING;
1920
}
1921
1922
return Section.MIDDLE;
1923
} else {
1924
int middle = p0 + length / 2;
1925
if (forward) {
1926
return pComp >= middle ? Section.TRAILING : Section.LEADING;
1927
} else {
1928
return pComp < middle ? Section.TRAILING : Section.LEADING;
1929
}
1930
}
1931
}
1932
1933
/**
1934
* This method divides a rectangle into two or three sections along
1935
* the horizontal axis and determines which section the given point
1936
* lies in; used by drag and drop when calculating drop locations.
1937
* <p>
1938
* See the documentation for {@link #liesIn} for more information
1939
* on how the section is calculated.
1940
*
1941
* @param rect the rectangle
1942
* @param p the point the check
1943
* @param ltr {@code true} for left to right orientation,
1944
* or {@code false} for right to left orientation
1945
* @param three {@code true} for three sections,
1946
* or {@code false} for two
1947
*
1948
* @return the {@code Section} where the point lies
1949
*
1950
* @throws NullPointerException if {@code rect} or {@code p} are
1951
* {@code null}
1952
*/
1953
public static Section liesInHorizontal(Rectangle rect, Point p,
1954
boolean ltr, boolean three) {
1955
return liesIn(rect, p, true, ltr, three);
1956
}
1957
1958
/**
1959
* This method divides a rectangle into two or three sections along
1960
* the vertical axis and determines which section the given point
1961
* lies in; used by drag and drop when calculating drop locations.
1962
* <p>
1963
* See the documentation for {@link #liesIn} for more information
1964
* on how the section is calculated.
1965
*
1966
* @param rect the rectangle
1967
* @param p the point the check
1968
* @param three {@code true} for three sections,
1969
* or {@code false} for two
1970
*
1971
* @return the {@code Section} where the point lies
1972
*
1973
* @throws NullPointerException if {@code rect} or {@code p} are
1974
* {@code null}
1975
*/
1976
public static Section liesInVertical(Rectangle rect, Point p,
1977
boolean three) {
1978
return liesIn(rect, p, false, false, three);
1979
}
1980
1981
/**
1982
* Maps the index of the column in the view at
1983
* {@code viewColumnIndex} to the index of the column
1984
* in the table model. Returns the index of the corresponding
1985
* column in the model. If {@code viewColumnIndex}
1986
* is less than zero, returns {@code viewColumnIndex}.
1987
*
1988
* @param cm the table model
1989
* @param viewColumnIndex the index of the column in the view
1990
* @return the index of the corresponding column in the model
1991
*
1992
* @see JTable#convertColumnIndexToModel(int)
1993
* @see javax.swing.plaf.basic.BasicTableHeaderUI
1994
*/
1995
public static int convertColumnIndexToModel(TableColumnModel cm,
1996
int viewColumnIndex) {
1997
if (viewColumnIndex < 0) {
1998
return viewColumnIndex;
1999
}
2000
return cm.getColumn(viewColumnIndex).getModelIndex();
2001
}
2002
2003
/**
2004
* Maps the index of the column in the {@code cm} at
2005
* {@code modelColumnIndex} to the index of the column
2006
* in the view. Returns the index of the
2007
* corresponding column in the view; returns {@code -1} if this column
2008
* is not being displayed. If {@code modelColumnIndex} is less than zero,
2009
* returns {@code modelColumnIndex}.
2010
*
2011
* @param cm the table model
2012
* @param modelColumnIndex the index of the column in the model
2013
* @return the index of the corresponding column in the view
2014
*
2015
* @see JTable#convertColumnIndexToView(int)
2016
* @see javax.swing.plaf.basic.BasicTableHeaderUI
2017
*/
2018
public static int convertColumnIndexToView(TableColumnModel cm,
2019
int modelColumnIndex) {
2020
if (modelColumnIndex < 0) {
2021
return modelColumnIndex;
2022
}
2023
for (int column = 0; column < cm.getColumnCount(); column++) {
2024
if (cm.getColumn(column).getModelIndex() == modelColumnIndex) {
2025
return column;
2026
}
2027
}
2028
return -1;
2029
}
2030
2031
public static int getSystemMnemonicKeyMask() {
2032
Toolkit toolkit = Toolkit.getDefaultToolkit();
2033
if (toolkit instanceof SunToolkit) {
2034
return ((SunToolkit) toolkit).getFocusAcceleratorKeyMask();
2035
}
2036
return InputEvent.ALT_MASK;
2037
}
2038
2039
/**
2040
* Returns the {@link TreePath} that identifies the changed nodes.
2041
*
2042
* @param event changes in a tree model
2043
* @param model corresponing tree model
2044
* @return the path to the changed nodes
2045
*/
2046
public static TreePath getTreePath(TreeModelEvent event, TreeModel model) {
2047
TreePath path = event.getTreePath();
2048
if ((path == null) && (model != null)) {
2049
Object root = model.getRoot();
2050
if (root != null) {
2051
path = new TreePath(root);
2052
}
2053
}
2054
return path;
2055
}
2056
2057
/**
2058
* Used to listen to "blit" repaints in RepaintManager.
2059
*/
2060
public interface RepaintListener {
2061
void repaintPerformed(JComponent c, int x, int y, int w, int h);
2062
}
2063
}
2064
2065