001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.awt;
019:
020: import java.awt.event.InputEvent;
021: import java.awt.event.KeyEvent;
022: import java.awt.event.MouseEvent;
023: import java.awt.event.MouseWheelEvent;
024: import org.apache.harmony.awt.ChoiceStyle;
025: import org.apache.harmony.awt.PeriodicTimer;
026: import org.apache.harmony.awt.ScrollStateController;
027: import org.apache.harmony.awt.Scrollable;
028: import org.apache.harmony.awt.ScrollbarStateController;
029: import org.apache.harmony.awt.gl.MultiRectArea;
030: import org.apache.harmony.awt.state.ListState;
031:
032: /**
033: * Helper class: popup window containing list of
034: * Choice items, implements popup and scrolling list behavior
035: * of Choice component
036: */
037: class ChoicePopupBox extends PopupBox {
038: private final Choice choice;
039:
040: /**
041: * how many items to scroll on PgUp/PgDn
042: */
043: final static int PAGE_SIZE = 8;
044:
045: /**
046: * how many ticks to skip when scroll speed is minimum
047: */
048: final static int MAX_SKIP = 12;
049:
050: int selectedItem;
051:
052: int focusedItem;
053:
054: /**
055: * fields for dragging/scrolling behavior implementation
056: */
057: private int scrollLocation;
058:
059: transient ScrollPaneAdjustable vAdj;
060:
061: Scrollable scrollable;
062:
063: ScrollStateController scrollController;
064:
065: ScrollbarStateController scrollbarController;
066:
067: boolean scroll;
068:
069: private boolean scrollDragged;
070:
071: /**
072: * fields for scrolling animation
073: */
074: private final PeriodicTimer dragScrollTimer;
075:
076: private int dragScrollSpeed;
077:
078: private Runnable dragScrollHandler;
079:
080: private int nSkip; // scroll every nSkip ticks
081:
082: private final ChoiceListState listState;
083:
084: private final ChoiceStyle style;
085:
086: /**
087: * List state implementation
088: */
089: class ChoiceListState extends Choice.ComponentState implements
090: ListState {
091: ChoiceListState() {
092: choice.super ();
093: }
094:
095: public Rectangle getItemBounds(int idx) {
096: return ChoicePopupBox.this .getItemBounds(idx);
097: }
098:
099: public Rectangle getClient() {
100: Rectangle clientRect = ChoicePopupBox.this .getBounds();
101: if (scroll) {
102: clientRect.width -= scrollable.getAdjustableWidth();
103: }
104: clientRect.grow(-1, -1);
105: return clientRect;
106: }
107:
108: public int getItemCount() {
109: return choice.getItemCount();
110: }
111:
112: public boolean isSelected(int idx) {
113: return (selectedItem == idx);
114: }
115:
116: public String getItem(int idx) {
117: return choice.getItem(idx);
118: }
119:
120: public int getCurrentIndex() {
121: return focusedItem;
122: }
123:
124: @Override
125: public Dimension getSize() {
126: return ChoicePopupBox.this .getSize();
127: }
128: }
129:
130: /**
131: * Scrolling behavior implementation
132: */
133: class ChoiceScrollable implements Scrollable {
134: public Adjustable getVAdjustable() {
135: return vAdj;
136: }
137:
138: public Adjustable getHAdjustable() {
139: return null;
140: }
141:
142: public Insets getInsets() {
143: return Choice.INSETS;
144: }
145:
146: public Point getLocation() {
147: return new Point(0, scrollLocation);
148: }
149:
150: public void setLocation(Point p) {
151: scrollLocation = p.y;
152: }
153:
154: public Component getComponent() {
155: return choice;
156: }
157:
158: public Dimension getSize() {
159: return new Dimension(getWidth(), choice.getItemHeight()
160: * choice.getItemCount());
161: }
162:
163: public void doRepaint() {
164: repaint();
165: }
166:
167: public int getAdjustableWidth() {
168: return vAdj.getBounds().width;
169: }
170:
171: public int getAdjustableHeight() {
172: return 0;
173: }
174:
175: public void setAdjustableSizes(Adjustable adj, int vis,
176: int min, int max) {
177: vAdj.setSizes(vis, min, max);
178: }
179:
180: public int getAdjustableMode(Adjustable adj) {
181: return Scrollable.VERTICAL_ONLY;
182: }
183:
184: public void setAdjustableBounds(Adjustable adj, Rectangle r) {
185: vAdj.setBounds(r);
186: }
187:
188: public int getWidth() {
189: return ChoicePopupBox.this .getWidth();
190: }
191:
192: public int getHeight() {
193: return ChoicePopupBox.this .getHeight();
194: }
195:
196: public void doRepaint(Rectangle r) {
197: repaint(r);
198: }
199: }
200:
201: public ChoicePopupBox(Choice choice) {
202: scrollLocation = 0;
203: this .choice = choice;
204: style = choice.popupStyle;
205: vAdj = new ScrollPaneAdjustable(choice, Adjustable.VERTICAL) {
206: private static final long serialVersionUID = 2280561825998835980L;
207:
208: @Override
209: void repaintComponent(Rectangle r) {
210: if (isVisible()) {
211: repaint(r);
212: }
213: }
214: };
215: scrollable = new ChoiceScrollable();
216: listState = new ChoiceListState();
217: scrollController = new ScrollStateController(scrollable);
218: scrollbarController = vAdj.getStateController();
219: vAdj.addAdjustmentListener(scrollController);
220: nSkip = MAX_SKIP;
221: dragScrollTimer = new PeriodicTimer(25l,
222: getAsyncHandler(getDragScrollHandler()));
223: }
224:
225: public int getWidth() {
226: return getSize().width;
227: }
228:
229: /**
230: * @return action to be performed on scrolling by mouse drag:
231: * action just highlights("focuses") next/previous item
232: */
233: private Runnable getDragScrollHandler() {
234: if (dragScrollHandler == null) {
235: dragScrollHandler = new Runnable() {
236: int n;
237:
238: public void run() {
239: if (n++ % nSkip == 0) {
240: int selItem = focusedItem
241: + (dragScrollSpeed / Math
242: .abs(dragScrollSpeed));
243: selectItem(selItem, false);
244: vAdj
245: .setValue(vAdj.getValue()
246: + dragScrollSpeed);
247: }
248: }
249: };
250: }
251: return dragScrollHandler;
252: }
253:
254: /**
255: * Makes handler asynchronous
256: * @param handler any Runnable
257: * @return asynchronous Runnable: just invokes <code> handler.run()</code>
258: * on event dispatch thread
259: */
260: private Runnable getAsyncHandler(final Runnable handler) {
261: return new Runnable() {
262: public void run() {
263: EventQueue.invokeLater(handler);
264: }
265: };
266: }
267:
268: /**
269: * Paints popup list and vertical scrollbar(if necessary)
270: */
271: @Override
272: void paint(Graphics g) {
273: toolkit.theme.drawList(g, listState, true);
274: if (scroll) {
275: Rectangle r = vAdj.getBounds();
276: Rectangle oldClip = g.getClipBounds();
277: g.setClip(r.x, r.y, r.width, r.height);
278: vAdj.prepaint(g);
279: g.setClip(oldClip);
280: }
281: }
282:
283: /**
284: * Gets item bounds inside popup list
285: * @param pos item index
286: * @return item bounds rectangle relative to
287: * popup list window origin
288: */
289: Rectangle getItemBounds(int pos) {
290: int itemHeight = choice.getItemHeight();
291: Point p = new Point(0, pos * itemHeight);
292: Rectangle itemRect = new Rectangle(p, new Dimension(getWidth(),
293: itemHeight));
294: itemRect.translate(0, scrollLocation);
295: return itemRect;
296: }
297:
298: /**
299: * Calculates popup window screen bounds
300: * using choice style and location on screen
301: * @return list popup window bounds in screen coordinates
302: */
303: Rectangle calcBounds() {
304: scroll = false;
305: int itemHeight = choice.getItemHeight();
306: int choiceWidth = choice.getWidth();
307: int itemWidth = style.getPopupWidth(choiceWidth);
308: int count = choice.getItemCount();
309: if (count > PAGE_SIZE) {
310: count = PAGE_SIZE;
311: scroll = true;
312: }
313: int height = count * itemHeight;
314: Rectangle screenBounds = choice.getGraphicsConfiguration()
315: .getBounds();
316: Point screenLoc = choice.getLocationOnScreen();
317: int x = style.getPopupX(screenLoc.x, itemWidth, choiceWidth,
318: screenBounds.width);
319: int y = calcY(screenLoc.y, height, screenBounds.height);
320: return new Rectangle(x, y, itemWidth, height);
321: }
322:
323: /**
324: * Places list popup window below or above Choice component
325: * @param y Choice component screen y-coordinate
326: * @param height list height
327: * @param screenHeight height of entire screen
328: * @return y screen coordinate of list popup window
329: */
330: int calcY(int y, int height, int screenHeight) {
331: int h = choice.getHeight();
332: y += h; // popup is below choice
333: int maxHeight = screenHeight - y;
334: if (height > maxHeight) {
335: // popup is above choice
336: y -= height + h;
337: }
338: return y;
339: }
340:
341: void show() {
342: Rectangle r = calcBounds();
343: selectedItem = choice.getSelectedIndex();
344: show(r.getLocation(), r.getSize(), choice.getWindowAncestor());
345: if (scroll) {
346: vAdj.setUnitIncrement(choice.getItemHeight());
347: vAdj.setBlockIncrement(vAdj.getUnitIncrement() * PAGE_SIZE);
348: scrollController.layoutScrollbars();
349: makeFirst(choice.selectedIndex);
350: }
351: getNativeWindow().setAlwaysOnTop(true);
352: }
353:
354: /**
355: * @return current popup window bounds
356: */
357: Rectangle getBounds() {
358: return new Rectangle(new Point(), getSize());
359: }
360:
361: @Override
362: void onKeyEvent(int eventId, int vKey, long when, int modifiers) {
363: if (eventId == KeyEvent.KEY_PRESSED) {
364: switch (vKey) {
365: case KeyEvent.VK_ESCAPE:
366: hide();
367: break;
368: case KeyEvent.VK_ENTER:
369: hide();
370: choice.selectAndFire(selectedItem);
371: break;
372: }
373: }
374: }
375:
376: /**
377: * Moves selection <code>incr</code> items up/down from current selected
378: * item, scrolls list to the new selected item
379: */
380: void changeSelection(int incr) {
381: int newIndex = choice.getValidIndex(selectedItem + incr);
382: makeFirst(newIndex);
383: selectItem(newIndex);
384: }
385:
386: /**
387: * Scrolls list to make item the first visible item
388: * @param idx index of item to scroll to(make visible)
389: */
390: void makeFirst(int idx) {
391: if (scroll) {
392: vAdj.setValue(getItemBounds(idx).y - scrollLocation);
393: }
394: }
395:
396: /**
397: * Scrolls list to specified y position
398: */
399: void dragScroll(int y) {
400: if (!scroll) {
401: return;
402: }
403: int h = getHeight();
404: if ((y >= 0) && (y < h)) {
405: dragScrollTimer.stop();
406: return;
407: }
408: int itemHeight = choice.getItemHeight();
409: int dy = ((y < 0) ? -y : (y - h));
410: nSkip = Math.max(1, MAX_SKIP - 5 * dy / itemHeight);
411: dragScrollSpeed = itemHeight * (y / Math.abs(y));
412: dragScrollTimer.start();
413: }
414:
415: /**
416: * Handles all mouse events on popup window
417: */
418: @Override
419: void onMouseEvent(int eventId, Point where, int mouseButton,
420: long when, int modifiers, int wheelRotation) {
421: if (scroll && (eventId == MouseEvent.MOUSE_WHEEL)) {
422: processMouseWheel(where, when, modifiers, wheelRotation);
423: return;
424: }
425: if (scroll
426: && (vAdj.getBounds().contains(where) || scrollDragged)) {
427: processMouseEvent(eventId, where, mouseButton, when,
428: modifiers);
429: return;
430: }
431: boolean button1 = (mouseButton == MouseEvent.BUTTON1);
432: int y = where.y;
433: int index = (y - scrollLocation) / choice.getItemHeight();
434: switch (eventId) {
435: case MouseEvent.MOUSE_PRESSED:
436: break;
437: case MouseEvent.MOUSE_DRAGGED:
438: // scroll on mouse drag
439: if ((modifiers & InputEvent.BUTTON1_DOWN_MASK) != 0) {
440: dragScroll(y);
441: }
442: case MouseEvent.MOUSE_MOVED:
443: if ((index < 0) || (index >= choice.getItemCount())
444: || (index == selectedItem)) {
445: return;
446: }
447: boolean select = getBounds().contains(where);
448: if ((y >= 0) && (y < getHeight())) {
449: selectItem(index, select); //hot track
450: }
451: break;
452: case MouseEvent.MOUSE_RELEASED:
453: if (button1) {
454: hide();
455: choice.selectAndFire(index);
456: }
457: break;
458: }
459: }
460:
461: /**
462: * Mouse wheel event processing by vertical scrollbar
463: */
464: private void processMouseWheel(Point where, long when,
465: int modifiers, int wheelRotation) {
466: MouseWheelEvent mwe = new MouseWheelEvent(choice,
467: MouseEvent.MOUSE_WHEEL, when, modifiers, where.x,
468: where.y, 0, false, MouseWheelEvent.WHEEL_UNIT_SCROLL,
469: 1, wheelRotation);
470: scrollController.mouseWheelMoved(mwe);
471: }
472:
473: /**
474: * Mouse event processing by vertical scrollbar
475: */
476: private void processMouseEvent(int id, Point where, int button,
477: long when, int modifiers) {
478: MouseEvent me = new MouseEvent(choice, id, when, modifiers,
479: where.x, where.y, 0, false, button);
480: switch (id) {
481: case MouseEvent.MOUSE_PRESSED:
482: scrollDragged = true;
483: scrollbarController.mousePressed(me);
484: break;
485: case MouseEvent.MOUSE_RELEASED:
486: scrollDragged = false;
487: scrollbarController.mouseReleased(me);
488: break;
489: case MouseEvent.MOUSE_DRAGGED:
490: scrollbarController.mouseDragged(me);
491: break;
492: }
493: }
494:
495: /**
496: * Selects/focuses item and repaints popup window
497: * @param index item to be focused/selected
498: * @param highlight item is highlighted(selected) if true,
499: * only focused otherwise
500: */
501: private void selectItem(int index, boolean highlight) {
502: Rectangle oldRect = listState.getItemBounds(focusedItem);
503: if (focusedItem != selectedItem) {
504: oldRect = oldRect.union(listState
505: .getItemBounds(selectedItem));
506: }
507: focusedItem = index;
508: selectedItem = (highlight ? focusedItem : -1);
509: Rectangle itemRect = listState.getItemBounds(index);
510: Rectangle paintRect = itemRect.union(oldRect);
511: paintRect.grow(0, 2);
512: if (scroll) {
513: paintRect.width -= vAdj.getBounds().width;
514: }
515: repaint(paintRect);
516: }
517:
518: private void selectItem(int index) {
519: selectItem(index, true);
520: }
521:
522: void repaint(final Rectangle r) {
523: EventQueue.invokeLater(new Runnable() {
524: public void run() {
525: paint(new MultiRectArea(r));
526: }
527: });
528: }
529:
530: void repaint() {
531: repaint(null);
532: }
533:
534: @Override
535: boolean closeOnUngrab(Point start, Point end) {
536: if (!getBounds().contains(end)) {
537: // close on mouse ungrab only
538: // if grab started not above
539: // the scrollbar
540: return !vAdj.getBounds().contains(start);
541: }
542: return true;
543: }
544:
545: @Override
546: void hide() {
547: // stop timer before closing:
548: if (scroll && dragScrollTimer.isRunning()) {
549: dragScrollTimer.stop();
550: }
551: super.hide();
552: }
553: }
|