Prep for 2019 release
[fanfix.git] / src / jexer / TFileOpenBox.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.util.List;
34 import java.util.ResourceBundle;
35
36 import jexer.backend.SwingTerminal;
37 import jexer.bits.GraphicsChars;
38 import jexer.event.TKeypressEvent;
39 import jexer.ttree.TDirectoryTreeItem;
40 import jexer.ttree.TTreeItem;
41 import jexer.ttree.TTreeViewWidget;
42 import static jexer.TKeypress.*;
43
44 /**
45 * TFileOpenBox is a system-modal dialog for selecting a file to open. Call
46 * it like:
47 *
48 * <pre>
49 * {@code
50 * filename = application.fileOpenBox("/path/to/file.ext",
51 * TFileOpenBox.Type.OPEN);
52 * if (filename != null) {
53 * ... the user selected a file, go open it ...
54 * }
55 * }
56 * </pre>
57 *
58 */
59 public class TFileOpenBox extends TWindow {
60
61 /**
62 * Translated strings.
63 */
64 private static final ResourceBundle i18n = ResourceBundle.getBundle(TFileOpenBox.class.getName());
65
66 // ------------------------------------------------------------------------
67 // Constants --------------------------------------------------------------
68 // ------------------------------------------------------------------------
69
70 /**
71 * TFileOpenBox can be called for either Open or Save actions.
72 */
73 public enum Type {
74 /**
75 * Button will be labeled "Open".
76 */
77 OPEN,
78
79 /**
80 * Button will be labeled "Save".
81 */
82 SAVE,
83
84 /**
85 * Button will be labeled "Select".
86 */
87 SELECT
88 }
89
90 // ------------------------------------------------------------------------
91 // Variables --------------------------------------------------------------
92 // ------------------------------------------------------------------------
93
94 /**
95 * String to return, or null if the user canceled.
96 */
97 private String filename = null;
98
99 /**
100 * The left-side tree view pane.
101 */
102 private TTreeViewWidget treeView;
103
104 /**
105 * The data behind treeView.
106 */
107 private TDirectoryTreeItem treeViewRoot;
108
109 /**
110 * The right-side directory list pane.
111 */
112 private TDirectoryList directoryList;
113
114 /**
115 * The top row text field.
116 */
117 private TField entryField;
118
119 /**
120 * The Open or Save button.
121 */
122 private TButton openButton;
123
124 /**
125 * The type of box this is (OPEN, SAVE, or SELECT).
126 */
127 private Type type = Type.OPEN;
128
129 // ------------------------------------------------------------------------
130 // Constructors -----------------------------------------------------------
131 // ------------------------------------------------------------------------
132
133 /**
134 * Public constructor. The file open box will be centered on screen.
135 *
136 * @param application the TApplication that manages this window
137 * @param path path of selected file
138 * @param type one of the Type constants
139 * @throws IOException of a java.io operation throws
140 */
141 public TFileOpenBox(final TApplication application, final String path,
142 final Type type) throws IOException {
143
144 this(application, path, type, null);
145 }
146
147 /**
148 * Public constructor. The file open box will be centered on screen.
149 *
150 * @param application the TApplication that manages this window
151 * @param path path of selected file
152 * @param type one of the Type constants
153 * @param filters a list of strings that files must match to be displayed
154 * @throws IOException of a java.io operation throws
155 */
156 public TFileOpenBox(final TApplication application, final String path,
157 final Type type, final List<String> filters) throws IOException {
158
159 // Register with the TApplication
160 super(application, "", 0, 0, 76, 22, MODAL);
161
162 // Add text field
163 entryField = addField(1, 1, getWidth() - 4, false,
164 (new File(path)).getCanonicalPath(),
165 new TAction() {
166 public void DO() {
167 try {
168 checkFilename(entryField.getText());
169 } catch (IOException e) {
170 // If the backend is Swing, we can emit the stack
171 // trace to stderr. Otherwise, just squash it.
172 if (getScreen() instanceof SwingTerminal) {
173 e.printStackTrace();
174 }
175 }
176 }
177 }, null);
178 entryField.onKeypress(new TKeypressEvent(kbEnd));
179
180 // Add directory treeView
181 treeView = addTreeViewWidget(1, 3, 30, getHeight() - 6,
182 new TAction() {
183 public void DO() {
184 TTreeItem item = treeView.getSelected();
185 File selectedDir = ((TDirectoryTreeItem) item).getFile();
186 try {
187 directoryList.setPath(selectedDir.getCanonicalPath());
188 if (type == Type.OPEN) {
189 openButton.setEnabled(false);
190 }
191 activate(treeView);
192 } catch (IOException e) {
193 // If the backend is Swing, we can emit the stack
194 // trace to stderr. Otherwise, just squash it.
195 if (getScreen() instanceof SwingTerminal) {
196 e.printStackTrace();
197 }
198 }
199 }
200 }
201 );
202 treeViewRoot = new TDirectoryTreeItem(treeView, path, true);
203
204 // Add directory files list
205 directoryList = addDirectoryList(path, 34, 3, 28, getHeight() - 6,
206 new TAction() {
207 public void DO() {
208 try {
209 File newPath = directoryList.getPath();
210 entryField.setText(newPath.getCanonicalPath());
211 entryField.onKeypress(new TKeypressEvent(kbEnd));
212 openButton.setEnabled(true);
213 activate(entryField);
214 checkFilename(entryField.getText());
215 } catch (IOException e) {
216 // If the backend is Swing, we can emit the stack
217 // trace to stderr. Otherwise, just squash it.
218 if (getScreen() instanceof SwingTerminal) {
219 e.printStackTrace();
220 }
221 }
222 }
223 },
224 new TAction() {
225 public void DO() {
226 try {
227 File newPath = directoryList.getPath();
228 entryField.setText(newPath.getCanonicalPath());
229 entryField.onKeypress(new TKeypressEvent(kbEnd));
230 openButton.setEnabled(true);
231 activate(entryField);
232 } catch (IOException e) {
233 // If the backend is Swing, we can emit the stack
234 // trace to stderr. Otherwise, just squash it.
235 if (getScreen() instanceof SwingTerminal) {
236 e.printStackTrace();
237 }
238 }
239 }
240 },
241 filters);
242
243 String openLabel = "";
244 switch (type) {
245 case OPEN:
246 openLabel = i18n.getString("openButton");
247 setTitle(i18n.getString("openTitle"));
248 break;
249 case SAVE:
250 openLabel = i18n.getString("saveButton");
251 setTitle(i18n.getString("saveTitle"));
252 break;
253 case SELECT:
254 openLabel = i18n.getString("selectButton");
255 setTitle(i18n.getString("selectTitle"));
256 break;
257 default:
258 throw new IllegalArgumentException("Invalid type: " + type);
259 }
260 this.type = type;
261
262 // Setup button actions
263 openButton = addButton(openLabel, this.getWidth() - 12, 3,
264 new TAction() {
265 public void DO() {
266 try {
267 checkFilename(entryField.getText());
268 } catch (IOException e) {
269 // If the backend is Swing, we can emit the stack
270 // trace to stderr. Otherwise, just squash it.
271 if (getScreen() instanceof SwingTerminal) {
272 e.printStackTrace();
273 }
274 }
275 }
276 }
277 );
278 if (type == Type.OPEN) {
279 openButton.setEnabled(false);
280 }
281
282 addButton(i18n.getString("cancelButton"), getWidth() - 12, 5,
283 new TAction() {
284 public void DO() {
285 filename = null;
286 getApplication().closeWindow(TFileOpenBox.this);
287 }
288 }
289 );
290
291 // Default to the directory list
292 activate(directoryList);
293
294 // Set the secondaryFiber to run me
295 getApplication().enableSecondaryEventReceiver(this);
296
297 // Yield to the secondary thread. When I come back from the
298 // constructor response will already be set.
299 getApplication().yield();
300 }
301
302 // ------------------------------------------------------------------------
303 // Event handlers ---------------------------------------------------------
304 // ------------------------------------------------------------------------
305
306 /**
307 * Handle keystrokes.
308 *
309 * @param keypress keystroke event
310 */
311 @Override
312 public void onKeypress(final TKeypressEvent keypress) {
313 // Escape - behave like cancel
314 if (keypress.equals(kbEsc)) {
315 // Close window
316 filename = null;
317 getApplication().closeWindow(this);
318 return;
319 }
320
321 if (treeView.isActive()) {
322 if ((keypress.equals(kbEnter))
323 || (keypress.equals(kbUp))
324 || (keypress.equals(kbDown))
325 || (keypress.equals(kbPgUp))
326 || (keypress.equals(kbPgDn))
327 || (keypress.equals(kbHome))
328 || (keypress.equals(kbEnd))
329 ) {
330 // Tree view will be changing, update the directory list.
331 super.onKeypress(keypress);
332
333 // This is the same action as treeView's enter.
334 TTreeItem item = treeView.getSelected();
335 File selectedDir = ((TDirectoryTreeItem) item).getFile();
336 try {
337 directoryList.setPath(selectedDir.getCanonicalPath());
338 if (type == Type.OPEN) {
339 openButton.setEnabled(false);
340 }
341 activate(treeView);
342 } catch (IOException e) {
343 // If the backend is Swing, we can emit the stack trace
344 // to stderr. Otherwise, just squash it.
345 if (getScreen() instanceof SwingTerminal) {
346 e.printStackTrace();
347 }
348 }
349 return;
350 }
351 }
352
353 // Pass to my parent
354 super.onKeypress(keypress);
355 }
356
357 // ------------------------------------------------------------------------
358 // TWidget ----------------------------------------------------------------
359 // ------------------------------------------------------------------------
360
361 /**
362 * Draw me on screen.
363 */
364 @Override
365 public void draw() {
366 super.draw();
367 vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
368 getBackground());
369 }
370
371 // ------------------------------------------------------------------------
372 // TFileOpenBox -----------------------------------------------------------
373 // ------------------------------------------------------------------------
374
375 /**
376 * Get the return string.
377 *
378 * @return the filename the user selected, or null if they canceled.
379 */
380 public String getFilename() {
381 return filename;
382 }
383
384 /**
385 * See if there is a valid filename to return. If the filename is a
386 * directory, then
387 *
388 * @param newFilename the filename to check and return
389 * @throws IOException of a java.io operation throws
390 */
391 private void checkFilename(final String newFilename) throws IOException {
392 File newFile = new File(newFilename);
393 if (newFile.exists()) {
394 if (newFile.isFile()) {
395 filename = newFilename;
396 getApplication().closeWindow(this);
397 return;
398 }
399 if (newFile.isDirectory()) {
400 treeViewRoot = new TDirectoryTreeItem(treeView,
401 newFilename, true);
402 treeView.setTreeRoot(treeViewRoot, true);
403 if (type == Type.OPEN) {
404 openButton.setEnabled(false);
405 }
406 directoryList.setPath(newFilename);
407 }
408 } else if (type != Type.OPEN) {
409 filename = newFilename;
410 getApplication().closeWindow(this);
411 return;
412 }
413 }
414
415 }