Commit | Line | Data |
---|---|---|
5434cb2b KL |
1 | import java.awt.image.BufferedImage; |
2 | import java.io.File; | |
3 | import java.io.IOException; | |
4 | import java.util.ArrayList; | |
5 | import java.util.List; | |
6 | import javax.imageio.ImageIO; | |
7 | ||
8 | import jexer.TAction; | |
9 | import jexer.TApplication; | |
10 | import jexer.TDesktop; | |
11 | import jexer.TDirectoryList; | |
12 | import jexer.TImage; | |
13 | import jexer.backend.SwingTerminal; | |
14 | import jexer.bits.CellAttributes; | |
15 | import jexer.bits.GraphicsChars; | |
16 | import jexer.event.TKeypressEvent; | |
17 | import jexer.event.TResizeEvent; | |
18 | import jexer.menu.TMenu; | |
19 | import jexer.ttree.TDirectoryTreeItem; | |
20 | import jexer.ttree.TTreeItem; | |
21 | import jexer.ttree.TTreeViewWidget; | |
22 | import static jexer.TKeypress.*; | |
23 | ||
24 | /** | |
25 | * Implements a simple image thumbnail file viewer. Much of this code was | |
26 | * stripped down from TFileOpenBox. | |
27 | */ | |
28 | public class JexerImageViewer extends TApplication { | |
29 | ||
30 | /** | |
31 | * Main entry point. | |
32 | */ | |
33 | public static void main(String [] args) throws Exception { | |
5434cb2b KL |
34 | JexerImageViewer app = new JexerImageViewer(); |
35 | (new Thread(app)).start(); | |
36 | } | |
37 | ||
38 | /** | |
39 | * Public constructor chooses the ECMA-48 / Xterm backend. | |
40 | */ | |
41 | public JexerImageViewer() throws Exception { | |
42 | super(BackendType.XTERM); | |
43 | ||
44 | // The stock tool menu has items for redrawing the screen, opening | |
45 | // images, and (when using the Swing backend) setting the font. | |
46 | addToolMenu(); | |
47 | ||
48 | // We will have one menu containing a mix of new and stock commands | |
49 | TMenu fileMenu = addMenu("&File"); | |
50 | ||
51 | // Stock commands: a new shell, exit program. | |
52 | fileMenu.addDefaultItem(TMenu.MID_SHELL); | |
53 | fileMenu.addSeparator(); | |
54 | fileMenu.addDefaultItem(TMenu.MID_EXIT); | |
55 | ||
56 | // Filter the files list to support image suffixes only. | |
57 | List<String> filters = new ArrayList<String>(); | |
58 | filters.add("^.*\\.[Jj][Pp][Gg]$"); | |
59 | filters.add("^.*\\.[Jj][Pp][Ee][Gg]$"); | |
60 | filters.add("^.*\\.[Pp][Nn][Gg]$"); | |
61 | filters.add("^.*\\.[Gg][Ii][Ff]$"); | |
62 | filters.add("^.*\\.[Bb][Mm][Pp]$"); | |
63 | setDesktop(new ImageViewerDesktop(this, ".", filters)); | |
64 | } | |
65 | ||
66 | } | |
67 | ||
68 | /** | |
69 | * The desktop contains a tree view on the left, list of files on the top | |
70 | * right, and image view on the bottom right. | |
71 | */ | |
72 | class ImageViewerDesktop extends TDesktop { | |
73 | ||
74 | /** | |
75 | * The left-side tree view pane. | |
76 | */ | |
77 | private TTreeViewWidget treeView; | |
78 | ||
79 | /** | |
80 | * The data behind treeView. | |
81 | */ | |
82 | private TDirectoryTreeItem treeViewRoot; | |
83 | ||
84 | /** | |
85 | * The top-right-side directory list pane. | |
86 | */ | |
87 | private TDirectoryList directoryList; | |
88 | ||
89 | /** | |
90 | * The bottom-right-side image pane. | |
91 | */ | |
92 | private TImage imageWidget; | |
93 | ||
94 | /** | |
95 | * Public constructor. | |
96 | * | |
97 | * @param application the TApplication that manages this window | |
98 | * @param path path of selected file | |
99 | * @param filters a list of strings that files must match to be displayed | |
100 | * @throws IOException of a java.io operation throws | |
101 | */ | |
102 | public ImageViewerDesktop(final TApplication application, final String path, | |
103 | final List<String> filters) throws IOException { | |
104 | ||
105 | super(application); | |
106 | setActive(true); | |
107 | ||
108 | // Add directory treeView | |
109 | treeView = addTreeViewWidget(0, 0, getWidth() / 2, getHeight() - 1, | |
110 | new TAction() { | |
111 | public void DO() { | |
112 | TTreeItem item = treeView.getSelected(); | |
113 | File selectedDir = ((TDirectoryTreeItem) item).getFile(); | |
114 | try { | |
115 | directoryList.setPath(selectedDir.getCanonicalPath()); | |
116 | if (directoryList.getList().size() > 0) { | |
117 | setThumbnail(directoryList.getPath()); | |
118 | } else { | |
119 | if (imageWidget != null) { | |
120 | getChildren().remove(imageWidget); | |
121 | } | |
122 | imageWidget = null; | |
123 | } | |
124 | activate(treeView); | |
125 | } catch (IOException e) { | |
126 | // If the backend is Swing, we can emit the stack | |
127 | // trace to stderr. Otherwise, just squash it. | |
128 | if (getScreen() instanceof SwingTerminal) { | |
129 | e.printStackTrace(); | |
130 | } | |
131 | } | |
132 | } | |
133 | } | |
134 | ); | |
135 | treeViewRoot = new TDirectoryTreeItem(treeView, path, true); | |
136 | ||
137 | // Add directory files list | |
138 | directoryList = addDirectoryList(path, getWidth() / 2 + 1, 0, | |
139 | getWidth() / 2 - 1, getHeight() / 2, | |
140 | ||
141 | new TAction() { | |
142 | public void DO() { | |
143 | setThumbnail(directoryList.getPath()); | |
144 | } | |
145 | }, | |
146 | new TAction() { | |
147 | ||
148 | public void DO() { | |
149 | setThumbnail(directoryList.getPath()); | |
150 | } | |
151 | }, | |
152 | filters); | |
153 | ||
154 | // This appears to be a bug. If I pass keystrokes to the tree view, | |
155 | // it will center correctly. | |
156 | treeView.onKeypress(new TKeypressEvent(kbDown)); | |
157 | treeView.onKeypress(new TKeypressEvent(kbUp)); | |
158 | ||
159 | if (directoryList.getList().size() > 0) { | |
160 | activate(directoryList); | |
161 | setThumbnail(directoryList.getPath()); | |
162 | } else { | |
163 | activate(treeView); | |
164 | } | |
165 | } | |
166 | ||
167 | /** | |
168 | * Handle window/screen resize events. | |
169 | * | |
170 | * @param event resize event | |
171 | */ | |
172 | @Override | |
173 | public void onResize(final TResizeEvent event) { | |
174 | ||
175 | // Resize the tree and list | |
176 | treeView.setY(1); | |
177 | treeView.setWidth(getWidth() / 2); | |
178 | treeView.setHeight(getHeight() - 1); | |
179 | treeView.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, | |
180 | treeView.getWidth(), | |
181 | treeView.getHeight())); | |
182 | treeView.getTreeView().onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, | |
183 | treeView.getWidth() - 1, | |
184 | treeView.getHeight() - 1)); | |
185 | directoryList.setX(getWidth() / 2 + 1); | |
186 | directoryList.setY(1); | |
187 | directoryList.setWidth(getWidth() / 2 - 1); | |
188 | directoryList.setHeight(getHeight() / 2 - 1); | |
189 | directoryList.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, | |
190 | directoryList.getWidth(), | |
191 | directoryList.getHeight())); | |
192 | ||
193 | // Recreate the image | |
194 | if (imageWidget != null) { | |
195 | getChildren().remove(imageWidget); | |
196 | } | |
197 | imageWidget = null; | |
198 | if (directoryList.getList().size() > 0) { | |
199 | activate(directoryList); | |
200 | setThumbnail(directoryList.getPath()); | |
201 | } else { | |
202 | activate(treeView); | |
203 | } | |
204 | } | |
205 | ||
206 | /** | |
207 | * Handle keystrokes. | |
208 | * | |
209 | * @param keypress keystroke event | |
210 | */ | |
211 | @Override | |
212 | public void onKeypress(final TKeypressEvent keypress) { | |
213 | ||
214 | if (treeView.isActive() || directoryList.isActive()) { | |
215 | if ((keypress.equals(kbEnter)) | |
216 | || (keypress.equals(kbUp)) | |
217 | || (keypress.equals(kbDown)) | |
218 | || (keypress.equals(kbPgUp)) | |
219 | || (keypress.equals(kbPgDn)) | |
220 | || (keypress.equals(kbHome)) | |
221 | || (keypress.equals(kbEnd)) | |
222 | ) { | |
223 | // Tree view will be changing, update the directory list. | |
224 | super.onKeypress(keypress); | |
225 | ||
226 | // This is the same action as treeView's enter. | |
227 | TTreeItem item = treeView.getSelected(); | |
228 | File selectedDir = ((TDirectoryTreeItem) item).getFile(); | |
229 | try { | |
230 | if (treeView.isActive()) { | |
231 | directoryList.setPath(selectedDir.getCanonicalPath()); | |
232 | } | |
233 | if (directoryList.getList().size() > 0) { | |
234 | activate(directoryList); | |
235 | setThumbnail(directoryList.getPath()); | |
236 | } else { | |
237 | if (imageWidget != null) { | |
238 | getChildren().remove(imageWidget); | |
239 | } | |
240 | imageWidget = null; | |
241 | activate(treeView); | |
242 | } | |
243 | } catch (IOException e) { | |
244 | // If the backend is Swing, we can emit the stack trace | |
245 | // to stderr. Otherwise, just squash it. | |
246 | if (getScreen() instanceof SwingTerminal) { | |
247 | e.printStackTrace(); | |
248 | } | |
249 | } | |
250 | return; | |
251 | } | |
252 | } | |
253 | ||
254 | // Pass to my parent | |
255 | super.onKeypress(keypress); | |
256 | } | |
257 | ||
258 | /** | |
259 | * Draw me on screen. | |
260 | */ | |
261 | @Override | |
262 | public void draw() { | |
263 | CellAttributes background = getTheme().getColor("tdesktop.background"); | |
264 | putAll(' ', background); | |
265 | ||
266 | vLineXY(getWidth() / 2, 0, getHeight(), | |
267 | GraphicsChars.WINDOW_SIDE, getBackground()); | |
268 | ||
269 | hLineXY(getWidth() / 2, getHeight() / 2, (getWidth() + 1) / 2, | |
270 | GraphicsChars.WINDOW_TOP, getBackground()); | |
271 | ||
272 | putCharXY(getWidth() / 2, getHeight() / 2, | |
273 | GraphicsChars.WINDOW_LEFT_TEE, getBackground()); | |
274 | } | |
275 | ||
276 | /** | |
277 | * Set the image thumbnail. | |
278 | * | |
279 | * @param file the image file | |
280 | */ | |
281 | private void setThumbnail(final File file) { | |
282 | if (file == null) { | |
283 | return; | |
284 | } | |
285 | if (!file.exists() || !file.isFile()) { | |
286 | return; | |
287 | } | |
288 | ||
289 | BufferedImage image = null; | |
290 | try { | |
291 | image = ImageIO.read(file); | |
292 | } catch (IOException e) { | |
293 | // If the backend is Swing, we can emit the stack trace to | |
294 | // stderr. Otherwise, just squash it. | |
295 | if (getScreen() instanceof SwingTerminal) { | |
296 | e.printStackTrace(); | |
297 | } | |
298 | return; | |
299 | } | |
300 | ||
301 | if (imageWidget != null) { | |
302 | getChildren().remove(imageWidget); | |
303 | } | |
304 | int width = getWidth() / 2 - 1; | |
305 | int height = getHeight() / 2 - 1; | |
306 | ||
307 | imageWidget = new TImage(this, getWidth() - width, | |
308 | getHeight() - height, width, height, image, 0, 0, null); | |
309 | ||
be528f1a KL |
310 | // Resize the image to fit within the pane. |
311 | imageWidget.setScaleType(TImage.Scale.SCALE); | |
5434cb2b KL |
312 | |
313 | imageWidget.setActive(false); | |
314 | activate(directoryList); | |
315 | } | |
316 | ||
317 | } |