image thumbnail viewer example
[nikiroo-utils.git] / examples / JexerImageViewer.java
CommitLineData
5434cb2b
KL
1import java.awt.image.BufferedImage;
2import java.io.File;
3import java.io.IOException;
4import java.util.ArrayList;
5import java.util.List;
6import javax.imageio.ImageIO;
7
8import jexer.TAction;
9import jexer.TApplication;
10import jexer.TDesktop;
11import jexer.TDirectoryList;
12import jexer.TImage;
13import jexer.backend.SwingTerminal;
14import jexer.bits.CellAttributes;
15import jexer.bits.GraphicsChars;
16import jexer.event.TKeypressEvent;
17import jexer.event.TResizeEvent;
18import jexer.menu.TMenu;
19import jexer.ttree.TDirectoryTreeItem;
20import jexer.ttree.TTreeItem;
21import jexer.ttree.TTreeViewWidget;
22import static jexer.TKeypress.*;
23
24/**
25 * Implements a simple image thumbnail file viewer. Much of this code was
26 * stripped down from TFileOpenBox.
27 */
28public 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 */
75class 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}