a9d6cdb3f52760ac60c70dc1606a12b6d4f1eb6f
2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import jexer
.bits
.CellAttributes
;
32 import jexer
.bits
.GraphicsChars
;
33 import jexer
.event
.TMenuEvent
;
34 import jexer
.event
.TMouseEvent
;
35 import jexer
.event
.TResizeEvent
;
36 import jexer
.menu
.TMenu
;
39 * TSplitPane contains two widgets with a draggable horizontal or vertical
42 public class TSplitPane
extends TWidget
{
44 // ------------------------------------------------------------------------
45 // Variables --------------------------------------------------------------
46 // ------------------------------------------------------------------------
49 * If true, split vertically. If false, split horizontally.
51 private boolean vertical
= true;
54 * The location of the split bar, either as a column number for vertical
55 * split or a row number for horizontal split.
57 private int split
= 0;
60 * The widget on the left side.
65 * The widget on the right side.
67 private TWidget right
;
70 * The widget on the top side.
75 * The widget on the bottom side.
77 private TWidget bottom
;
80 * If true, we are in the middle of a split move.
82 private boolean inSplitMove
= false;
85 * The last seen mouse position.
87 private TMouseEvent mouse
;
89 // ------------------------------------------------------------------------
90 // Constructors -----------------------------------------------------------
91 // ------------------------------------------------------------------------
96 * @param parent parent widget
97 * @param x column relative to parent
98 * @param y row relative to parent
99 * @param width width of widget
100 * @param height height of widget
101 * @param vertical if true, split vertically
103 public TSplitPane(final TWidget parent
, final int x
, final int y
,
104 final int width
, final int height
, final boolean vertical
) {
106 super(parent
, x
, y
, width
, height
);
108 this.vertical
= vertical
;
112 // ------------------------------------------------------------------------
113 // Event handlers ---------------------------------------------------------
114 // ------------------------------------------------------------------------
117 * Handle window/screen resize events.
119 * @param event resize event
122 public void onResize(final TResizeEvent event
) {
123 if (event
.getType() == TResizeEvent
.Type
.WIDGET
) {
125 super.onResize(event
);
127 // System.err.println("onResize(): " + toString());
129 if (vertical
&& (split
>= getWidth() - 2)) {
131 } else if (!vertical
&& (split
>= getHeight() - 2)) {
140 * Handle mouse button presses.
142 * @param mouse mouse button event
145 public void onMouseDown(final TMouseEvent mouse
) {
150 if (mouse
.isMouse1()) {
152 inSplitMove
= (mouse
.getAbsoluteX() - getAbsoluteX() == split
);
154 inSplitMove
= (mouse
.getAbsoluteY() - getAbsoluteY() == split
);
161 // I didn't take it, pass it on to my children
162 super.onMouseDown(mouse
);
166 * Handle mouse button releases.
168 * @param mouse mouse button release event
171 public void onMouseUp(final TMouseEvent mouse
) {
174 if (inSplitMove
&& mouse
.isMouse1()) {
176 // System.err.println(toPrettyString());
183 // I didn't take it, pass it on to my children
184 super.onMouseUp(mouse
);
188 * Handle mouse movements.
190 * @param mouse mouse motion event
193 public void onMouseMotion(final TMouseEvent mouse
) {
196 if ((mouse
.getAbsoluteX() - getAbsoluteX() < 0)
197 || (mouse
.getAbsoluteX() - getAbsoluteX() >= getWidth())
198 || (mouse
.getAbsoluteY() - getAbsoluteY() < 0)
199 || (mouse
.getAbsoluteY() - getAbsoluteY() >= getHeight())
201 // Mouse has travelled out of my window.
207 split
= mouse
.getAbsoluteX() - getAbsoluteX();
208 split
= Math
.min(Math
.max(1, split
), getWidth() - 2);
210 split
= mouse
.getAbsoluteY() - getAbsoluteY();
211 split
= Math
.min(Math
.max(1, split
), getHeight() - 2);
217 // I didn't take it, pass it on to my children
218 super.onMouseMotion(mouse
);
221 // ------------------------------------------------------------------------
222 // TWidget ----------------------------------------------------------------
223 // ------------------------------------------------------------------------
230 CellAttributes attr
= getTheme().getColor("tsplitpane");
232 vLineXY(split
, 0, getHeight(), GraphicsChars
.WINDOW_SIDE
, attr
);
233 // TODO: draw intersections of children
236 && (mouse
.getAbsoluteX() == getAbsoluteX() + split
)
237 && (mouse
.getAbsoluteY() >= getAbsoluteY()) &&
238 (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight())
240 putCharXY(split
, mouse
.getAbsoluteY() - getAbsoluteY(),
244 hLineXY(0, split
, getWidth(), GraphicsChars
.SINGLE_BAR
, attr
);
245 // TODO: draw intersections of children
248 && (mouse
.getAbsoluteY() == getAbsoluteY() + split
)
249 && (mouse
.getAbsoluteX() >= getAbsoluteX()) &&
250 (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth())
252 putCharXY(mouse
.getAbsoluteX() - getAbsoluteX(), split
,
260 * Generate a human-readable string for this widget.
262 * @return a human-readable string
265 public String
toString() {
266 return String
.format("%s(%8x) %s position (%d, %d) geometry %dx%d " +
267 "split %d left %s(%8x) right %s(%8x) top %s(%8x) bottom %s(%8x) " +
268 "active %s enabled %s visible %s", getClass().getName(),
269 hashCode(), (vertical ?
"VERTICAL" : "HORIZONTAL"),
270 getX(), getY(), getWidth(), getHeight(), split
,
271 (left
== null ?
"null" : left
.getClass().getName()),
272 (left
== null ?
0 : left
.hashCode()),
273 (right
== null ?
"null" : right
.getClass().getName()),
274 (right
== null ?
0 : right
.hashCode()),
275 (top
== null ?
"null" : top
.getClass().getName()),
276 (top
== null ?
0 : top
.hashCode()),
277 (bottom
== null ?
"null" : bottom
.getClass().getName()),
278 (bottom
== null ?
0 : bottom
.hashCode()),
279 isActive(), isEnabled(), isVisible());
282 // ------------------------------------------------------------------------
283 // TSplitPane -------------------------------------------------------------
284 // ------------------------------------------------------------------------
287 * Get the widget on the left side.
289 * @return the widget on the left, or null if not set
291 public TWidget
getLeft() {
296 * Set the widget on the left side.
298 * @param left the widget to set, or null to remove
300 public void setLeft(final TWidget left
) {
302 throw new IllegalArgumentException("cannot set left on " +
303 "horizontal split pane");
306 if (this.left
!= null) {
313 left
.setParent(this, false);
314 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
319 * Get the widget on the right side.
321 * @return the widget on the right, or null if not set
323 public TWidget
getRight() {
328 * Set the widget on the right side.
330 * @param right the widget to set, or null to remove
332 public void setRight(final TWidget right
) {
334 throw new IllegalArgumentException("cannot set right on " +
335 "horizontal split pane");
338 if (this.right
!= null) {
345 right
.setParent(this, false);
346 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
351 * Get the widget on the top side.
353 * @return the widget on the top, or null if not set
355 public TWidget
getTop() {
360 * Set the widget on the top side.
362 * @param top the widget to set, or null to remove
364 public void setTop(final TWidget top
) {
366 throw new IllegalArgumentException("cannot set top on vertical " +
370 if (this.top
!= null) {
377 top
.setParent(this, false);
378 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
383 * Get the widget on the bottom side.
385 * @return the widget on the bottom, or null if not set
387 public TWidget
getBottom() {
392 * Set the widget on the bottom side.
394 * @param bottom the widget to set, or null to remove
396 public void setBottom(final TWidget bottom
) {
398 throw new IllegalArgumentException("cannot set bottom on " +
399 "vertical split pane");
401 if (bottom
== null) {
402 if (this.bottom
!= null) {
408 this.bottom
= bottom
;
409 bottom
.setParent(this, false);
410 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
415 * Remove a widget, regardless of what pane it is on.
417 * @param widget the widget to remove
419 public void removeWidget(final TWidget widget
) {
420 if (widget
== null) {
421 throw new IllegalArgumentException("cannot remove null widget");
423 if (left
== widget
) {
425 assert(right
!= widget
);
426 assert(top
!= widget
);
427 assert(bottom
!= widget
);
430 if (right
== widget
) {
432 assert(left
!= widget
);
433 assert(top
!= widget
);
434 assert(bottom
!= widget
);
439 assert(left
!= widget
);
440 assert(right
!= widget
);
441 assert(bottom
!= widget
);
444 if (bottom
== widget
) {
446 assert(left
!= widget
);
447 assert(right
!= widget
);
448 assert(top
!= widget
);
451 throw new IllegalArgumentException("widget " + widget
+
452 " not in this split");
456 * Replace a widget, regardless of what pane it is on, with another
459 * @param oldWidget the widget to remove
460 * @param newWidget the widget to replace it with
462 public void replaceWidget(final TWidget oldWidget
,
463 final TWidget newWidget
) {
465 if (oldWidget
== null) {
466 throw new IllegalArgumentException("cannot remove null oldWidget");
468 if (left
== oldWidget
) {
470 assert(right
!= newWidget
);
471 assert(top
!= newWidget
);
472 assert(bottom
!= newWidget
);
475 if (right
== oldWidget
) {
477 assert(left
!= newWidget
);
478 assert(top
!= newWidget
);
479 assert(bottom
!= newWidget
);
482 if (top
== oldWidget
) {
484 assert(left
!= newWidget
);
485 assert(right
!= newWidget
);
486 assert(bottom
!= newWidget
);
489 if (bottom
== oldWidget
) {
490 setBottom(newWidget
);
491 assert(left
!= newWidget
);
492 assert(right
!= newWidget
);
493 assert(top
!= newWidget
);
496 throw new IllegalArgumentException("oldWidget " + oldWidget
+
497 " not in this split");
501 * Layout the two child widgets.
503 private void layoutChildren() {
505 // System.err.println("layoutChildren(): " + toString());
509 left
.setDimensions(0, 0, split
, getHeight());
510 left
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
511 left
.getWidth(), left
.getHeight()));
512 // System.err.println(" move/size left: " + left.toString());
515 right
.setDimensions(split
+ 1, 0, getWidth() - split
- 1,
517 right
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
518 right
.getWidth(), right
.getHeight()));
519 // System.err.println(" move/size right: " + right.toString());
523 top
.setDimensions(0, 0, getWidth(), split
);
524 top
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
525 top
.getWidth(), top
.getHeight()));
526 // System.err.println(" move/size top: " + top.toString());
528 if (bottom
!= null) {
529 bottom
.setDimensions(0, split
+ 1, getWidth(),
530 getHeight() - split
- 1);
531 bottom
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
532 bottom
.getWidth(), bottom
.getHeight()));
533 // System.err.println(" move/size bottom: " + bottom.toString());
539 * Recenter the split to the middle of this split pane.
541 public void center() {
543 split
= getWidth() / 2;
545 split
= getHeight() / 2;
551 * Remove this split, removing the widget specified.
553 * @param widgetToRemove the widget to remove
554 * @param doClose if true, call the close() method before removing the
556 * @return the pane that remains, or null if nothing is retained
558 public TWidget
removeSplit(final TWidget widgetToRemove
,
559 final boolean doClose
) {
563 if ((widgetToRemove
!= left
) && (widgetToRemove
!= right
)) {
564 throw new IllegalArgumentException("widget to remove is not " +
565 "either of the panes in this splitpane");
567 if (widgetToRemove
== left
) {
574 if ((widgetToRemove
!= top
) && (widgetToRemove
!= bottom
)) {
575 throw new IllegalArgumentException("widget to remove is not " +
576 "either of the panes in this splitpane");
578 if (widgetToRemove
== top
) {
585 // Remove me from my parent widget.
586 TWidget myParent
= getParent();
590 if (myParent
instanceof TSplitPane
) {
591 // TSplitPane has a left/right/top/bottom link to me
592 // somewhere, remove it.
593 ((TSplitPane
) myParent
).removeWidget(this);
596 // Nothing is left of either pane. Remove me and bail out.
600 if (myParent
instanceof TSplitPane
) {
601 // TSplitPane has a left/right/top/bottom link to me
602 // somewhere, replace me with keep.
603 ((TSplitPane
) myParent
).replaceWidget(this, keep
);
605 keep
.setParent(myParent
, false);
606 keep
.setDimensions(getX(), getY(), getWidth(), getHeight());
607 keep
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
611 // System.err.println("\nAfter removeSplit():\n" + myParent.toPrettyString());