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]
32 import java
.io
.IOException
;
33 import java
.util
.List
;
34 import java
.util
.ResourceBundle
;
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
.*;
45 * TFileOpenBox is a system-modal dialog for selecting a file to open. Call
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 ...
59 public class TFileOpenBox
extends TWindow
{
64 private static final ResourceBundle i18n
= ResourceBundle
.getBundle(TFileOpenBox
.class.getName());
66 // ------------------------------------------------------------------------
67 // Constants --------------------------------------------------------------
68 // ------------------------------------------------------------------------
71 * TFileOpenBox can be called for either Open or Save actions.
75 * Button will be labeled "Open".
80 * Button will be labeled "Save".
85 * Button will be labeled "Select".
90 // ------------------------------------------------------------------------
91 // Variables --------------------------------------------------------------
92 // ------------------------------------------------------------------------
95 * String to return, or null if the user canceled.
97 private String filename
= null;
100 * The left-side tree view pane.
102 private TTreeViewWidget treeView
;
105 * The data behind treeView.
107 private TDirectoryTreeItem treeViewRoot
;
110 * The right-side directory list pane.
112 private TDirectoryList directoryList
;
115 * The top row text field.
117 private TField entryField
;
120 * The Open or Save button.
122 private TButton openButton
;
125 * The type of box this is (OPEN, SAVE, or SELECT).
127 private Type type
= Type
.OPEN
;
129 // ------------------------------------------------------------------------
130 // Constructors -----------------------------------------------------------
131 // ------------------------------------------------------------------------
134 * Public constructor. The file open box will be centered on screen.
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
141 public TFileOpenBox(final TApplication application
, final String path
,
142 final Type type
) throws IOException
{
144 this(application
, path
, type
, null);
148 * Public constructor. The file open box will be centered on screen.
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
156 public TFileOpenBox(final TApplication application
, final String path
,
157 final Type type
, final List
<String
> filters
) throws IOException
{
159 // Register with the TApplication
160 super(application
, "", 0, 0, 76, 22, MODAL
);
163 entryField
= addField(1, 1, getWidth() - 4, false,
164 (new File(path
)).getCanonicalPath(),
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
) {
178 entryField
.onKeypress(new TKeypressEvent(kbEnd
));
180 // Add directory treeView
181 treeView
= addTreeViewWidget(1, 3, 30, getHeight() - 6,
184 TTreeItem item
= treeView
.getSelected();
185 File selectedDir
= ((TDirectoryTreeItem
) item
).getFile();
187 directoryList
.setPath(selectedDir
.getCanonicalPath());
188 entryField
.setText(selectedDir
.getCanonicalPath());
189 if (type
== Type
.OPEN
) {
190 openButton
.setEnabled(false);
193 } catch (IOException e
) {
194 // If the backend is Swing, we can emit the stack
195 // trace to stderr. Otherwise, just squash it.
196 if (getScreen() instanceof SwingTerminal
) {
203 treeViewRoot
= new TDirectoryTreeItem(treeView
, path
, true);
205 // Add directory files list
206 directoryList
= addDirectoryList(path
, 34, 3, 28, getHeight() - 6,
210 File newPath
= directoryList
.getPath();
211 entryField
.setText(newPath
.getCanonicalPath());
212 entryField
.onKeypress(new TKeypressEvent(kbEnd
));
213 openButton
.setEnabled(true);
214 activate(entryField
);
215 checkFilename(entryField
.getText());
216 } catch (IOException e
) {
217 // If the backend is Swing, we can emit the stack
218 // trace to stderr. Otherwise, just squash it.
219 if (getScreen() instanceof SwingTerminal
) {
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
) {
244 String openLabel
= "";
247 openLabel
= i18n
.getString("openButton");
248 setTitle(i18n
.getString("openTitle"));
251 openLabel
= i18n
.getString("saveButton");
252 setTitle(i18n
.getString("saveTitle"));
255 openLabel
= i18n
.getString("selectButton");
256 setTitle(i18n
.getString("selectTitle"));
259 throw new IllegalArgumentException("Invalid type: " + type
);
263 // Setup button actions
264 openButton
= addButton(openLabel
, this.getWidth() - 12, 3,
268 checkFilename(entryField
.getText());
269 } catch (IOException e
) {
270 // If the backend is Swing, we can emit the stack
271 // trace to stderr. Otherwise, just squash it.
272 if (getScreen() instanceof SwingTerminal
) {
279 if (type
== Type
.OPEN
) {
280 openButton
.setEnabled(false);
283 addButton(i18n
.getString("cancelButton"), getWidth() - 12, 5,
287 getApplication().closeWindow(TFileOpenBox
.this);
292 // Default to the directory list
293 activate(directoryList
);
295 // Set the secondaryFiber to run me
296 getApplication().enableSecondaryEventReceiver(this);
298 // Yield to the secondary thread. When I come back from the
299 // constructor response will already be set.
300 getApplication().yield();
303 // ------------------------------------------------------------------------
304 // Event handlers ---------------------------------------------------------
305 // ------------------------------------------------------------------------
310 * @param keypress keystroke event
313 public void onKeypress(final TKeypressEvent keypress
) {
314 // Escape - behave like cancel
315 if (keypress
.equals(kbEsc
)) {
318 getApplication().closeWindow(this);
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
))
331 // Tree view will be changing, update the directory list.
332 super.onKeypress(keypress
);
334 // This is the same action as treeView's enter.
335 TTreeItem item
= treeView
.getSelected();
336 File selectedDir
= ((TDirectoryTreeItem
) item
).getFile();
338 directoryList
.setPath(selectedDir
.getCanonicalPath());
339 if (type
== Type
.OPEN
) {
340 openButton
.setEnabled(false);
343 } catch (IOException e
) {
344 // If the backend is Swing, we can emit the stack trace
345 // to stderr. Otherwise, just squash it.
346 if (getScreen() instanceof SwingTerminal
) {
355 super.onKeypress(keypress
);
358 // ------------------------------------------------------------------------
359 // TWidget ----------------------------------------------------------------
360 // ------------------------------------------------------------------------
368 vLineXY(33, 4, getHeight() - 6, GraphicsChars
.WINDOW_SIDE
,
372 // ------------------------------------------------------------------------
373 // TFileOpenBox -----------------------------------------------------------
374 // ------------------------------------------------------------------------
377 * Get the return string.
379 * @return the filename the user selected, or null if they canceled.
381 public String
getFilename() {
386 * See if there is a valid filename to return. If the filename is a
389 * @param newFilename the filename to check and return
390 * @throws IOException of a java.io operation throws
392 private void checkFilename(final String newFilename
) throws IOException
{
393 File newFile
= new File(newFilename
);
394 if (newFile
.exists()) {
395 if (newFile
.isFile() || (type
== Type
.SELECT
)) {
396 filename
= newFilename
;
397 getApplication().closeWindow(this);
400 if (newFile
.isDirectory()) {
401 treeViewRoot
= new TDirectoryTreeItem(treeView
,
403 treeView
.setTreeRoot(treeViewRoot
, true);
404 if (type
== Type
.OPEN
) {
405 openButton
.setEnabled(false);
407 directoryList
.setPath(newFilename
);
409 } else if (type
!= Type
.OPEN
) {
410 filename
= newFilename
;
411 getApplication().closeWindow(this);