#47 setfont only in swing thread
[fanfix.git] / examples / JexerTilingWindowManager.java
1 import jexer.TApplication;
2 import jexer.TTerminalWindow;
3 import jexer.TWindow;
4 import jexer.event.TKeypressEvent;
5 import jexer.event.TMenuEvent;
6 import jexer.event.TMouseEvent;
7 import jexer.event.TResizeEvent;
8 import jexer.menu.TMenu;
9
10 /**
11 * Implements a simple tiling window manager. A root non-moveable
12 * non-resizable terminal window is created first, which can be split
13 * horizontally or vertically. Each new window retains a reference to its
14 * "parent", and upon closing resizes that parent back to its original size.
15 *
16 * This example shows what can be done with minimal changes to stock Jexer
17 * widgets. You will quickly see that closing a "parent" tile does not cause
18 * the "child" tile to resize. You could make a real subclass of
19 * TTerminalWindow that has extra fields and/or communicates more with
20 * JexerTilingWindowManager to get full coverage of tile creation,
21 * destruction, placement, movement, and so on.
22 */
23 public class JexerTilingWindowManager extends TApplication {
24
25 /**
26 * Menu item: split the terminal vertically.
27 */
28 private static final int MENU_SPLIT_VERTICAL = 2000;
29
30 /**
31 * Menu item: split the terminal horizontally.
32 */
33 private static final int MENU_SPLIT_HORIZONTAL = 2001;
34
35 /**
36 * Main entry point.
37 */
38 public static void main(String [] args) throws Exception {
39 // For this application, we must use ptypipe so that the tile shells
40 // can be aware of their size.
41 System.setProperty("jexer.TTerminal.ptypipe", "true");
42
43 JexerTilingWindowManager jtwm = new JexerTilingWindowManager();
44 (new Thread(jtwm)).start();
45 }
46
47 /**
48 * Public constructor chooses the ECMA-48 / Xterm backend.
49 */
50 public JexerTilingWindowManager() throws Exception {
51 super(BackendType.XTERM);
52
53 // The stock tool menu has items for redrawing the screen, opening
54 // images, and (when using the Swing backend) setting the font.
55 addToolMenu();
56
57 // We will have one menu containing a mix of new and stock commands
58 TMenu tileMenu = addMenu("&Tile");
59
60 // New commands for this example: split vertical and horizontal.
61 tileMenu.addItem(MENU_SPLIT_VERTICAL, "&Vertical Split");
62 tileMenu.addItem(MENU_SPLIT_HORIZONTAL, "&Horizontal Split");
63
64 // Stock commands: a new shell with resizable window, previous, next,
65 // close, and exit program.
66 tileMenu.addItem(TMenu.MID_SHELL, "&Floating");
67 tileMenu.addSeparator();
68 tileMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS);
69 tileMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT);
70 tileMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE);
71 tileMenu.addSeparator();
72 tileMenu.addDefaultItem(TMenu.MID_EXIT);
73
74 // Spin up the root tile
75 TTerminalWindow rootTile = makeTile(0, 0, getScreen().getWidth(),
76 getDesktopBottom() - 1, null);
77
78 // Let's add some bling! Enable focus-follows-mouse.
79 setFocusFollowsMouse(true);
80 }
81
82 /**
83 * Process menu events.
84 */
85 @Override
86 protected boolean onMenu(TMenuEvent event) {
87 if (event.getId() == MENU_SPLIT_VERTICAL) {
88 splitVertical();
89 return true;
90 }
91 if (event.getId() == MENU_SPLIT_HORIZONTAL) {
92 splitHorizontal();
93 return true;
94 }
95
96 return super.onMenu(event);
97 }
98
99 /**
100 * Perform the vertical split.
101 */
102 private void splitVertical() {
103 TWindow window = getActiveWindow();
104 if (!(window instanceof TTerminalWindow)) {
105 return;
106 }
107
108 TTerminalWindow tile = (TTerminalWindow) window;
109 // Give the extra column to the new tile.
110 int newWidth = (tile.getWidth() + 1) / 2;
111 int newY = tile.getY() - 1;
112 int newX = tile.getX() + tile.getWidth() - newWidth;
113 makeTile(newX, newY, newWidth, tile.getHeight(), tile);
114 tile.setWidth(tile.getWidth() - newWidth);
115 tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
116 tile.getWidth(), tile.getHeight()));
117 }
118
119 /**
120 * Perform the horizontal split.
121 */
122 private void splitHorizontal() {
123 TWindow window = getActiveWindow();
124 if (!(window instanceof TTerminalWindow)) {
125 return;
126 }
127
128 TTerminalWindow tile = (TTerminalWindow) window;
129 // Give the extra row to the new tile.
130 int newHeight = (tile.getHeight() + 1) / 2;
131 int newY = tile.getY() - 1 + tile.getHeight() - newHeight;
132 int newX = tile.getX();
133 makeTile(newX, newY, tile.getWidth(), newHeight, tile);
134 tile.setHeight(tile.getHeight() - newHeight);
135 tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
136 tile.getWidth(), tile.getHeight()));
137 }
138
139 /**
140 * Create a non-resizable non-movable terminal window.
141 *
142 * @param x the column number to place the top-left corner at. 0 is the
143 * left-most column.
144 * @param y the row number to place the top-left corner at. 0 is the
145 * top-most column.
146 * @param width the width of the window
147 * @param height the height of the window
148 * @param otherTile the other tile to resize when this window closes
149 */
150 private TTerminalWindow makeTile(int x, int y, int width, int height,
151 final TTerminalWindow otherTile) {
152
153 // We pass flags to disable the zoom (maximize) button, disable
154 // "smart" window placement, and set the specific location.
155 TTerminalWindow tile = new TTerminalWindow(this, x, y,
156 TWindow.NOZOOMBOX | TWindow.ABSOLUTEXY,
157 new String[] { "/bin/bash", "--login" }, true) {
158
159 /**
160 * When this terminal closes, if otherTile is defined then resize
161 * it to overcover me.
162 */
163 @Override
164 public void onClose() {
165 super.onClose();
166
167 if (otherTile != null) {
168 if (otherTile.getX() != getX()) {
169 // Undo the vertical split
170 otherTile.setX(Math.min(otherTile.getX(), getX()));
171 otherTile.setWidth(otherTile.getWidth() + getWidth());
172 }
173 if (otherTile.getY() != getY()) {
174 otherTile.setY(Math.min(otherTile.getY(), getY()));
175 otherTile.setHeight(otherTile.getHeight() + getHeight());
176 }
177 otherTile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
178 otherTile.getWidth(), otherTile.getHeight()));
179 }
180 }
181
182 /**
183 * Prevent the user from resizing or moving this window.
184 */
185 @Override
186 public void onMouseDown(final TMouseEvent mouse) {
187 super.onMouseDown(mouse);
188 stopMovements();
189 }
190
191 /**
192 * Prevent the user from resizing or moving this window.
193 */
194 @Override
195 public void onKeypress(final TKeypressEvent keypress) {
196 super.onKeypress(keypress);
197 stopMovements();
198 }
199
200 /**
201 * Permit the user to use all of the menu items.
202 */
203 @Override
204 public void onIdle() {
205 super.onIdle();
206 removeShortcutKeypress(jexer.TKeypress.kbAltT);
207 removeShortcutKeypress(jexer.TKeypress.kbF6);
208 }
209
210 };
211
212 // The initial window size was stock VT100 80x24. Change that now,
213 // and then call onResize() to notify ptypipe to set the shell's
214 // window size.
215 tile.setWidth(width);
216 tile.setHeight(height);
217 tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
218 tile.getWidth(), tile.getHeight()));
219
220 return tile;
221 }
222
223 }