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
.TMouseEvent
;
34 import jexer
.event
.TResizeEvent
;
37 * TSplitPane contains two widgets with a draggable horizontal or vertical
40 public class TSplitPane
extends TWidget
{
42 // ------------------------------------------------------------------------
43 // Variables --------------------------------------------------------------
44 // ------------------------------------------------------------------------
47 * If true, split vertically. If false, split horizontally.
49 private boolean vertical
= true;
52 * The location of the split bar, either as a column number for vertical
53 * split or a row number for horizontal split.
55 private int split
= 0;
58 * The widget on the left side.
63 * The widget on the right side.
65 private TWidget right
;
68 * The widget on the top side.
73 * The widget on the bottom side.
75 private TWidget bottom
;
78 * If true, we are in the middle of a split move.
80 private boolean inSplitMove
= false;
83 * The last seen mouse position.
85 private TMouseEvent mouse
;
87 // ------------------------------------------------------------------------
88 // Constructors -----------------------------------------------------------
89 // ------------------------------------------------------------------------
94 * @param parent parent widget
95 * @param x column relative to parent
96 * @param y row relative to parent
97 * @param width width of widget
98 * @param height height of widget
99 * @param vertical if true, split vertically
101 public TSplitPane(final TWidget parent
, final int x
, final int y
,
102 final int width
, final int height
, final boolean vertical
) {
104 super(parent
, x
, y
, width
, height
);
106 this.vertical
= vertical
;
110 // ------------------------------------------------------------------------
111 // Event handlers ---------------------------------------------------------
112 // ------------------------------------------------------------------------
115 * Handle window/screen resize events.
117 * @param event resize event
120 public void onResize(final TResizeEvent event
) {
121 if (event
.getType() == TResizeEvent
.Type
.WIDGET
) {
123 super.onResize(event
);
125 if (vertical
&& (split
>= getWidth() - 2)) {
127 } else if (!vertical
&& (split
>= getHeight() - 2)) {
136 * Handle mouse button presses.
138 * @param mouse mouse button event
141 public void onMouseDown(final TMouseEvent mouse
) {
146 if (mouse
.isMouse1()) {
148 inSplitMove
= (mouse
.getAbsoluteX() - getAbsoluteX() == split
);
150 inSplitMove
= (mouse
.getAbsoluteY() - getAbsoluteY() == split
);
157 // I didn't take it, pass it on to my children
158 super.onMouseDown(mouse
);
162 * Handle mouse button releases.
164 * @param mouse mouse button release event
167 public void onMouseUp(final TMouseEvent mouse
) {
170 if (inSplitMove
&& mouse
.isMouse1()) {
176 // I didn't take it, pass it on to my children
177 super.onMouseUp(mouse
);
181 * Handle mouse movements.
183 * @param mouse mouse motion event
186 public void onMouseMotion(final TMouseEvent mouse
) {
189 if ((mouse
.getAbsoluteX() - getAbsoluteX() < 0)
190 || (mouse
.getAbsoluteX() - getAbsoluteX() >= getWidth())
191 || (mouse
.getAbsoluteY() - getAbsoluteY() < 0)
192 || (mouse
.getAbsoluteY() - getAbsoluteY() >= getHeight())
194 // Mouse has travelled out of my window.
200 split
= mouse
.getAbsoluteX() - getAbsoluteX();
201 split
= Math
.min(Math
.max(1, split
), getWidth() - 2);
203 split
= mouse
.getAbsoluteY() - getAbsoluteY();
204 split
= Math
.min(Math
.max(1, split
), getHeight() - 2);
210 // I didn't take it, pass it on to my children
211 super.onMouseMotion(mouse
);
214 // ------------------------------------------------------------------------
215 // TWidget ----------------------------------------------------------------
216 // ------------------------------------------------------------------------
223 CellAttributes attr
= getTheme().getColor("tsplitpane");
225 vLineXY(split
, 0, getHeight(), GraphicsChars
.WINDOW_SIDE
, attr
);
227 // Draw intersections of children
228 if ((left
instanceof TSplitPane
)
229 && (((TSplitPane
) left
).vertical
== false)
230 && (right
instanceof TSplitPane
)
231 && (((TSplitPane
) right
).vertical
== false)
232 && (((TSplitPane
) left
).split
== ((TSplitPane
) right
).split
)
234 putCharXY(split
, ((TSplitPane
) left
).split
, '\u253C', attr
);
236 if ((left
instanceof TSplitPane
)
237 && (((TSplitPane
) left
).vertical
== false)
239 putCharXY(split
, ((TSplitPane
) left
).split
, '\u2524', attr
);
241 if ((right
instanceof TSplitPane
)
242 && (((TSplitPane
) right
).vertical
== false)
244 putCharXY(split
, ((TSplitPane
) right
).split
, '\u251C',
250 && (mouse
.getAbsoluteX() == getAbsoluteX() + split
)
251 && (mouse
.getAbsoluteY() >= getAbsoluteY()) &&
252 (mouse
.getAbsoluteY() < getAbsoluteY() + getHeight())
254 putCharXY(split
, mouse
.getAbsoluteY() - getAbsoluteY(),
258 hLineXY(0, split
, getWidth(), GraphicsChars
.SINGLE_BAR
, attr
);
260 // Draw intersections of children
261 if ((top
instanceof TSplitPane
)
262 && (((TSplitPane
) top
).vertical
== true)
263 && (bottom
instanceof TSplitPane
)
264 && (((TSplitPane
) bottom
).vertical
== true)
265 && (((TSplitPane
) top
).split
== ((TSplitPane
) bottom
).split
)
267 putCharXY(((TSplitPane
) top
).split
, split
, '\u253C', attr
);
269 if ((top
instanceof TSplitPane
)
270 && (((TSplitPane
) top
).vertical
== true)
272 putCharXY(((TSplitPane
) top
).split
, split
, '\u2534', attr
);
274 if ((bottom
instanceof TSplitPane
)
275 && (((TSplitPane
) bottom
).vertical
== true)
277 putCharXY(((TSplitPane
) bottom
).split
, split
, '\u252C',
283 && (mouse
.getAbsoluteY() == getAbsoluteY() + split
)
284 && (mouse
.getAbsoluteX() >= getAbsoluteX()) &&
285 (mouse
.getAbsoluteX() < getAbsoluteX() + getWidth())
287 putCharXY(mouse
.getAbsoluteX() - getAbsoluteX(), split
,
295 * Generate a human-readable string for this widget.
297 * @return a human-readable string
300 public String
toString() {
301 return String
.format("%s(%8x) %s position (%d, %d) geometry %dx%d " +
302 "split %d left %s(%8x) right %s(%8x) top %s(%8x) bottom %s(%8x) " +
303 "active %s enabled %s visible %s", getClass().getName(),
304 hashCode(), (vertical ?
"VERTICAL" : "HORIZONTAL"),
305 getX(), getY(), getWidth(), getHeight(), split
,
306 (left
== null ?
"null" : left
.getClass().getName()),
307 (left
== null ?
0 : left
.hashCode()),
308 (right
== null ?
"null" : right
.getClass().getName()),
309 (right
== null ?
0 : right
.hashCode()),
310 (top
== null ?
"null" : top
.getClass().getName()),
311 (top
== null ?
0 : top
.hashCode()),
312 (bottom
== null ?
"null" : bottom
.getClass().getName()),
313 (bottom
== null ?
0 : bottom
.hashCode()),
314 isActive(), isEnabled(), isVisible());
317 // ------------------------------------------------------------------------
318 // TSplitPane -------------------------------------------------------------
319 // ------------------------------------------------------------------------
322 * Get the widget on the left side.
324 * @return the widget on the left, or null if not set
326 public TWidget
getLeft() {
331 * Set the widget on the left side.
333 * @param left the widget to set, or null to remove
335 public void setLeft(final TWidget left
) {
337 throw new IllegalArgumentException("cannot set left on " +
338 "horizontal split pane");
341 if (this.left
!= null) {
348 left
.setParent(this, false);
349 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
354 * Get the widget on the right side.
356 * @return the widget on the right, or null if not set
358 public TWidget
getRight() {
363 * Set the widget on the right side.
365 * @param right the widget to set, or null to remove
367 public void setRight(final TWidget right
) {
369 throw new IllegalArgumentException("cannot set right on " +
370 "horizontal split pane");
373 if (this.right
!= null) {
380 right
.setParent(this, false);
381 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
386 * Get the widget on the top side.
388 * @return the widget on the top, or null if not set
390 public TWidget
getTop() {
395 * Set the widget on the top side.
397 * @param top the widget to set, or null to remove
399 public void setTop(final TWidget top
) {
401 throw new IllegalArgumentException("cannot set top on vertical " +
405 if (this.top
!= null) {
412 top
.setParent(this, false);
413 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
418 * Get the widget on the bottom side.
420 * @return the widget on the bottom, or null if not set
422 public TWidget
getBottom() {
427 * Set the widget on the bottom side.
429 * @param bottom the widget to set, or null to remove
431 public void setBottom(final TWidget bottom
) {
433 throw new IllegalArgumentException("cannot set bottom on " +
434 "vertical split pane");
436 if (bottom
== null) {
437 if (this.bottom
!= null) {
443 this.bottom
= bottom
;
444 bottom
.setParent(this, false);
445 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),
450 * Remove a widget, regardless of what pane it is on.
452 * @param widget the widget to remove
454 public void removeWidget(final TWidget widget
) {
455 if (widget
== null) {
456 throw new IllegalArgumentException("cannot remove null widget");
458 if (left
== widget
) {
460 assert(right
!= widget
);
461 assert(top
!= widget
);
462 assert(bottom
!= widget
);
465 if (right
== widget
) {
467 assert(left
!= widget
);
468 assert(top
!= widget
);
469 assert(bottom
!= widget
);
474 assert(left
!= widget
);
475 assert(right
!= widget
);
476 assert(bottom
!= widget
);
479 if (bottom
== widget
) {
481 assert(left
!= widget
);
482 assert(right
!= widget
);
483 assert(top
!= widget
);
486 throw new IllegalArgumentException("widget " + widget
+
487 " not in this split");
491 * Replace a widget, regardless of what pane it is on, with another
494 * @param oldWidget the widget to remove
495 * @param newWidget the widget to replace it with
497 public void replaceWidget(final TWidget oldWidget
,
498 final TWidget newWidget
) {
500 if (oldWidget
== null) {
501 throw new IllegalArgumentException("cannot remove null oldWidget");
503 if (left
== oldWidget
) {
505 assert(right
!= newWidget
);
506 assert(top
!= newWidget
);
507 assert(bottom
!= newWidget
);
510 if (right
== oldWidget
) {
512 assert(left
!= newWidget
);
513 assert(top
!= newWidget
);
514 assert(bottom
!= newWidget
);
517 if (top
== oldWidget
) {
519 assert(left
!= newWidget
);
520 assert(right
!= newWidget
);
521 assert(bottom
!= newWidget
);
524 if (bottom
== oldWidget
) {
525 setBottom(newWidget
);
526 assert(left
!= newWidget
);
527 assert(right
!= newWidget
);
528 assert(top
!= newWidget
);
531 throw new IllegalArgumentException("oldWidget " + oldWidget
+
532 " not in this split");
536 * Layout the two child widgets.
538 private void layoutChildren() {
541 left
.setDimensions(0, 0, split
, getHeight());
542 left
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
543 left
.getWidth(), left
.getHeight()));
546 right
.setDimensions(split
+ 1, 0, getWidth() - split
- 1,
548 right
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
549 right
.getWidth(), right
.getHeight()));
553 top
.setDimensions(0, 0, getWidth(), split
);
554 top
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
555 top
.getWidth(), top
.getHeight()));
557 if (bottom
!= null) {
558 bottom
.setDimensions(0, split
+ 1, getWidth(),
559 getHeight() - split
- 1);
560 bottom
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
,
561 bottom
.getWidth(), bottom
.getHeight()));
567 * Recenter the split to the middle of this split pane.
569 public void center() {
571 split
= getWidth() / 2;
573 split
= getHeight() / 2;
579 * Remove this split, removing the widget specified.
581 * @param widgetToRemove the widget to remove
582 * @param doClose if true, call the close() method before removing the
584 * @return the pane that remains, or null if nothing is retained
586 public TWidget
removeSplit(final TWidget widgetToRemove
,
587 final boolean doClose
) {
591 if ((widgetToRemove
!= left
) && (widgetToRemove
!= right
)) {
592 throw new IllegalArgumentException("widget to remove is not " +
593 "either of the panes in this splitpane");
595 if (widgetToRemove
== left
) {
602 if ((widgetToRemove
!= top
) && (widgetToRemove
!= bottom
)) {
603 throw new IllegalArgumentException("widget to remove is not " +
604 "either of the panes in this splitpane");
606 if (widgetToRemove
== top
) {
613 // Remove me from my parent widget.
614 TWidget myParent
= getParent();
618 if (myParent
instanceof TSplitPane
) {
619 // TSplitPane has a left/right/top/bottom link to me
620 // somewhere, remove it.
621 ((TSplitPane
) myParent
).removeWidget(this);
624 // Nothing is left of either pane. Remove me and bail out.
628 if (myParent
instanceof TSplitPane
) {
629 // TSplitPane has a left/right/top/bottom link to me
630 // somewhere, replace me with keep.
631 ((TSplitPane
) myParent
).replaceWidget(this, keep
);
633 keep
.setParent(myParent
, false);
634 keep
.setDimensions(getX(), getY(), getWidth(), getHeight());
635 keep
.onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, getWidth(),