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 if (type
== Type
.OPEN
) {
189 openButton
.setEnabled(false);
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
) {
202 treeViewRoot
= new TDirectoryTreeItem(treeView
, path
, true);
204 // Add directory files list
205 directoryList
= addDirectoryList(path
, 34, 3, 28, getHeight() - 6,
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
) {
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
) {
243 String openLabel
= "";
246 openLabel
= i18n
.getString("openButton");
247 setTitle(i18n
.getString("openTitle"));
250 openLabel
= i18n
.getString("saveButton");
251 setTitle(i18n
.getString("saveTitle"));
254 openLabel
= i18n
.getString("selectButton");
255 setTitle(i18n
.getString("selectTitle"));
258 throw new IllegalArgumentException("Invalid type: " + type
);
262 // Setup button actions
263 openButton
= addButton(openLabel
, this.getWidth() - 12, 3,
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
) {
278 if (type
== Type
.OPEN
) {
279 openButton
.setEnabled(false);
282 addButton(i18n
.getString("cancelButton"), getWidth() - 12, 5,
286 getApplication().closeWindow(TFileOpenBox
.this);
291 // Default to the directory list
292 activate(directoryList
);
294 // Set the secondaryFiber to run me
295 getApplication().enableSecondaryEventReceiver(this);
297 // Yield to the secondary thread. When I come back from the
298 // constructor response will already be set.
299 getApplication().yield();
302 // ------------------------------------------------------------------------
303 // Event handlers ---------------------------------------------------------
304 // ------------------------------------------------------------------------
309 * @param keypress keystroke event
312 public void onKeypress(final TKeypressEvent keypress
) {
313 // Escape - behave like cancel
314 if (keypress
.equals(kbEsc
)) {
317 getApplication().closeWindow(this);
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
))
330 // Tree view will be changing, update the directory list.
331 super.onKeypress(keypress
);
333 // This is the same action as treeView's enter.
334 TTreeItem item
= treeView
.getSelected();
335 File selectedDir
= ((TDirectoryTreeItem
) item
).getFile();
337 directoryList
.setPath(selectedDir
.getCanonicalPath());
338 if (type
== Type
.OPEN
) {
339 openButton
.setEnabled(false);
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
) {
354 super.onKeypress(keypress
);
357 // ------------------------------------------------------------------------
358 // TWidget ----------------------------------------------------------------
359 // ------------------------------------------------------------------------
367 vLineXY(33, 4, getHeight() - 6, GraphicsChars
.WINDOW_SIDE
,
371 // ------------------------------------------------------------------------
372 // TFileOpenBox -----------------------------------------------------------
373 // ------------------------------------------------------------------------
376 * Get the return string.
378 * @return the filename the user selected, or null if they canceled.
380 public String
getFilename() {
385 * See if there is a valid filename to return. If the filename is a
388 * @param newFilename the filename to check and return
389 * @throws IOException of a java.io operation throws
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);
399 if (newFile
.isDirectory()) {
400 treeViewRoot
= new TDirectoryTreeItem(treeView
,
402 treeView
.setTreeRoot(treeViewRoot
, true);
403 if (type
== Type
.OPEN
) {
404 openButton
.setEnabled(false);
406 directoryList
.setPath(newFilename
);
408 } else if (type
!= Type
.OPEN
) {
409 filename
= newFilename
;
410 getApplication().closeWindow(this);