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