Source code: jcurses/widgets/Window.java
1 package jcurses.widgets;
2
3
4 import jcurses.event.WindowEvent;
5 import jcurses.event.WindowListener;
6 import jcurses.event.WindowListenerManager;
7 import jcurses.system.CharColor;
8 import jcurses.system.InputChar;
9 import jcurses.system.Toolkit;
10 import jcurses.util.Protocol;
11 import jcurses.util.Rectangle;
12
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.Hashtable;
16 import java.util.Vector;
17
18 /**
19 * This class is a jcurses implementation of an text based window. An window
20 * under jcurses is, differnt from other GUI libraries, not a widget, this
21 * contains a panel ( a the called root panel ), that contains all widgets.
22 * A window can, but doesn't must, have a border and a title.
23 * All windows under jcurses are managed in a stack, the topmost visible window
24 * window on the stack gets all input chars for handling, this is so called focus
25 * window. If an window are created, it gets automatically to the top of the stack
26 * and leaves the until an other window is created or explicitly brought to the top.
27 *
28 */
29
30 public class Window {
31
32 private Panel _root = null;
33
34 private Vector _focusableChilds = null;
35 private int _currentIndex = -1;
36
37 private boolean _visible = false;
38
39 private Rectangle _rect = null;
40
41 private boolean _border = false;
42 private String _title = null;
43
44 private boolean _hasShadow = true;
45
46 private Vector _shortCutsList = new Vector();
47 private Hashtable _shortCutsTable = new Hashtable();
48
49 boolean _closed = false;
50
51
52
53 /**
54 * The constructor
55 *
56 * @param x the the top left corner's x coordinate
57 * @param y the top left corner's y coordinate
58 * @param width window's width
59 * @param height window's height
60 * @param border true, if the window has a border, false in other case
61 */
62 public Window (int x, int y, int width, int height, boolean border, String title) {
63 _border = border;
64 _title = title;
65 _rect = new Rectangle(width, height);
66 _rect.setLocation(x, y);
67
68 int x1 = border?x+1:x;
69 int y1 = border?y+1:y;
70 int w = border?width-2:width;
71 int h = border?height-2:height;
72
73 _root = new Panel(w, h);
74 _root.setSize(new Rectangle(w, h));
75 _root.setX(x1);
76 _root.setY(y1);
77 _root.setWindow(this);
78 WindowManager.createWindow(this);
79 }
80
81
82 /**
83 * The constructor. A window created with this constructor is centered on the screen.
84 *
85 * @param width window's width
86 * @param height window's height
87 * @param border true, if the window has a border, false in other case
88 */
89 public Window (int width, int height, boolean border, String title) {
90 this((Toolkit.getScreenWidth()-width)/2,(Toolkit.getScreenHeight()-height)/2, width, height, border, title);
91 }
92
93
94 private void configureRootPanel() {
95 if (_root == null) {
96 _root = new Panel();
97 }
98
99 int x = _rect.getX();
100 int y = _rect.getY();
101 int width = _rect.getWidth();
102 int height = _rect.getHeight();
103
104 int x1 = _border?x+1:x;
105 int y1 = _border?y+1:y;
106 int w = _border?width-2:width;
107 int h = _border?height-2:height;
108
109 _root.setSize(new Rectangle(w, h));
110 _root.setX(x1);
111 _root.setY(y1);
112
113 }
114
115
116 /**
117 * The method shows the window
118 */
119 public void show() {
120 setVisible(true);
121 }
122
123 /**
124 * The method hides the window
125 */
126
127 public void hide() {
128 setVisible(false);
129 }
130
131
132 /**
133 * The method changes the window's visibility status
134 *
135 * @param value true, if the window becomes visible, false in other case
136 */
137
138 public void setVisible(boolean value){
139 Window oldTop = WindowManager.getTopWindow();
140 if (value) {
141 pack();
142 _visible = value;
143 WindowManager.makeWindowVisible(this, oldTop);
144 } else {
145 _visible = value;
146 WindowManager.makeWindowInvisible(this, oldTop);
147 }
148 }
149
150
151 /**
152 * The method returns the window's visibility status
153 * @return true, if the window becomes visible, false in other case
154 */
155 public boolean isVisible() {
156 return _visible;
157 }
158
159 /**
160 * The method paint's the window
161 */
162
163 protected void paint() {
164 drawThingsIfNeeded();
165 _root.paint();
166 }
167
168
169 /**
170 * Currently the method makes the same as repaint, in next versions the method
171 * will repaint only the part of the window, that was hided.
172 */
173
174 protected void repaint() {
175 drawThingsIfNeeded();
176 _root.repaint();
177 }
178
179 /**
180 * @return the rectangle occupied by the window
181 */
182
183 protected Rectangle getRectangle() {
184 return _rect;
185 }
186
187 /**
188 * The method closed the window, that is removes it from window stack an
189 * evantually from screen, if it was visible.
190 */
191
192 public void close() {
193 WindowManager.removeWindow(this);
194 }
195
196
197 /**
198 * The method moves the window to the top of the stack
199 */
200
201 public void moveToTheTop() {
202 WindowManager.moveToTop(this);
203 }
204
205 /**
206 * Folgende Methoden bestimmen das zeichen das benutzt wird, um das Fenster zu schließen
207 */
208
209 private static InputChar __defaultClosingChar = new InputChar(27);//escape character
210 private InputChar _closingChar = getDefaultClosingChar();
211
212
213 private InputChar getDefaultClosingChar() {
214 return __defaultClosingChar;
215 }
216
217
218 /**
219 * The method returns the character to close window.
220 * As default is escape characted defined
221 *
222 * @return window's closing character
223 */
224 public InputChar getClosingChar() {
225 return _closingChar;
226 }
227
228
229
230 /**
231 * The method defines a new window's closing character. Default is escape.
232 * @param character new window's closing character
233 */
234 public void setClosingChar(InputChar character) {
235 _closingChar = character;
236 }
237
238
239
240 private static InputChar __defaultFocusChangeChar = new InputChar('\t');//tab character
241 private InputChar _focusChangeChar = getDefaultFocusChangeChar();
242
243
244 private InputChar getDefaultFocusChangeChar() {
245 return __defaultFocusChangeChar;
246 }
247
248 /**
249 * The method returns the charater used to navigate (change the focus) between widgets
250 * within the window. Default is 'tab'
251 *
252 * @return window's focus changing charater
253 */
254 public InputChar getFocusChangeChar() {
255 return _focusChangeChar;
256 }
257
258
259 /**
260 * The method defined the charater used to navigate (change the focus) between widgets
261 * within the window. Default is 'tab'
262 *
263 * @param character new window's focus changing charater
264 */
265 public void setFocusChangeChar(InputChar character) {
266 _focusChangeChar = character;
267 }
268
269 /**
270 * Behandlung der Eingabe.
271 * Vier mögliche Fälle:
272 * 1. Fenster schliessen.
273 * 2. Zum nächsten Widget springen.
274 * 3. Shortcut bearbeiten.
275 * 3. Eingabe vom aktuell Fokus habenden Kind bearbeiten lassen.
276 */
277
278
279 private boolean isShortCut(InputChar inp) {
280 return (_shortCutsList.indexOf(inp)!=-1);
281 }
282
283
284 private Widget getWidgetByShortCut(InputChar inp) {
285 return (Widget)_shortCutsTable.get(inp);
286 }
287
288
289 private static InputChar __upChar = new InputChar(InputChar.KEY_UP);
290 private static InputChar __downChar = new InputChar(InputChar.KEY_DOWN);
291 private static InputChar __leftChar = new InputChar(InputChar.KEY_LEFT);
292 private static InputChar __rightChar = new InputChar(InputChar.KEY_RIGHT);
293
294 /**
295 * The method tries to close the window, after the user has typed 'escape'
296 * or an other closing character. The procedure is as following:
297 * If the window has listeners, than an event is sent to the listeners. The window
298 * can be closed bei listeners. Did'nt listeners close the window, in leaves open.
299 * Has the window no listeners, than the method closes it.
300 *
301 */
302
303 public boolean tryToClose() {
304 if (_listenerManager.countListeners() > 0) {
305 _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.CLOSING));
306 return isClosed();
307 } else {
308 close();
309 return true;
310 }
311 }
312
313
314 /**
315 * @return true, if the window is already closed, false in othe case.
316 */
317
318 public boolean isClosed() {
319 return _closed;
320 }
321
322
323 /**
324 * The method is called by the libray to handle an input character, if the window has the focus.
325 */
326 protected void handleInput(InputChar inp) {
327 if (inp.equals(getClosingChar())) {
328 tryToClose();
329 } else if (inp.equals(getFocusChangeChar())) {
330 changeFocus();
331 } else if (inp.equals(__upChar)) {
332 Widget cur = getCurrentWidget();
333 if (cur != null) {
334 boolean result = cur.handleInput(inp);
335 if (!result) {
336 changeFocus(2);
337 }
338 }
339 } else if (inp.equals(__downChar)) {
340 Widget cur = getCurrentWidget();
341 if (cur != null) {
342 boolean result = cur.handleInput(inp);
343 if (!result) {
344 changeFocus(3);
345 }
346 }
347 } else if (inp.equals(__leftChar)) {
348 Widget cur = getCurrentWidget();
349 if (cur != null) {
350 boolean result = cur.handleInput(inp);
351 if (!result) {
352 changeFocus(0);
353 }
354 }
355 } else if (inp.equals(__rightChar)) {
356 Widget cur = getCurrentWidget();
357 if (cur != null) {
358 boolean result = cur.handleInput(inp);
359 if (!result) {
360 changeFocus(1);
361 }
362 }
363 } else if (isShortCut(inp)) {
364 Widget cur = getCurrentWidget();
365 if (cur!=null) {
366 boolean result = cur.handleInput(inp);
367 if (!result) {
368 getWidgetByShortCut(inp).handleInput(inp);
369 }
370 }
371 } else {
372 if (!handleInputByCurrentChild(inp)) {
373 onChar(inp);
374 }
375 }
376 }
377
378
379 /**
380 * The method is called by <code>handleInput</code>, if no widget has handled
381 * the input. Derived classes can override the method to define additional shortcuts.
382 */
383
384 protected void onChar(InputChar inp) {
385 //default nothing
386 }
387
388
389 private void changeFocus() {
390 if (_currentIndex != -1) {
391 int newIndex = (_currentIndex == (_focusableChilds.size()-1))?0:(_currentIndex+1);
392 if (newIndex!=_currentIndex) {
393 ((Widget)_focusableChilds.elementAt(_currentIndex)).setFocus(false);
394 ((Widget)_focusableChilds.elementAt(newIndex)).setFocus(true);
395 _currentIndex = newIndex;
396 }
397 }
398 }
399
400
401 private void changeFocus(int direction) {
402 if (_currentIndex != -1) {
403 changeFocus(getNextWidget(direction));
404 }
405 }
406
407
408
409 private Widget getNextWidget(int direction) {
410 Widget result = getCurrentWidget();
411 Widget current = getCurrentWidget();
412 int x = result.getAbsoluteX();
413 int y = result.getAbsoluteY();
414 int searchDirection = ((direction == 0) || (direction == 2))?-1:1;
415
416 Vector widgets = _focusableChilds;
417 int index = widgets.indexOf(result);
418 if (index < 0) {
419 throw new RuntimeException("widget in the sorted queue not found!!");
420 }
421
422 int distance = Integer.MAX_VALUE;
423 while (index < widgets.size() && index > -1) {
424 index+=searchDirection;
425 if (index < widgets.size() && index > -1) {
426 Widget candidate = (Widget)widgets.get(index);
427 if (((direction == 0) && WindowWidgetComparator.toTheLeftOf(candidate, current)) ||
428 ((direction == 1) && WindowWidgetComparator.toTheRightOf(candidate, current)) ||
429 ((direction == 2) && WindowWidgetComparator.atTheTopOf(candidate, current)) ||
430 ((direction == 3) && WindowWidgetComparator.atTheBottomOf(candidate, current))) {
431 int newDistance = WindowWidgetComparator.getDistance(candidate, current);
432 if (newDistance < distance) {
433 distance = newDistance;
434 result = candidate;
435 }
436 }
437 }
438
439 }
440
441 return result;
442
443 }
444
445
446
447
448 void changeFocus(Widget widget) {
449 int newIndex = _focusableChilds.indexOf(widget);
450 if (newIndex!=-1) {
451 if (_currentIndex == -1) {
452 widget.setFocus(true);
453 _currentIndex = newIndex;
454 } else if (_currentIndex == newIndex) {
455 } else {
456 widget.setFocus(true);
457 ((Widget)_focusableChilds.elementAt(_currentIndex)).setFocus(false);
458 _currentIndex = newIndex;
459 }
460
461 }
462 }
463
464
465 private Widget getCurrentWidget() {
466 if (_currentIndex != -1) {
467 return ((Widget)_focusableChilds.elementAt(_currentIndex));
468 } else {
469 return null;
470 }
471 }
472
473 private boolean handleInputByCurrentChild(InputChar inp) {
474 if (_currentIndex != -1) {
475 return ((Widget)_focusableChilds.elementAt(_currentIndex)).handleInput(inp);
476 }
477
478 return false;
479 }
480
481
482
483 private void loadShortcuts() {
484 _shortCutsList.clear();
485 _shortCutsTable.clear();
486
487 Vector list = _root.getListOfWidgetsWithShortCuts();
488 for (int i=0; i<list.size(); i++) {
489 Widget widget = (Widget)list.elementAt(i);
490 Vector shortCuts = widget.getShortCutsList();
491 _shortCutsList.addAll(shortCuts);
492 for (int j=0; j< shortCuts.size(); j++) {
493 _shortCutsTable.put(shortCuts.elementAt(j), widget);
494 }
495 }
496
497 }
498
499 private void loadFocusableChilds() {
500 _focusableChilds = _root.getListOfFocusables();
501 if (_focusableChilds.size() == 0) {
502 _currentIndex = -1;
503 } else {
504 Collections.sort(_focusableChilds, new WindowWidgetComparator());
505 _currentIndex = 0;
506 ((Widget)_focusableChilds.elementAt(0)).setFocus(true);
507 }
508 }
509
510
511 /**
512 * The method computes new window's layout.
513 * The method must already be called, if anything on the window building
514 * is changed, for example, an widget is removed or isn't more focusable
515 * ( because not visible or other ).
516 */
517 public void pack() {
518 cutIfNeeded();
519 configureRootPanel();
520 _root.pack();
521 loadFocusableChilds();
522 loadShortcuts();
523
524 }
525
526
527 private void cutIfNeeded() {
528 int maxWidth = Toolkit.getScreenWidth()-_rect.getX()-(_hasShadow?1:0);
529 int maxHeight = Toolkit.getScreenHeight()-_rect.getY()-(_hasShadow?1:0);
530
531
532 if (_rect.getWidth() > maxWidth) {
533 _rect.setWidth(maxWidth);
534 }
535
536 if (_rect.getHeight() > maxHeight) {
537 _rect.setHeight(maxHeight);
538 }
539
540
541
542 }
543
544
545
546 /**
547 * @return the root panel of the window
548 */
549 public Panel getRootPanel() {
550 //Ein kommentar
551 return _root;
552 }
553
554
555 /**
556 * Sets the root panel of the window. This is the top most widget container in the
557 * window's widget hierarchy. It occupies the entire window out of the border (if exists ).
558 */
559
560 public void setRootPanel(Panel root) {
561 _root = root;
562 _root.setWindow(this);
563 }
564
565
566 private void drawThingsIfNeeded() {
567 if (_border) {
568 Toolkit.drawBorder(_rect, getBorderColors());
569 }
570
571 paintTitle();
572
573 if (hasShadow()) {
574 Toolkit.drawRectangle(_rect.getX()+_rect.getWidth(),
575 _rect.getY()+1,
576 1,
577 _rect.getHeight(), getShadowColors());
578 Toolkit.drawRectangle(_rect.getX()+1,
579 _rect.getY()+_rect.getHeight(),
580 _rect.getWidth(),
581 1, getShadowColors());
582
583 }
584 }
585
586
587 private void paintTitle() {
588 if (_title!=null) {
589 CharColor color = getTitleColors();
590 Toolkit.printString( _title, _rect.getX()+(_rect.getWidth()-_title.length())/2,_rect.getY(), color);
591 }
592 }
593
594
595
596 private static CharColor __defaultBorderColors = new CharColor(CharColor.WHITE, CharColor.BLACK);
597 private CharColor _borderColors = getDefaultBorderColors();
598
599 public CharColor getDefaultBorderColors() {
600 return __defaultBorderColors;
601 }
602
603
604 public CharColor getBorderColors() {
605 return _borderColors;
606 }
607
608
609 public void setBorderColors(CharColor colors) {
610 _borderColors = colors;
611 }
612
613 /**
614 * Normaler title
615 */
616
617
618 private static CharColor __defaultTitleColors = new CharColor(CharColor.WHITE, CharColor.RED);
619 private CharColor _titleColors = getDefaultTitleColors();
620
621 public CharColor getDefaultTitleColors() {
622 return __defaultTitleColors;
623 }
624
625
626 public CharColor getTitleColors() {
627 return _titleColors;
628 }
629
630
631 public void setTitleColors(CharColor colors) {
632 _titleColors = colors;
633 }
634
635 /**
636 * The method defines, whether the window is to paint with a shadow
637 *
638 * @param value true if a shadow is to paint, false in othe case
639 */
640 public void setShadow(boolean value) {
641 _hasShadow=value;
642 }
643
644
645 boolean hasShadow() {
646 return _hasShadow;
647 }
648
649
650 private static CharColor __shadowColors = new CharColor(CharColor.BLACK, CharColor.BLACK);
651
652 private CharColor getShadowColors() {
653 return __shadowColors;
654 }
655
656
657 //Listener-Zeugs
658
659 private WindowListenerManager _listenerManager = new WindowListenerManager();
660
661 /**
662 * The method adds a listener to the window
663 *
664 * @param listener the listener to add
665 */
666 public void addListener(WindowListener listener) {
667 _listenerManager.addListener(listener);
668 }
669
670
671 /**
672 * The method remove a listener from the window
673 *
674 * @param listener the listener to remove
675 */
676 public void removeListener(WindowListener listener) {
677 _listenerManager.removeListener(listener);
678 }
679
680
681 /**
682 * The method is called, if the window gets focus.
683 */
684 protected void activate() {
685 _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.ACTIVATED));
686 }
687
688
689 /**
690 * The method is called, if the window loses focus.
691 */
692 protected void deactivate() {
693 _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.DEACTIVATED));
694 }
695
696 /**
697 * The method is called, if the window is closed.
698 */
699 protected void closed() {
700 _closed = true;
701 _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.CLOSED));
702 }
703
704
705
706 protected void resize(int width, int height) {
707 _rect.setWidth(width);
708 _rect.setHeight(height);
709 }
710
711
712
713
714
715
716
717
718
719
720
721
722 }
723
724
725 class WindowWidgetComparator implements Comparator {
726
727
728
729 // methods to compare widget positions
730 static boolean toTheLeftOf(Widget widget1, Widget widget2) {
731 Rectangle rect1 = widget1.getRectangle();
732 Rectangle rect2 = widget2.getRectangle();
733 boolean result = ((rect1.getX()+rect1.getWidth()-1) < rect2.getX());
734 return result;
735 }
736
737
738 static boolean toTheRightOf(Widget widget1, Widget widget2) {
739 Rectangle rect1 = widget1.getRectangle();
740 Rectangle rect2 = widget2.getRectangle();
741 boolean result = ((rect1.getX()) > (rect2.getX()+rect2.getWidth()-1));
742 return result;
743 }
744
745 static boolean atTheTopOf(Widget widget1, Widget widget2) {
746 Rectangle rect1 = widget1.getRectangle();
747 Rectangle rect2 = widget2.getRectangle();
748 boolean result = ((rect1.getY()+rect1.getHeight()-1) < rect2.getY());
749 return result;
750 }
751
752 static boolean atTheBottomOf(Widget widget1, Widget widget2) {
753 Rectangle rect1 = widget1.getRectangle();
754 Rectangle rect2 = widget2.getRectangle();
755 boolean result = ((rect1.getY()) > (rect2.getY()+rect2.getHeight()-1));
756 return result;
757 }
758
759 static int getDistance(Widget widget1, Widget widget2) {
760 Rectangle rect1 = widget1.getRectangle();
761 Rectangle rect2 = widget2.getRectangle();
762 int x1 = rect1.getX()+(rect1.getWidth()/2);
763 int y1 = rect1.getY()+(rect1.getHeight()/2);
764 int x2 = rect2.getX()+(rect2.getWidth()/2);
765 int y2 = rect2.getY()+(rect2.getHeight()/2);
766
767 return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
768
769 }
770
771
772 public int compare(Object obj1, Object obj2) {
773 if ((!(obj1 instanceof Widget)) || (!(obj2 instanceof Widget))) {
774 throw new RuntimeException("unknown classes to compare");
775 }
776
777 Widget widget1 = (Widget)obj1;
778 Widget widget2 = (Widget)obj2;
779
780 int result = 0;
781
782 if (atTheTopOf(widget1, widget2)) {
783 result = -1;
784 } else if (atTheBottomOf(widget1, widget2)) {
785 result = 1;
786 } else {
787 if (toTheLeftOf(widget1, widget2)) {
788 result = -1;
789 } else if (toTheRightOf(widget1, widget2)) {
790 result = 1;
791 } else {
792 result = 0;
793 }
794 }
795
796
797 return result;
798
799 }
800
801 }