1 | // Jomic - a viewer for comic book archives. |
2 | // Copyright (C) 2004-2011 Thomas Aglassinger |
3 | // |
4 | // This program is free software: you can redistribute it and/or modify |
5 | // it under the terms of the GNU General Public License as published by |
6 | // the Free Software Foundation, either version 3 of the License, or |
7 | // (at your option) any later version. |
8 | // |
9 | // This program is distributed in the hope that it will be useful, |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | // GNU General Public License for more details. |
13 | // |
14 | // You should have received a copy of the GNU General Public License |
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | package net.sf.jomic.ui; |
17 | |
18 | import java.awt.DisplayMode; |
19 | import java.awt.Graphics2D; |
20 | import java.awt.GraphicsDevice; |
21 | import java.awt.GraphicsEnvironment; |
22 | import java.awt.event.ActionEvent; |
23 | import java.awt.event.ActionListener; |
24 | import java.awt.event.InputEvent; |
25 | import java.awt.event.KeyEvent; |
26 | import java.awt.event.KeyListener; |
27 | import java.awt.event.MouseEvent; |
28 | import java.awt.event.MouseListener; |
29 | import java.awt.image.BufferStrategy; |
30 | import java.awt.image.RenderedImage; |
31 | import java.beans.PropertyChangeEvent; |
32 | import java.beans.PropertyChangeListener; |
33 | import java.lang.reflect.InvocationTargetException; |
34 | import java.util.HashMap; |
35 | import java.util.Map; |
36 | import java.util.TreeMap; |
37 | |
38 | import javax.swing.JFrame; |
39 | import javax.swing.JMenu; |
40 | import javax.swing.JMenuItem; |
41 | import javax.swing.KeyStroke; |
42 | import javax.swing.SwingUtilities; |
43 | |
44 | import net.sf.jomic.comic.ComicModel; |
45 | import net.sf.jomic.comic.ComicSheet; |
46 | import net.sf.jomic.comic.ComicSheetLayout; |
47 | import net.sf.jomic.comic.ComicView; |
48 | import net.sf.jomic.common.ComicSheetRenderSettings; |
49 | import net.sf.jomic.common.FullScreenCancelabel; |
50 | import net.sf.jomic.common.JomicTools; |
51 | import net.sf.jomic.common.PropertyConstants; |
52 | import net.sf.jomic.common.Settings; |
53 | import net.sf.jomic.tools.ImageTools; |
54 | import net.sf.jomic.tools.LocaleTools; |
55 | |
56 | import org.apache.commons.logging.Log; |
57 | import org.apache.commons.logging.LogFactory; |
58 | |
59 | /** |
60 | * Comic viewer in full screen mode connected to a JomicFrame. |
61 | * |
62 | * @author Thomas Aglassinger |
63 | * @see JomicFrame |
64 | */ |
65 | public final class FullScreenViewer |
66 | implements ActionListener, FullScreenCancelabel, KeyListener, PropertyChangeListener, MouseListener |
67 | { |
68 | private static final String DELEGATE_TO_CONTROLLER = "delegateToController;"; |
69 | private static final String REFRESH_VIEW = "refreshView;"; |
70 | private static final String REMAIN_IN_FULL_SCREEN = "remainInFullScreen;"; |
71 | private static final int WINDOW_HEIGHT = 500; |
72 | private static final int WINDOW_WIDTH = 600; |
73 | |
74 | private static /*@ spec_public nullable @*/ FullScreenViewer instance; |
75 | |
76 | //@ public ghost boolean disposed; |
77 | |
78 | private /*@ nullable @*/ ComicModel comic; |
79 | private /*@ nullable @*/ ComicView comicView; |
80 | private Map commandHandlingMap; |
81 | private /*@ nullable @*/ GraphicsDevice defaultDevice; |
82 | private /*@ nullable @*/ JFrame frame; |
83 | private ImageTools imageTools; |
84 | private boolean isVisible; |
85 | private JomicTools jomicTools; |
86 | private boolean /*@ spec_public */ actionListenersAdded; |
87 | private LocaleTools localeTools; |
88 | private Log logger; |
89 | private /*@ nullable @*/ DisplayMode oldDisplayMode; |
90 | private /*@ nullable @*/ JomicFrame controller; |
91 | private Settings settings; |
92 | private boolean /*@ spec_public */ settingsChangedListenerAdded; |
93 | private boolean showFullScreenInWindow; |
94 | private Map keyStrokeToCommandMap; |
95 | private boolean ignoreNextLeftMouseButtonReleased; |
96 | private RefreshDisplayRunner refreshDisplayRunner; |
97 | |
98 | /** |
99 | * Create a new fullscreen viewer for the specified controlling JomicFrame. The full screen viewer |
100 | * opens in a borderless frame processes some commands such as page navigation. All other |
101 | * commands revert to window mode, and delegate the command to the <code>controller</code>. |
102 | * |
103 | * @param newController the JomicFrame acting as controller for this full screen viewer |
104 | */ |
105 | private FullScreenViewer() { |
106 | logger = LogFactory.getLog(FullScreenViewer.class); |
107 | imageTools = ImageTools.instance(); |
108 | jomicTools = JomicTools.instance(); |
109 | localeTools = LocaleTools.instance(); |
110 | settings = Settings.instance(); |
111 | comicView = new ComicView(); |
112 | comicView.setBorder(null); |
113 | refreshDisplayRunner = new RefreshDisplayRunner(); |
114 | keyStrokeToCommandMap = new HashMap(); |
115 | commandHandlingMap = new TreeMap(); |
116 | commandHandlingMap.put(Commands.ADVANCE, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
117 | commandHandlingMap.put(Commands.FIRST_PAGE, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
118 | commandHandlingMap.put(Commands.GO_NEXT, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
119 | commandHandlingMap.put(Commands.GO_NEXT_FEW, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
120 | commandHandlingMap.put(Commands.GO_PREVIOUS, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
121 | commandHandlingMap.put(Commands.GO_PREVIOUS_FEW, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
122 | commandHandlingMap.put(Commands.LAST_PAGE, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
123 | commandHandlingMap.put(Commands.FULL_SCREEN, ""); |
124 | commandHandlingMap.put(Commands.RETREAT, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
125 | commandHandlingMap.put(Commands.ROTATE_LEFT, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
126 | commandHandlingMap.put(Commands.ROTATE_RIGHT, DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
127 | commandHandlingMap.put(Commands.TOGGLE_SWAP_LEFT_AND_RIGHT_IMAGE, |
128 | DELEGATE_TO_CONTROLLER + REFRESH_VIEW + REMAIN_IN_FULL_SCREEN); |
129 | } |
130 | |
131 | //@ ensures instance != null; |
132 | public static synchronized FullScreenViewer instance() { |
133 | if (instance == null) { |
134 | instance = new FullScreenViewer(); |
135 | } |
136 | return instance; |
137 | } |
138 | |
139 | public void setController(/*@ nullable @*/ JomicFrame newController) { |
140 | if (newController != null) { |
141 | controller = newController; |
142 | comic = controller.getComicModel(); |
143 | assignShortCutKeys(); |
144 | } else { |
145 | controller = null; |
146 | comic = null; |
147 | keyStrokeToCommandMap.clear(); |
148 | } |
149 | } |
150 | |
151 | //@ ensures isVisible() == newVisible; |
152 | public void setVisible(boolean newVisible) { |
153 | if (newVisible) { |
154 | setVisibleTrue(); |
155 | isVisible = true; |
156 | invokeLaterRefreshComicView(); |
157 | } else { |
158 | setVisibleFalse(); |
159 | isVisible = false; |
160 | } |
161 | } |
162 | |
163 | public /*@ pure @*/ boolean isVisible() { |
164 | return isVisible; |
165 | } |
166 | |
167 | //@ ensures actionListenersAdded |
168 | //@ ensures settingsChangedListenerAdded |
169 | private void setVisibleTrue() { |
170 | assert controller != null; |
171 | |
172 | setUpDefaultDevice(); |
173 | setUpComicView(); |
174 | |
175 | // Try to switch to full screen, and browse comic. |
176 | boolean viewerIsVisible = false; |
177 | |
178 | showFullScreenInWindow = settings.getBooleanProperty(PropertyConstants.TEST_SHOW_FULL_SCREEN_IN_WINDOW); |
179 | if (showFullScreenInWindow) { |
180 | frame = new JFrame(); |
181 | frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT); |
182 | } else { |
183 | frame = new JFrame(defaultDevice.getDefaultConfiguration()); |
184 | } |
185 | try { |
186 | controller.setVisible(false); |
187 | frame.setIgnoreRepaint(true); |
188 | if (!showFullScreenInWindow) { |
189 | frame.setUndecorated(true); |
190 | defaultDevice.setFullScreenWindow(frame); |
191 | } |
192 | frame.setTitle(FullScreenViewer.class.getName()); |
193 | frame.setResizable(false); |
194 | frame.validate(); |
195 | if (showFullScreenInWindow) { |
196 | frame.setVisible(true); |
197 | } else { |
198 | frame.createBufferStrategy(2); |
199 | } |
200 | frame.addKeyListener(instance()); |
201 | frame.addMouseListener(instance()); |
202 | actionListenersAdded = true; |
203 | settings.addPropertyChangeListener(PropertyConstants.MOST_RECENT_PAGE, this); |
204 | settingsChangedListenerAdded = true; |
205 | viewerIsVisible = true; |
206 | } finally { |
207 | if (!viewerIsVisible) { |
208 | setVisible(false); |
209 | controller.setVisible(true); |
210 | } |
211 | } |
212 | } |
213 | |
214 | //@ ensures comicView != null; |
215 | private /*@ helper @*/ void setUpComicView() { |
216 | comicView.setModel(comic, null, settings.getMostRecentPage()); |
217 | comicView.setTwoPageMode(settings.getTwoPageMode()); |
218 | comicView.setSwapLeftAndRightImage(settings.getSwapLeftAndRightImage()); |
219 | comicView.setScaleMode(ImageTools.SCALE_FIT); |
220 | } |
221 | |
222 | //@ ensures defaultDevice != null; |
223 | //@ ensures oldDisplayMode != null; |
224 | //@ signals_only IllegalStateException; |
225 | private /*@ helper @*/ void setUpDefaultDevice() { |
226 | GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); |
227 | |
228 | if (env == null) { |
229 | String errorMessage = localeTools.getMessage("errors.cannotObtainGraphicsEnvirontment"); |
230 | |
231 | throw new IllegalStateException(errorMessage); |
232 | } |
233 | |
234 | GraphicsDevice[] devices = env.getScreenDevices(); |
235 | |
236 | if (devices == null) { |
237 | String errorMessage = localeTools.getMessage("errors.cannotObtainScreenDevices"); |
238 | |
239 | throw new IllegalStateException(errorMessage); |
240 | } |
241 | if (devices.length == 0) { |
242 | String errorMessage = localeTools.getMessage("errors.listOfScreenDevicesMustNotBeEmpty"); |
243 | |
244 | throw new IllegalStateException(errorMessage); |
245 | } |
246 | defaultDevice = devices[0]; |
247 | oldDisplayMode = defaultDevice.getDisplayMode(); |
248 | if (logger.isInfoEnabled()) { |
249 | logger.info("graphics device=\"" + defaultDevice.getIDstring() |
250 | + "\""); |
251 | logger.info("display mode=" + oldDisplayMode.getWidth() + "x" |
252 | + oldDisplayMode.getHeight() + "x" |
253 | + oldDisplayMode.getBitDepth() + " @ " |
254 | + oldDisplayMode.getRefreshRate()); |
255 | } |
256 | boolean isFullScreen = defaultDevice.isFullScreenSupported(); |
257 | |
258 | if (!isFullScreen) { |
259 | String errorMessage = localeTools.getMessage( |
260 | "errors.deviceMustSupportFullScreen", defaultDevice.getIDstring()); |
261 | |
262 | throw new IllegalStateException(errorMessage); |
263 | } |
264 | } |
265 | |
266 | /** |
267 | * Delegate all events to controller, but also process some yourself (for example navigational |
268 | * commands). Unless the command is in COMMANDS_REMAINING_IN_FULL_SCREEN, revert back to window |
269 | * mode. |
270 | */ |
271 | //@ also |
272 | //@ requires event.getActionCommand() != null; |
273 | public void actionPerformed(ActionEvent event) { |
274 | try { |
275 | String command = event.getActionCommand(); |
276 | String commandActions = (String) commandHandlingMap.get(command); |
277 | boolean delegateToController; |
278 | boolean refreshView; |
279 | boolean remainInFullScreen; |
280 | |
281 | if (logger.isInfoEnabled()) { |
282 | logger.info("command=" + command + ": commandActions=" + commandActions); |
283 | } |
284 | if (commandActions != null) { |
285 | delegateToController = commandActions.indexOf(DELEGATE_TO_CONTROLLER) >= 0; |
286 | refreshView = commandActions.indexOf(REFRESH_VIEW) >= 0; |
287 | remainInFullScreen = commandActions.indexOf(REMAIN_IN_FULL_SCREEN) >= 0; |
288 | |
289 | // In case advance/retreat have to open a new file, exit full screen. |
290 | if (remainInFullScreen) { |
291 | if (command.equals(Commands.ADVANCE) && comicView.isLast()) { |
292 | remainInFullScreen = false; |
293 | } else if (command.equals(Commands.RETREAT) && comicView.isFirst()) { |
294 | remainInFullScreen = false; |
295 | } |
296 | } |
297 | } else { |
298 | // Command cannot be processed in full screen. |
299 | delegateToController = true; |
300 | refreshView = false; |
301 | remainInFullScreen = false; |
302 | } |
303 | if (logger.isDebugEnabled()) { |
304 | logger.debug("command=" + command |
305 | + ", delegateToController=" + delegateToController |
306 | + ", refreshView=" + refreshView |
307 | + ", remainInFullScreen=" + remainInFullScreen); |
308 | } |
309 | |
310 | if (!remainInFullScreen) { |
311 | performCancel(); |
312 | } |
313 | if (delegateToController) { |
314 | final ActionEvent eventToDelegate = event; |
315 | if (logger.isDebugEnabled()) { |
316 | logger.debug("delegate command to controller: " + command); |
317 | } |
318 | SwingUtilities.invokeLater(new DelegateActionToControllerRunner(eventToDelegate)); |
319 | } |
320 | if (refreshView) { |
321 | invokeLaterRefreshComicView(); |
322 | } |
323 | } catch (Throwable error) { |
324 | final ActionEvent eventToReport = event; |
325 | final Throwable stackToReport = error; |
326 | |
327 | performCancel(); |
328 | SwingUtilities.invokeLater(new ShowErrorRunner(eventToReport, stackToReport)); |
329 | } |
330 | } |
331 | |
332 | //@ ensures !actionListenersAdded |
333 | //@ ensures !settingsChangedListenerAdded |
334 | private void setVisibleFalse() { |
335 | if (actionListenersAdded) { |
336 | frame.removeMouseListener(this); |
337 | frame.removeKeyListener(this); |
338 | actionListenersAdded = false; |
339 | } |
340 | if (settingsChangedListenerAdded) { |
341 | settings.removePropertyChangeListener(PropertyConstants.MOST_RECENT_PAGE, this); |
342 | settingsChangedListenerAdded = false; |
343 | } |
344 | if (controller != null) { |
345 | controller.getComicView().setImageIndex(comicView.getImageIndex()); |
346 | } |
347 | if (defaultDevice != null) { |
348 | defaultDevice.setFullScreenWindow(null); |
349 | defaultDevice = null; |
350 | } |
351 | if (frame != null) { |
352 | frame.removeKeyListener(this); |
353 | frame.dispose(); |
354 | frame = null; |
355 | } |
356 | } |
357 | |
358 | //@ requires !disposed; |
359 | //@ ensures !isVisible(); |
360 | //@ ensures disposed; |
361 | public void dispose() { |
362 | if (isVisible()) { |
363 | setVisible(false); |
364 | } |
365 | if (comicView != null) { |
366 | comicView.dispose(); |
367 | comicView = null; |
368 | } |
369 | if (refreshDisplayRunner != null) { |
370 | refreshDisplayRunner.dispose(); |
371 | refreshDisplayRunner = null; |
372 | } |
373 | setController(null); |
374 | if (settingsChangedListenerAdded) { |
375 | settings.removePropertyChangeListener(PropertyConstants.MOST_RECENT_PAGE, this); |
376 | } |
377 | //@ set disposed = true; |
378 | } |
379 | |
380 | //@ also |
381 | //@ requires event.getPropertyName().equals(PropertyConstants.MOST_RECENT_PAGE); |
382 | public void propertyChange(PropertyChangeEvent event) { |
383 | String newPageText = (String) event.getNewValue(); |
384 | int newPage = Integer.parseInt(newPageText); |
385 | |
386 | logger.info("change page to " + newPageText); |
387 | comicView.setPage(newPage); |
388 | invokeLaterRefreshComicView(); |
389 | } |
390 | |
391 | private void addShortCutKey(KeyStroke keyStroke, String command) { |
392 | if (logger.isDebugEnabled()) { |
393 | logger.debug("assign KeyStroke \"" + keyStroke + "\" to command \"" + command + "\" "); |
394 | } |
395 | keyStrokeToCommandMap.put(keyStroke, command); |
396 | } |
397 | |
398 | private void assignShortCutKeys() { |
399 | // Copy short cuts from menu. |
400 | for (int menuIndex = 0; menuIndex < controller.getJMenuBar().getMenuCount(); menuIndex += 1) { |
401 | JMenu menuToScanForItems = controller.getJMenuBar().getMenu(menuIndex); |
402 | |
403 | for (int menuItemIndex = 0; menuItemIndex < menuToScanForItems.getItemCount(); menuItemIndex += 1) { |
404 | JMenuItem itemToAnalyseForKeyStroke = menuToScanForItems.getItem(menuItemIndex); |
405 | |
406 | if (itemToAnalyseForKeyStroke != null) { |
407 | KeyStroke itemStroke = itemToAnalyseForKeyStroke.getAccelerator(); |
408 | |
409 | if (itemStroke != null) { |
410 | String itemCommand = itemToAnalyseForKeyStroke.getActionCommand(); |
411 | |
412 | addShortCutKey(itemStroke, itemCommand); |
413 | } |
414 | } |
415 | } |
416 | } |
417 | |
418 | // Add additional, non-menu shortcuts. TODO: Consolidate with JomicFrame. |
419 | addShortCutKey(KeyStroke.getKeyStroke("END"), Commands.LAST_PAGE); |
420 | addShortCutKey(KeyStroke.getKeyStroke("HOME"), Commands.FIRST_PAGE); |
421 | addShortCutKey(KeyStroke.getKeyStroke("PAGE_DOWN"), Commands.GO_NEXT); |
422 | addShortCutKey(KeyStroke.getKeyStroke("PAGE_UP"), Commands.GO_PREVIOUS); |
423 | addShortCutKey(KeyStroke.getKeyStroke("alt PAGE_DOWN"), Commands.GO_NEXT_FEW); |
424 | addShortCutKey(KeyStroke.getKeyStroke("alt PAGE_UP"), Commands.GO_PREVIOUS_FEW); |
425 | addShortCutKey(KeyStroke.getKeyStroke("SPACE"), Commands.ADVANCE); |
426 | addShortCutKey(KeyStroke.getKeyStroke("ESCAPE"), Commands.FULL_SCREEN); |
427 | } |
428 | |
429 | private void invokeLaterRefreshComicView() { |
430 | SwingUtilities.invokeLater(refreshDisplayRunner); |
431 | } |
432 | |
433 | public void performCancel() { |
434 | SwingUtilities.invokeLater(new CancelFullScreenRunner()); |
435 | } |
436 | |
437 | //@ ensures !isVisible(); |
438 | public void performCancelAndWait() throws InterruptedException, InvocationTargetException { |
439 | SwingUtilities.invokeAndWait(new CancelFullScreenRunner()); |
440 | } |
441 | |
442 | /** |
443 | * @see #invokeLaterRefreshComicView() |
444 | */ |
445 | private void refreshDisplay() { |
446 | if (Boolean.getBoolean(PropertyConstants.SYSTEM_PROPERTY_PREFIX + PropertyConstants.TEST_BEEP_ON_REPAINT)) { |
447 | jomicTools.beep(); |
448 | } |
449 | if (isVisible()) { |
450 | int screenWidth = frame.getWidth(); |
451 | int screenHeight = frame.getHeight(); |
452 | BufferStrategy bufferStrategy = frame.getBufferStrategy(); |
453 | Graphics2D g2d = (Graphics2D) bufferStrategy.getDrawGraphics(); |
454 | try { |
455 | ComicSheet comicSheet = comicView.getSheetForPage(comicView.getPage()); |
456 | int imageIndex = controller.getComicView().getImageIndex(); |
457 | int leftImageIndex = comicSheet.getLeftImageIndex(); |
458 | RenderedImage leftImage = comicView.getComicImage(leftImageIndex).getImage(); |
459 | RenderedImage rightImage = null; |
460 | |
461 | if (comicSheet.hasRightImage()) { |
462 | int rightImageIndex = comicSheet.getRightImageIndex(); |
463 | |
464 | rightImage = comicView.getComicImage(rightImageIndex).getImage(); |
465 | } |
466 | |
467 | if (settings.useBlur()) { |
468 | String blurMode = settings.getBlurMode(); |
469 | leftImage = imageTools.getBluredImage(leftImage, blurMode); |
470 | if (rightImage != null) { |
471 | rightImage = imageTools.getBluredImage(rightImage, blurMode); |
472 | } |
473 | } |
474 | |
475 | ComicSheetLayout renderer = new ComicSheetLayout(); |
476 | ComicSheetRenderSettings renderSettings = new ComicSheetRenderSettings(); |
477 | |
478 | renderSettings.setTwoPageMode(settings.getTwoPageMode()); |
479 | renderSettings.setSwapLeftAndRightImage(settings.getSwapLeftAndRightImage()); |
480 | renderSettings.setScaleMode(ImageTools.SCALE_FIT); |
481 | renderSettings.setRotation(controller.getComicView().getRotation()); |
482 | |
483 | renderer.renderTo(g2d, screenWidth, screenHeight, imageIndex, comicSheet, |
484 | leftImage, rightImage, renderSettings); |
485 | bufferStrategy.show(); |
486 | } finally { |
487 | g2d.dispose(); |
488 | } |
489 | } |
490 | } |
491 | |
492 | /** |
493 | * Runnable to cancel full-screen mode. |
494 | * |
495 | * @author Thomas Aglassinger. |
496 | */ |
497 | private final class CancelFullScreenRunner implements Runnable |
498 | { |
499 | private JomicFrame oldController; |
500 | |
501 | private CancelFullScreenRunner() { |
502 | oldController = controller; |
503 | } |
504 | |
505 | public void run() { |
506 | setVisible(false); |
507 | if (oldController != null) { |
508 | oldController.getComicView().updateDisplay(); |
509 | oldController.setVisible(true); |
510 | } |
511 | } |
512 | } |
513 | |
514 | /** |
515 | * Runnable to delegate ActionEvent to owning JomicFrame to keep it up to date. |
516 | * |
517 | * @author Thomas Aglassinger. |
518 | */ |
519 | private final class DelegateActionToControllerRunner implements Runnable |
520 | { |
521 | private ActionEvent eventToDelegate; |
522 | |
523 | private DelegateActionToControllerRunner(ActionEvent newEventToDelegate) { |
524 | super(); |
525 | eventToDelegate = newEventToDelegate; |
526 | } |
527 | |
528 | public void run() { |
529 | controller.actionPerformed(eventToDelegate); |
530 | } |
531 | } |
532 | |
533 | /** |
534 | * Runnable to refresh full-screen display. |
535 | * |
536 | * @author Thomas Aglassinger |
537 | */ |
538 | private final class RefreshDisplayRunner implements Runnable |
539 | { |
540 | private ComicModel oldComic; |
541 | |
542 | public RefreshDisplayRunner() { |
543 | super(); |
544 | } |
545 | |
546 | public void run() { |
547 | if (oldComic != comic) { |
548 | setUpComicView(); |
549 | oldComic = comic; |
550 | } |
551 | refreshDisplay(); |
552 | } |
553 | |
554 | public void dispose() { |
555 | oldComic = null; |
556 | } |
557 | } |
558 | |
559 | /** |
560 | * Runnable to show error. |
561 | * |
562 | * @author Thomas Aglassinger. |
563 | */ |
564 | private final class ShowErrorRunner implements Runnable |
565 | { |
566 | private ActionEvent actionToReport; |
567 | private Throwable errorToReport; |
568 | |
569 | private ShowErrorRunner(ActionEvent newActionToReport, Throwable newErrorToReport) { |
570 | super(); |
571 | actionToReport = newActionToReport; |
572 | errorToReport = newErrorToReport; |
573 | } |
574 | |
575 | public void run() { |
576 | jomicTools.showError(actionToReport, errorToReport); |
577 | } |
578 | } |
579 | |
580 | public void keyTyped(KeyEvent keyEvent) { |
581 | // Do nothing. |
582 | } |
583 | |
584 | /** |
585 | * Check if <code>keyEvent</code> can be processed and if so, convert it |
586 | * to an <code>ActionEvent</code> and handle it. |
587 | * |
588 | * @see #actionPerformed(ActionEvent) |
589 | */ |
590 | public void keyPressed(KeyEvent keyEvent) { |
591 | KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent); |
592 | |
593 | logger.debug("key pressed: " + keyStroke); |
594 | String command = (String) keyStrokeToCommandMap.get(keyStroke); |
595 | if (command != null) { |
596 | ActionEvent actionEvent = new ActionEvent(keyEvent.getSource(), ActionEvent.ACTION_PERFORMED, command); |
597 | |
598 | actionPerformed(actionEvent); |
599 | } else { |
600 | logger.debug("ignoring unknown key stroke: " + keyStroke); |
601 | } |
602 | } |
603 | |
604 | public void keyReleased(KeyEvent keyEvent) { |
605 | // Do nothing. |
606 | } |
607 | |
608 | public void mouseClicked(MouseEvent mouseEvent) { |
609 | // Do nothing. |
610 | } |
611 | |
612 | public void mouseEntered(MouseEvent mouseEvent) { |
613 | // Do nothing. |
614 | } |
615 | |
616 | public void mouseExited(MouseEvent mouseEvent) { |
617 | // Do nothing. |
618 | } |
619 | |
620 | public void mousePressed(MouseEvent mouseEvent) { |
621 | // Do nothing. |
622 | } |
623 | |
624 | public void mouseReleased(MouseEvent mouseEvent) { |
625 | if (SwingUtilities.isLeftMouseButton(mouseEvent)) { |
626 | if (ignoreNextLeftMouseButtonReleased) { |
627 | // Right mouse button was pressed with left mouse button down just before, so there |
628 | // is no point in going forward right now. |
629 | ignoreNextLeftMouseButtonReleased = false; |
630 | } else { |
631 | ActionEvent actionEvent = new ActionEvent(mouseEvent.getSource(), |
632 | ActionEvent.ACTION_PERFORMED, Commands.ADVANCE); |
633 | actionPerformed(actionEvent); |
634 | } |
635 | } else if (SwingUtilities.isRightMouseButton(mouseEvent)) { |
636 | boolean leftButtonDown = (mouseEvent.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0; |
637 | |
638 | if (leftButtonDown) { |
639 | ActionEvent actionEvent = new ActionEvent(mouseEvent.getSource(), |
640 | ActionEvent.ACTION_PERFORMED, Commands.RETREAT); |
641 | actionPerformed(actionEvent); |
642 | ignoreNextLeftMouseButtonReleased = true; |
643 | } |
644 | } |
645 | } |
646 | } |