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