]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_4_2-src/tools/misc/src/com/ibm/icu/dev/tool/ime/translit/TransliteratorInputMethod.java
go
[Dictionary.git] / jars / icu4j-4_4_2-src / tools / misc / src / com / ibm / icu / dev / tool / ime / translit / TransliteratorInputMethod.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2004-2010, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 \r
8 package com.ibm.icu.dev.tool.ime.translit;\r
9 \r
10 import java.awt.AWTEvent;\r
11 import java.awt.Color;\r
12 import java.awt.Component;\r
13 import java.awt.Dimension;\r
14 import java.awt.Point;\r
15 import java.awt.Rectangle;\r
16 import java.awt.Toolkit;\r
17 import java.awt.Window;\r
18 import java.awt.event.ActionEvent;\r
19 import java.awt.event.ActionListener;\r
20 import java.awt.event.InputEvent;\r
21 import java.awt.event.InputMethodEvent;\r
22 import java.awt.event.KeyEvent;\r
23 import java.awt.event.MouseEvent;\r
24 import java.awt.font.TextAttribute;\r
25 import java.awt.font.TextHitInfo;\r
26 import java.awt.im.InputMethodHighlight;\r
27 import java.awt.im.spi.InputMethod;\r
28 import java.awt.im.spi.InputMethodContext;\r
29 import java.text.AttributedString;\r
30 import java.text.Collator;\r
31 import java.util.Comparator;\r
32 import java.util.Enumeration;\r
33 import java.util.Locale;\r
34 import java.util.MissingResourceException;\r
35 import java.util.ResourceBundle;\r
36 import java.util.TreeSet;\r
37 \r
38 import javax.swing.JComboBox;\r
39 import javax.swing.JLabel;\r
40 import javax.swing.JList;\r
41 import javax.swing.ListCellRenderer;\r
42 \r
43 import com.ibm.icu.impl.Utility;\r
44 import com.ibm.icu.lang.UCharacter;\r
45 import com.ibm.icu.text.ReplaceableString;\r
46 import com.ibm.icu.text.Transliterator;\r
47 \r
48 public class TransliteratorInputMethod implements InputMethod {\r
49 \r
50     private static boolean usesAttachedIME() {\r
51         // we're in the ext directory so permissions are not an issue\r
52         String os = System.getProperty("os.name");\r
53         if (os != null) {\r
54             return os.indexOf("Windows") == -1;\r
55         }\r
56         return false;\r
57     }\r
58 \r
59     // true if Solaris style; false if PC style, assume Apple uses PC style for now\r
60     private static final boolean attachedStatusWindow = usesAttachedIME();\r
61 \r
62     // the shared status window\r
63     private static Window statusWindow;\r
64 \r
65     // current or last owner\r
66     private static TransliteratorInputMethod statusWindowOwner;\r
67 \r
68     // cache location limits for attached\r
69     private static Rectangle attachedLimits;\r
70 \r
71     // convenience of access, to reflect the current state\r
72     private static JComboBox choices;\r
73 \r
74     //\r
75     // per-instance state\r
76     //\r
77 \r
78     // if we're attached, the status window follows the client window\r
79     private Point attachedLocation;\r
80 \r
81     private static int gid;\r
82 \r
83     private int id = gid++;\r
84 \r
85     InputMethodContext imc;\r
86     private boolean enabled = true;\r
87 \r
88     private int selectedIndex = -1; // index in JComboBox corresponding to our transliterator\r
89     private Transliterator transliterator;\r
90     private int desiredContext;\r
91     private StringBuffer buffer;\r
92     private ReplaceableString replaceableText;\r
93     private Transliterator.Position index;\r
94 \r
95     // debugging\r
96     private static boolean TRACE_EVENT = false;\r
97     private static boolean TRACE_MESSAGES = false;\r
98     private static boolean TRACE_BUFFER = false;\r
99 \r
100     public TransliteratorInputMethod() {\r
101         if (TRACE_MESSAGES)\r
102             dumpStatus("<constructor>");\r
103 \r
104         buffer = new StringBuffer();\r
105         replaceableText = new ReplaceableString(buffer);\r
106         index = new Transliterator.Position();\r
107     }\r
108 \r
109     public void dumpStatus(String msg) {\r
110         System.out.println("(" + this + ") " + msg);\r
111     }\r
112 \r
113     public void setInputMethodContext(InputMethodContext context) {\r
114         initStatusWindow(context);\r
115 \r
116         imc = context;\r
117         imc.enableClientWindowNotification(this, attachedStatusWindow);\r
118     }\r
119 \r
120     private static void initStatusWindow(InputMethodContext context) {\r
121         if (statusWindow == null) {\r
122             String title;\r
123             try {\r
124                 ResourceBundle rb = ResourceBundle\r
125                         .getBundle("com.ibm.icu.dev.tool.ime.translit.Transliterator");\r
126                 title = rb.getString("title");\r
127             } catch (MissingResourceException m) {\r
128                 System.out.println("Transliterator resources missing: " + m);\r
129                 title = "Transliterator Input Method";\r
130             }\r
131 \r
132             Window sw = context.createInputMethodWindow(title, false);\r
133 \r
134             // get all the ICU Transliterators\r
135             Enumeration en = Transliterator.getAvailableIDs();\r
136             TreeSet types = new TreeSet(new LabelComparator());\r
137 \r
138             while (en.hasMoreElements()) {\r
139                 String id = (String) en.nextElement();\r
140                 String name = Transliterator.getDisplayName(id);\r
141                 JLabel label = new JLabel(name);\r
142                 label.setName(id);\r
143                 types.add(label);\r
144             }\r
145 \r
146             // add the transliterators to the combo box\r
147 \r
148             choices = new JComboBox(types.toArray());\r
149 \r
150             choices.setEditable(false);\r
151             choices.setSelectedIndex(0);\r
152             choices.setRenderer(new NameRenderer());\r
153             choices.setActionCommand("transliterator");\r
154 \r
155             choices.addActionListener(new ActionListener() {\r
156                 public void actionPerformed(ActionEvent e) {\r
157                     if (statusWindowOwner != null) {\r
158                         statusWindowOwner.statusWindowAction(e);\r
159                     }\r
160                 }\r
161             });\r
162 \r
163             sw.add(choices);\r
164             sw.pack();\r
165 \r
166             Dimension sd = Toolkit.getDefaultToolkit().getScreenSize();\r
167             Dimension wd = sw.getSize();\r
168             if (attachedStatusWindow) {\r
169                 attachedLimits = new Rectangle(0, 0, sd.width - wd.width,\r
170                         sd.height - wd.height);\r
171             } else {\r
172                 sw.setLocation(sd.width - wd.width, sd.height - wd.height - 25);\r
173             }\r
174 \r
175             synchronized (TransliteratorInputMethod.class) {\r
176                 if (statusWindow == null) {\r
177                     statusWindow = sw;\r
178                 }\r
179             }\r
180         }\r
181     }\r
182 \r
183     private void statusWindowAction(ActionEvent e) {\r
184         if (TRACE_MESSAGES)\r
185             dumpStatus(">>status window action");\r
186         JComboBox cb = (JComboBox) e.getSource();\r
187         int si = cb.getSelectedIndex();\r
188         if (si != selectedIndex) { // otherwise, we don't need to change\r
189             if (TRACE_MESSAGES)\r
190                 dumpStatus("status window action oldIndex: " + selectedIndex\r
191                         + " newIndex: " + si);\r
192 \r
193             selectedIndex = si;\r
194 \r
195             JLabel item = (JLabel) cb.getSelectedItem();\r
196 \r
197             // construct the actual transliterator\r
198             // commit any text that may be present first\r
199             commitAll();\r
200 \r
201             transliterator = Transliterator.getInstance(item.getName());\r
202             desiredContext = transliterator.getMaximumContextLength();\r
203 \r
204             reset();\r
205         }\r
206         if (TRACE_MESSAGES)\r
207             dumpStatus("<<status window action");\r
208     }\r
209 \r
210     // java has no pin to rectangle function?\r
211     private static void pin(Point p, Rectangle r) {\r
212         if (p.x < r.x) {\r
213             p.x = r.x;\r
214         } else if (p.x > r.x + r.width) {\r
215             p.x = r.x + r.width;\r
216         }\r
217         if (p.y < r.y) {\r
218             p.y = r.y;\r
219         } else if (p.y > r.y + r.height) {\r
220             p.y = r.y + r.height;\r
221         }\r
222     }\r
223 \r
224     public void notifyClientWindowChange(Rectangle location) {\r
225         if (TRACE_MESSAGES)\r
226             dumpStatus(">>notify client window change: " + location);\r
227         synchronized (TransliteratorInputMethod.class) {\r
228             if (statusWindowOwner == this) {\r
229                 if (location == null) {\r
230                     statusWindow.setVisible(false);\r
231                 } else {\r
232                     attachedLocation = new Point(location.x, location.y\r
233                             + location.height);\r
234                     pin(attachedLocation, attachedLimits);\r
235                     statusWindow.setLocation(attachedLocation);\r
236                     statusWindow.setVisible(true);\r
237                 }\r
238             }\r
239         }\r
240         if (TRACE_MESSAGES)\r
241             dumpStatus("<<notify client window change: " + location);\r
242     }\r
243 \r
244     public void activate() {\r
245         if (TRACE_MESSAGES)\r
246             dumpStatus(">>activate");\r
247 \r
248         synchronized (TransliteratorInputMethod.class) {\r
249             if (statusWindowOwner != this) {\r
250                 if (TRACE_MESSAGES)\r
251                     dumpStatus("setStatusWindowOwner from: " + statusWindowOwner + " to: " + this);\r
252 \r
253                 statusWindowOwner = this;\r
254                 // will be null before first change notification\r
255                 if (attachedStatusWindow && attachedLocation != null) {\r
256                     statusWindow.setLocation(attachedLocation);\r
257                 }\r
258                 choices.setSelectedIndex(selectedIndex == -1 ? choices\r
259                         .getSelectedIndex() : selectedIndex);\r
260             }\r
261 \r
262             choices.setForeground(Color.BLACK);\r
263             statusWindow.setVisible(true);\r
264         }\r
265         if (TRACE_MESSAGES)\r
266             dumpStatus("<<activate");\r
267     }\r
268     \r
269     public void deactivate(boolean isTemporary) {\r
270         if (TRACE_MESSAGES)\r
271             dumpStatus(">>deactivate" + (isTemporary ? " (temporary)" : ""));\r
272         if (!isTemporary) {\r
273             synchronized (TransliteratorInputMethod.class) {\r
274                 choices.setForeground(Color.LIGHT_GRAY);\r
275             }\r
276         }\r
277         if (TRACE_MESSAGES)\r
278             dumpStatus("<<deactivate" + (isTemporary ? " (temporary)" : ""));\r
279     }\r
280     \r
281     public void hideWindows() {\r
282         if (TRACE_MESSAGES)\r
283             dumpStatus(">>hideWindows");\r
284         synchronized (TransliteratorInputMethod.class) {\r
285             if (statusWindowOwner == this) {\r
286                 if (TRACE_MESSAGES)\r
287                     dumpStatus("hiding");\r
288                 statusWindow.setVisible(false);\r
289             }\r
290         }\r
291         if (TRACE_MESSAGES)\r
292             dumpStatus("<<hideWindows");\r
293     }\r
294     \r
295     public boolean setLocale(Locale locale) {\r
296         return false;\r
297     }\r
298     \r
299     public Locale getLocale() {\r
300         return Locale.getDefault();\r
301     }\r
302     \r
303     public void setCharacterSubsets(Character.Subset[] subsets) {\r
304     }\r
305 \r
306     public void reconvert() {\r
307         throw new UnsupportedOperationException();\r
308     }\r
309 \r
310     public void removeNotify() {\r
311         if (TRACE_MESSAGES)\r
312             dumpStatus("**removeNotify");\r
313     }\r
314 \r
315     public void endComposition() {\r
316         commitAll();\r
317     }\r
318 \r
319     public void dispose() {\r
320         if (TRACE_MESSAGES)\r
321             dumpStatus("**dispose");\r
322     }\r
323     \r
324     public Object getControlObject() {\r
325         return null;\r
326     }\r
327     \r
328     public void setCompositionEnabled(boolean enable) {\r
329         enabled = enable;\r
330     }\r
331 \r
332     public boolean isCompositionEnabled() {\r
333         return enabled;\r
334     }\r
335 \r
336     // debugging\r
337     private String eventInfo(AWTEvent event) {\r
338         String info = event.toString();\r
339         StringBuffer buf = new StringBuffer();\r
340         int index1 = info.indexOf("[");\r
341         int index2 = info.indexOf(",", index1);\r
342         buf.append(info.substring(index1 + 1, index2));\r
343 \r
344         index1 = info.indexOf("] on ");\r
345         index2 = info.indexOf("[", index1);\r
346         if (index2 != -1) {\r
347             int index3 = info.lastIndexOf(".", index2);\r
348             if (index3 < index1 + 4) {\r
349                 index3 = index1 + 4;\r
350             }\r
351             buf.append(" on ");\r
352             buf.append(info.substring(index3 + 1, index2));\r
353         }\r
354         return buf.toString();\r
355     }\r
356 \r
357     public void dispatchEvent(AWTEvent event) {\r
358         final int MODIFIERS = \r
359             InputEvent.CTRL_MASK | \r
360             InputEvent.META_MASK | \r
361             InputEvent.ALT_MASK | \r
362             InputEvent.ALT_GRAPH_MASK;\r
363     \r
364         switch (event.getID()) {\r
365         case MouseEvent.MOUSE_PRESSED:\r
366             if (enabled) {\r
367                 if (TRACE_EVENT) System.out.println("TIM: " + eventInfo(event));\r
368                 // we'll get this even if the user is scrolling, can we rely on the component?\r
369                 // commitAll(); // don't allow even clicks within our own edit area\r
370             }\r
371             break;\r
372     \r
373         case KeyEvent.KEY_TYPED: {\r
374             if (enabled) {\r
375                 KeyEvent ke = (KeyEvent)event;\r
376                 if (TRACE_EVENT) System.out.println("TIM: " + eventInfo(ke));\r
377                 if ((ke.getModifiers() & MODIFIERS) != 0) {\r
378                     commitAll(); // assume a command, let it go through\r
379                 } else {\r
380                     if (handleTyped(ke.getKeyChar())) {\r
381                         ke.consume();\r
382                     }\r
383                 }\r
384             }\r
385         } break;\r
386     \r
387         case KeyEvent.KEY_PRESSED: {\r
388             if (enabled) {\r
389             KeyEvent ke = (KeyEvent)event;\r
390             if (TRACE_EVENT) System.out.println("TIM: " + eventInfo(ke));\r
391                 if (handlePressed(ke.getKeyCode())) {\r
392                     ke.consume();\r
393                 }\r
394             }\r
395         } break;\r
396     \r
397         case KeyEvent.KEY_RELEASED: {\r
398             // this won't autorepeat, which is better for toggle actions\r
399             KeyEvent ke = (KeyEvent)event;\r
400             if (ke.getKeyCode() == KeyEvent.VK_SPACE && ke.isControlDown()) {\r
401                 setCompositionEnabled(!enabled);\r
402             }\r
403         } break;\r
404     \r
405         default:\r
406             break;\r
407         }\r
408     }\r
409 \r
410     /** Wipe clean */\r
411     private void reset() {\r
412         buffer.delete(0, buffer.length());\r
413         index.contextStart = index.contextLimit = index.start = index.limit = 0;\r
414     }\r
415 \r
416     // committed}context-composed|composed\r
417     //          ^       ^        ^\r
418     //         cc     start    ctxLim\r
419 \r
420     private void traceBuffer(String msg, int cc, int off) {\r
421         if (TRACE_BUFFER)\r
422             System.out.println(Utility.escape(msg + ": '"\r
423                     + buffer.substring(0, cc) + '}'\r
424                     + buffer.substring(cc, index.start) + '-'\r
425                     + buffer.substring(index.start, index.contextLimit) + '|'\r
426                     + buffer.substring(index.contextLimit) + '\''));\r
427     }\r
428 \r
429     private void update(boolean flush) {\r
430         int len = buffer.length();\r
431         String text = buffer.toString();\r
432         AttributedString as = new AttributedString(text);\r
433 \r
434         int cc, off;\r
435         if (flush) {\r
436             off = index.contextLimit - len; // will be negative\r
437             cc = index.start = index.limit = index.contextLimit = len;\r
438         } else {\r
439             cc = index.start > desiredContext ? index.start - desiredContext\r
440                     : 0;\r
441             off = index.contextLimit - cc;\r
442         }\r
443 \r
444         if (index.start < len) {\r
445             as.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,\r
446                     InputMethodHighlight.SELECTED_RAW_TEXT_HIGHLIGHT,\r
447                     index.start, len);\r
448         }\r
449 \r
450         imc.dispatchInputMethodEvent(\r
451                 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, as.getIterator(),\r
452                 cc, TextHitInfo.leading(off), null);\r
453 \r
454         traceBuffer("update", cc, off);\r
455 \r
456         if (cc > 0) {\r
457             buffer.delete(0, cc);\r
458             index.start -= cc;\r
459             index.limit -= cc;\r
460             index.contextLimit -= cc;\r
461         }\r
462     }\r
463 \r
464     private void updateCaret() {\r
465         imc.dispatchInputMethodEvent(InputMethodEvent.CARET_POSITION_CHANGED,\r
466                 null, 0, TextHitInfo.leading(index.contextLimit), null);\r
467         traceBuffer("updateCaret", 0, index.contextLimit);\r
468     }\r
469     \r
470     private void caretToStart() {\r
471         if (index.contextLimit > index.start) {\r
472             index.contextLimit = index.limit = index.start;\r
473             updateCaret();\r
474         }\r
475     }\r
476 \r
477     private void caretToLimit() {\r
478         if (index.contextLimit < buffer.length()) {\r
479             index.contextLimit = index.limit = buffer.length();\r
480             updateCaret();\r
481         }\r
482     }\r
483 \r
484     private boolean caretTowardsStart() {\r
485         int bufpos = index.contextLimit;\r
486         if (bufpos > index.start) {\r
487             --bufpos;\r
488             if (bufpos > index.start\r
489                     && UCharacter.isLowSurrogate(buffer.charAt(bufpos))\r
490                     && UCharacter.isHighSurrogate(buffer.charAt(bufpos - 1))) {\r
491                 --bufpos;\r
492             }\r
493             index.contextLimit = index.limit = bufpos;\r
494             updateCaret();\r
495             return true;\r
496         }\r
497         return commitAll();\r
498     }\r
499 \r
500     private boolean caretTowardsLimit() {\r
501         int bufpos = index.contextLimit;\r
502         if (bufpos < buffer.length()) {\r
503             ++bufpos;\r
504             if (bufpos < buffer.length()\r
505                     && UCharacter.isLowSurrogate(buffer.charAt(bufpos))\r
506                     && UCharacter.isHighSurrogate(buffer.charAt(bufpos - 1))) {\r
507                 ++bufpos;\r
508             }\r
509             index.contextLimit = index.limit = bufpos;\r
510             updateCaret();\r
511             return true;\r
512         }\r
513         return commitAll();\r
514     }\r
515 \r
516     private boolean canBackspace() {\r
517         return index.contextLimit > 0;\r
518     }\r
519 \r
520     private boolean backspace() {\r
521         int bufpos = index.contextLimit;\r
522         if (bufpos > 0) {\r
523             int limit = bufpos;\r
524             --bufpos;\r
525             if (bufpos > 0 && UCharacter.isLowSurrogate(buffer.charAt(bufpos))\r
526                     && UCharacter.isHighSurrogate(buffer.charAt(bufpos - 1))) {\r
527                 --bufpos;\r
528             }\r
529             if (bufpos < index.start) {\r
530                 index.start = bufpos;\r
531             }\r
532             index.contextLimit = index.limit = bufpos;\r
533             doDelete(bufpos, limit);\r
534             return true;\r
535         }\r
536         return false;\r
537     }\r
538 \r
539     private boolean canDelete() {\r
540         return index.contextLimit < buffer.length();\r
541     }\r
542 \r
543     private boolean delete() {\r
544         int bufpos = index.contextLimit;\r
545         if (bufpos < buffer.length()) {\r
546             int limit = bufpos + 1;\r
547             if (limit < buffer.length()\r
548                     && UCharacter.isHighSurrogate(buffer.charAt(limit - 1))\r
549                     && UCharacter.isLowSurrogate(buffer.charAt(limit))) {\r
550                 ++limit;\r
551             }\r
552             doDelete(bufpos, limit);\r
553             return true;\r
554         }\r
555         return false;\r
556     }\r
557 \r
558     private void doDelete(int start, int limit) {\r
559         buffer.delete(start, limit);\r
560         update(false);\r
561     }\r
562 \r
563     private boolean commitAll() {\r
564         if (buffer.length() > 0) {\r
565             boolean atStart = index.start == index.contextLimit;\r
566             boolean didConvert = buffer.length() > index.start;\r
567             index.contextLimit = index.limit = buffer.length();\r
568             transliterator.finishTransliteration(replaceableText, index);\r
569             if (atStart) {\r
570                 index.start = index.limit = index.contextLimit = 0;\r
571             }\r
572             update(true);\r
573             return didConvert;\r
574         }\r
575         return false;\r
576     }\r
577 \r
578     private void clearAll() {\r
579         int len = buffer.length();\r
580         if (len > 0) {\r
581             if (len > index.start) {\r
582                 buffer.delete(index.start, len);\r
583             }\r
584             update(true);\r
585         }\r
586     }\r
587 \r
588     private boolean insert(char c) {\r
589         transliterator.transliterate(replaceableText, index, c);\r
590         update(false);\r
591         return true;\r
592     }\r
593 \r
594     private boolean editing() {\r
595         return buffer.length() > 0;\r
596     }\r
597 \r
598     /**\r
599      * The big problem is that from release to release swing changes how it\r
600      * handles some characters like tab and backspace.  Sometimes it handles\r
601      * them as keyTyped events, and sometimes it handles them as keyPressed\r
602      * events.  If you want to allow the event to go through so swing handles\r
603      * it, you have to allow one or the other to go through.  If you don't want\r
604      * the event to go through so you can handle it, you have to stop the\r
605      * event both places.\r
606      * @return whether the character was handled\r
607      */\r
608     private boolean handleTyped(char ch) {\r
609         if (enabled) {\r
610             switch (ch) {\r
611             case '\b': if (editing()) return backspace(); break;\r
612             case '\t': if (editing()) { return commitAll(); } break;\r
613             case '\u001b': if (editing()) { clearAll(); return true; } break;\r
614             case '\u007f': if (editing()) return delete(); break;\r
615             default: return insert(ch);\r
616             }\r
617         }\r
618         return false;\r
619     }\r
620 \r
621     /**\r
622      * Handle keyPressed events.\r
623      */\r
624     private boolean handlePressed(int code) {\r
625         if (enabled && editing()) {\r
626             switch (code) {\r
627             case KeyEvent.VK_PAGE_UP:\r
628             case KeyEvent.VK_UP:\r
629             case KeyEvent.VK_KP_UP:\r
630             case KeyEvent.VK_HOME:\r
631             caretToStart(); return true;\r
632             case KeyEvent.VK_PAGE_DOWN:\r
633             case KeyEvent.VK_DOWN:\r
634             case KeyEvent.VK_KP_DOWN:\r
635             case KeyEvent.VK_END:\r
636             caretToLimit(); return true;\r
637             case KeyEvent.VK_LEFT:\r
638             case KeyEvent.VK_KP_LEFT:\r
639             return caretTowardsStart();\r
640             case KeyEvent.VK_RIGHT:\r
641             case KeyEvent.VK_KP_RIGHT:\r
642             return caretTowardsLimit();\r
643             case KeyEvent.VK_BACK_SPACE: \r
644             return canBackspace(); // unfortunately, in 1.5 swing handles this in keyPressed instead of keyTyped\r
645             case KeyEvent.VK_DELETE: \r
646             return canDelete(); // this too?\r
647             case KeyEvent.VK_TAB:\r
648             case KeyEvent.VK_ENTER:\r
649             return commitAll(); // so we'll never handle VK_TAB in keyTyped\r
650             \r
651             case KeyEvent.VK_SHIFT:\r
652             case KeyEvent.VK_CONTROL:\r
653             case KeyEvent.VK_ALT:\r
654             return false; // ignore these unless a key typed event gets generated\r
655             default: \r
656             // by default, let editor handle it, and we'll assume that it will tell us\r
657             // to endComposition if it does anything funky with, e.g., function keys.\r
658             return false;\r
659             }\r
660         }\r
661         return false;\r
662     }\r
663 \r
664     public String toString() {\r
665         final String[] names = { \r
666             "alice", "bill", "carrie", "doug", "elena", "frank", "gertie", "howie", "ingrid", "john" \r
667         };\r
668     \r
669         if (id < names.length) {\r
670             return names[id];\r
671         } else {\r
672             return names[id] + "-" + (id/names.length);\r
673         }\r
674     }\r
675 }\r
676 \r
677 class NameRenderer extends JLabel implements ListCellRenderer {\r
678 \r
679     /**\r
680      * For serialization\r
681      */\r
682     private static final long serialVersionUID = -210152863798631747L;\r
683 \r
684     public Component getListCellRendererComponent(\r
685         JList list,\r
686         Object value,\r
687         int index,\r
688         boolean isSelected,\r
689         boolean cellHasFocus) {\r
690 \r
691         String s = ((JLabel)value).getText();\r
692         setText(s);\r
693 \r
694         if (isSelected) {\r
695             setBackground(list.getSelectionBackground());\r
696             setForeground(list.getSelectionForeground());\r
697         } else {\r
698             setBackground(list.getBackground());\r
699             setForeground(list.getForeground());\r
700         }\r
701 \r
702         setEnabled(list.isEnabled());\r
703         setFont(list.getFont());\r
704         setOpaque(true);\r
705         return this;\r
706     }\r
707 }\r
708 \r
709 class LabelComparator implements Comparator {\r
710     public int compare(Object obj1, Object obj2) {\r
711         Collator collator = Collator.getInstance();\r
712         return collator.compare(((JLabel)obj1).getText(), ((JLabel)obj2).getText());\r
713     }\r
714 \r
715     public boolean equals(Object obj1) {\r
716         return this.equals(obj1);\r
717     }\r
718 }\r