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: * @author Dmitry A. Durnev
019: * @version $Revision$
020: */package org.apache.harmony.awt;
021:
022: import java.awt.Adjustable;
023: import java.awt.Dimension;
024: import java.awt.EventQueue;
025: import java.awt.Point;
026: import java.awt.Rectangle;
027: import java.awt.event.AdjustmentEvent;
028: import java.awt.event.ComponentEvent;
029: import java.awt.event.ComponentListener;
030: import java.awt.event.FocusEvent;
031: import java.awt.event.FocusListener;
032: import java.awt.event.KeyEvent;
033: import java.awt.event.KeyListener;
034: import java.awt.event.MouseEvent;
035: import java.awt.event.MouseListener;
036: import java.awt.event.MouseMotionListener;
037:
038: import org.apache.harmony.awt.internal.nls.Messages;
039: import org.apache.harmony.awt.state.ScrollbarState;
040:
041: /**
042: * ScrollbarStateController.
043: * Implements Scrollbar behavior. Responds to
044: * input, focus, component events and updates
045: * scrollbar state. Repaints scrollbar when necessary,
046: * fires [adjustment] events.
047: */
048: public abstract class ScrollbarStateController implements
049: MouseListener, MouseMotionListener, FocusListener, KeyListener,
050: ComponentListener {
051:
052: private static final long DELAY = 100l; //animation delay in ms
053: // delay between mouse press event and scroll start:
054: private static final long START_DELAY = 250l;
055:
056: private final ScrollbarState state;
057: private final SimpleButton incr, decr;
058: private final Slider slider;
059: private boolean pressed;
060: private boolean focused;
061:
062: /**
063: * store last scroll type(see AdjustmentEvent) to be able
064: * to get it when firing adjustment event
065: */
066: private int type = -1;
067:
068: /**
069: * Which part of scrollbar track is highlighted
070: * while block scrolling by mouse press on track
071: * @see ScrollbarState
072: */
073: private int highlight = ScrollbarState.NO_HIGHLIGHT;
074:
075: private ScrollTask unitTask;
076: private BlockScrollTask blockTask;
077: int curMouseX, curMouseY; //for dragging
078: private Point dragPoint; //for dragging
079:
080: public ScrollbarStateController(ScrollbarState state) {
081: this .state = state;
082: incr = new SimpleButton(state.getIncreaseRect());
083: decr = new SimpleButton(state.getDecreaseRect());
084: slider = new Slider(state.getSliderRect());
085: unitTask = new ScrollTask(1);
086: blockTask = new BlockScrollTask(1);
087: }
088:
089: /**
090: * Scrollbar Arrow button behavior:
091: * handles mouse events
092: */
093: class SimpleButton {
094: Rectangle rect;
095: boolean pressed;
096: boolean mouseInside;
097:
098: SimpleButton(Rectangle r) {
099: rect = r;
100: }
101:
102: boolean mousePressed(Point pt) {
103: if (rect == null) {
104: return false;
105: }
106:
107: mouseInside = rect.contains(pt);
108: if (mouseInside) {
109: pressed = true;
110: repaint();
111: }
112:
113: return mouseInside;
114: }
115:
116: boolean mouseReleased(Point pt) {
117: if (rect == null) {
118: return false;
119: }
120:
121: // there's no mouse capture, so
122: // have to handle releases manually:
123: boolean wasPressed = pressed;
124: if (wasPressed) {
125: pressed = false;
126: repaint();
127: }
128:
129: mouseInside = rect.contains(pt);
130: return wasPressed && mouseInside;
131: }
132:
133: boolean mouseDragged(Point pt) {
134:
135: boolean oldInside = mouseInside;
136: mouseInside = rect.contains(pt);
137: if (pressed && (oldInside != mouseInside)) {
138: repaint();
139: }
140: return mouseInside;
141: }
142:
143: void repaint() {
144: ScrollbarStateController.this .repaint(rect);
145: }
146:
147: boolean isPressed() {
148: return (pressed && mouseInside);
149: }
150:
151: }
152:
153: /**
154: * Scrollbar Thumb[scroll box] behavior:
155: * handles mouse drags
156: */
157: class Slider extends SimpleButton {
158: boolean dragged;
159:
160: Slider(Rectangle r) {
161: super (r);
162: }
163:
164: boolean isDragged() {
165: return dragged;
166: }
167:
168: @Override
169: boolean mouseReleased(Point pt) {
170: boolean result = super .mouseReleased(pt);
171:
172: if (dragged) {
173: dragged = false;
174: }
175: return result;
176: }
177:
178: @Override
179: boolean mouseDragged(Point pt) {
180: boolean result = super .mouseDragged(pt);
181:
182: if (pressed) {
183: dragged = true;
184: setValueOnDragging(pt);
185: curMouseX = pt.x;
186: curMouseY = pt.y;
187: }
188: return result;
189: }
190: }
191:
192: /**
193: * Timer task which scrolls by unit
194: * on every tick.
195: */
196: class ScrollTask implements Runnable {
197:
198: /**
199: * scroll direction(up or down)
200: */
201: int dir;
202:
203: /**
204: * overall running time
205: */
206: long runTime;
207:
208: private PeriodicTimer timer;
209:
210: /**
211: * Delay before scroll starts
212: */
213: private long startDelay;
214:
215: ScrollTask(int dir) {
216: setDir(dir);
217: timer = new PeriodicTimer(DELAY, this );
218: }
219:
220: public void run() {
221: runTime += timer.getPeriod();
222: if (runTime < startDelay) {
223: return;
224: }
225: EventQueue.invokeLater(new Runnable() {
226: public void run() {
227: scroll();
228: }
229:
230: });
231: }
232:
233: void setDir(int dir) {
234: this .dir = dir;
235: }
236:
237: void scroll() {
238: scrollByUnit(dir);
239: }
240:
241: PeriodicTimer getTimer() {
242: return timer;
243: }
244:
245: void start(int dir, long delay) {
246: setDir(dir);
247: startDelay = delay;
248: runTime = 0;
249: timer.start();
250:
251: }
252: }
253:
254: /**
255: * Timer task which scrolls by block
256: * on every tick.
257: */
258: class BlockScrollTask extends ScrollTask {
259:
260: BlockScrollTask(int dir) {
261: super (dir);
262: }
263:
264: @Override
265: void scroll() {
266: int dir = getBlockDir(new Point(curMouseX, curMouseY));
267: if (this .dir != dir) {
268: getTimer().stop();
269: return;
270: }
271: scrollByBlock(dir);
272: }
273: }
274:
275: public void mouseClicked(MouseEvent e) {
276: // ignored
277:
278: }
279:
280: public void mouseEntered(MouseEvent e) {
281: // ignored
282:
283: }
284:
285: public void mouseExited(MouseEvent e) {
286: // ignored
287:
288: }
289:
290: public void mousePressed(MouseEvent me) {
291: if (me.getButton() != MouseEvent.BUTTON1) {
292: return;
293: }
294: if (!focused) {
295: requestFocus();
296: }
297: Point pt = getPoint(me);
298: curMouseX = pt.x;
299: curMouseY = pt.y;
300: boolean incrPressed = incr.mousePressed(pt);
301: boolean decrPressed = decr.mousePressed(pt);
302: slider.mousePressed(pt);
303: if (incrPressed || decrPressed) {
304: int dir = (incrPressed ? 1 : -1);
305: scrollByUnit(dir);
306: // start timer
307: unitTask.start(dir, START_DELAY);
308: } else {
309: pressed = true;
310: int dir = getBlockDir(pt);
311: if (dir != 0) {
312: scrollByBlock(dir);
313: blockTask.start(dir, START_DELAY);
314: } else {
315: Point thumbLoc = state.getSliderRect().getLocation();
316: dragPoint = new Point(pt.x - thumbLoc.x, pt.y
317: - thumbLoc.y);
318: }
319: repaint();
320: }
321:
322: }
323:
324: /**
325: * Gets direction of block scroll
326: * started by mouse press on scrollbar
327: * track
328: * @param pt point where mouse was pressed
329: * in scrollbar coordinates
330: * @return 1 for scroll down/right, -1 for
331: * up/left, 0 - no scroll
332: */
333: private int getBlockDir(Point pt) {
334: Rectangle sliderRect = state.getSliderRect();
335: if ((sliderRect == null) || sliderRect.isEmpty()) {
336: return 0;
337: }
338: Rectangle upperRect = state.getUpperTrackBounds();
339: Rectangle lowerRect = state.getLowerTrackBounds();
340:
341: if (lowerRect.contains(pt)) {
342: highlight = ScrollbarState.INCREASE_HIGHLIGHT;
343: return 1;
344: } else if (upperRect.contains(pt)) {
345: highlight = ScrollbarState.DECREASE_HIGHLIGHT;
346: return -1;
347: } else {
348: highlight = ScrollbarState.NO_HIGHLIGHT;
349: type = AdjustmentEvent.TRACK;
350: repaint(state.getTrackBounds());
351: }
352: return 0;
353: }
354:
355: public void mouseReleased(MouseEvent me) {
356: if (me.getButton() != MouseEvent.BUTTON1) {
357: return;
358: }
359: Point pt = getPoint(me);
360: boolean incrReleased = incr.mouseReleased(pt);
361: boolean decrReleased = decr.mouseReleased(pt);
362: slider.mouseReleased(pt);
363: if (!(decrReleased || incrReleased)) {
364: pressed = false;
365: type = -1;
366: highlight = ScrollbarState.NO_HIGHLIGHT;
367: repaint();
368: }
369: // stop timer
370: blockTask.getTimer().stop();
371: unitTask.getTimer().stop();
372:
373: }
374:
375: public void mouseDragged(MouseEvent e) {
376: Point pt = getPoint(e);
377: switchTimerOnDrag(pt, incr, 1);
378: switchTimerOnDrag(pt, decr, -1);
379: slider.mouseDragged(pt);
380: }
381:
382: /**
383: * Translates component coordinates into
384: * scrollbar coordinates
385: * @param e coordinates of this mouse event are translated
386: * @return point in scrollbar coordinates
387: */
388: private Point getPoint(MouseEvent e) {
389: Point pt = e.getPoint().getLocation();
390: Point loc = state.getLocation();
391: pt.translate(-loc.x, -loc.y);
392: return pt;
393: }
394:
395: /**
396: * Starts/stops unit scrolling on mouse drag
397: * in/out of arrow button
398: * @param pt current mouse position
399: * @param b arrow button
400: * @param dir scroll direction
401: */
402: void switchTimerOnDrag(Point pt, SimpleButton b, int dir) {
403: boolean wasPressed = b.isPressed();
404: b.mouseDragged(pt);
405: boolean isPressed = b.isPressed();
406: if (isPressed != wasPressed) {
407: if (isPressed) {
408: unitTask.start(dir, 0l);
409: } else {
410: unitTask.getTimer().stop();
411: }
412: }
413: }
414:
415: protected abstract void repaint(Rectangle r);
416:
417: protected abstract void repaint();
418:
419: protected abstract void requestFocus();
420:
421: public void mouseMoved(MouseEvent e) {
422: // ignored
423:
424: }
425:
426: public void focusGained(FocusEvent e) {
427: focused = true;
428: repaint();
429:
430: }
431:
432: public void focusLost(FocusEvent e) {
433: focused = false;
434: repaint();
435: }
436:
437: public void keyPressed(KeyEvent e) {
438: // awt.54=Key event for unfocused component
439: assert focused : Messages.getString("awt.54"); //$NON-NLS-1$
440: int keyCode = e.getKeyCode();
441: switch (keyCode) {
442: case KeyEvent.VK_UP:
443: case KeyEvent.VK_LEFT:
444: scrollByUnit(-1);
445: break;
446: case KeyEvent.VK_DOWN:
447: case KeyEvent.VK_RIGHT:
448: scrollByUnit(1);
449: break;
450: case KeyEvent.VK_PAGE_UP:
451: scrollByBlock(-1);
452: break;
453: case KeyEvent.VK_PAGE_DOWN:
454: scrollByBlock(1);
455: break;
456: case KeyEvent.VK_HOME:
457: setAdjMinMaxValue(false);
458: break;
459: case KeyEvent.VK_END:
460: setAdjMinMaxValue(true);
461: break;
462:
463: }
464: }
465:
466: public void keyReleased(KeyEvent e) {
467: // ignored
468: }
469:
470: public void keyTyped(KeyEvent e) {
471: // ignored
472: }
473:
474: public void componentHidden(ComponentEvent e) {
475: // ignored
476:
477: }
478:
479: public void componentMoved(ComponentEvent e) {
480: // ignored
481:
482: }
483:
484: public void componentResized(ComponentEvent e) {
485: // call repaint() to calculate internal layout
486: repaint();
487: }
488:
489: public void componentShown(ComponentEvent e) {
490: // ignored
491:
492: }
493:
494: public boolean isIncreasePressed() {
495: return incr.isPressed();
496: }
497:
498: public boolean isDecreasePressed() {
499: return decr.isPressed();
500: }
501:
502: public boolean isSliderDragged() {
503: return slider.isDragged();
504: }
505:
506: protected abstract void fireEvent();
507:
508: public final boolean isPressed() {
509: return pressed;
510: }
511:
512: public final int getType() {
513: return type;
514: }
515:
516: /**
517: * Sets scrollbar value on
518: * thumb dragging by mouse.
519: * Repaints scrollbar & fires events
520: * when necessary.
521: * @param pt current mouse position
522: */
523: public void setValueOnDragging(final Point pt) {
524:
525: int extent = state.getSliderSize();
526: int viewSize = state.getScrollSize();
527: int availableScrollingSize = viewSize - extent;
528: int thumbSize = getThumbSize();
529: int availableTrackSize = state.getTrackSize() - thumbSize;
530: Rectangle trackRect = state.getTrackBounds();
531: int offset = getOffset(pt, trackRect.x + dragPoint.x,
532: trackRect.y + dragPoint.y);
533:
534: float fVal = 0.0f;
535: if (availableTrackSize != 0) {
536: fVal = (float) offset * availableScrollingSize
537: / availableTrackSize;
538: }
539: int val = Math.round(fVal);
540: boolean repaint = false;
541: if (offset != 0) {
542:
543: Rectangle oldRect = (Rectangle) state.getSliderRect()
544: .clone();
545: Rectangle r = new Rectangle(oldRect);
546:
547: if (state.isVertical()) {
548: r.y = pt.y - dragPoint.y;
549: } else {
550: r.x = pt.x - dragPoint.x;
551: }
552: if (trackRect.contains(r)) {
553: state.setSliderRect(r);
554: Rectangle paintRect = r.union(oldRect);
555: repaint(paintRect);
556: } else {
557: repaint = true;
558: }
559: }
560:
561: Adjustable adj = state.getAdjustable();
562: int oldVal = adj.getValue();
563: state.setValue(AdjustmentEvent.TRACK, val);
564: if (oldVal != adj.getValue()) {
565: fireEvent();
566: if (repaint) {
567: repaint();
568: }
569: }
570:
571: }
572:
573: /**
574: * @return width/height of horizontal/vertical
575: * scrollbar thumb
576: */
577: private int getThumbSize() {
578: Dimension sliderSize = state.getSliderRect().getSize();
579: return (state.isVertical() ? sliderSize.height
580: : sliderSize.width);
581: }
582:
583: /**
584: * Gets distance in points between specified point
585: * and current mouse position
586: * @param pt new position coordinates
587: * @param currentMouseX current mouse x coordinate
588: * @param currentMouseY current mouse y coordinate
589: * @return distance in pixels from pt to (currentMouseX, currentMouseY)
590: */
591: int getOffset(final Point pt, final int currentMouseX,
592: final int currentMouseY) {
593: boolean vertical = state.isVertical();
594: int newPos = vertical ? pt.y : pt.x;
595: int oldPos = vertical ? currentMouseY : currentMouseX;
596: int offset = newPos - oldPos;
597: if (!vertical
598: && !state.getComponentOrientation().isLeftToRight()) {
599: offset = -offset;
600: }
601: return offset;
602: }
603:
604: /**
605: * Changes scrollbar value on increment.
606: * Fires adjustment event on value change.
607: * @param increment change amount
608: */
609: private void updateAdjValue(final int increment) {
610: Adjustable adj = state.getAdjustable();
611: int oldVal = adj.getValue();
612: state.setValue(type, oldVal + increment);
613: if (oldVal != adj.getValue()) {
614: fireEvent();
615: }
616: }
617:
618: /**
619: * Scrolls by block up(left) if dir is < 0
620: * or down(right) if dir is > 0.
621: * @param dir direction
622: */
623: void scrollByBlock(final int dir) {
624: type = (dir > 0 ? AdjustmentEvent.BLOCK_INCREMENT
625: : AdjustmentEvent.BLOCK_DECREMENT);
626: updateAdjValue(state.getAdjustable().getBlockIncrement() * dir);
627: }
628:
629: /**
630: * Scrolls by unit up(left) if dir is < 0
631: * or down(right) if dir is > 0.
632: * @param dir direction
633: */
634: void scrollByUnit(final int dir) {
635: type = (dir > 0 ? AdjustmentEvent.UNIT_INCREMENT
636: : AdjustmentEvent.UNIT_DECREMENT);
637: updateAdjValue(state.getAdjustable().getUnitIncrement() * dir);
638: }
639:
640: /**
641: * Scrolls to scrollbar maximum or minimum value
642: * @param max scroll to maximum if true, scroll to minimum
643: * otherwise
644: */
645: void setAdjMinMaxValue(boolean max) {
646: Adjustable adj = state.getAdjustable();
647: int newVal = max ? adj.getMaximum() : adj.getMinimum();
648: int oldVal = adj.getValue();
649: adj.setValue(newVal);
650: if (oldVal != newVal) {
651: type = AdjustmentEvent.TRACK;
652: fireEvent();
653: }
654: }
655:
656: public int getHighlight() {
657: return highlight;
658: }
659: }
|