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.comic; |
17 | |
18 | import java.awt.Color; |
19 | import java.awt.Cursor; |
20 | import java.awt.Dimension; |
21 | import java.awt.Graphics2D; |
22 | import java.awt.GridBagConstraints; |
23 | import java.awt.GridBagLayout; |
24 | import java.awt.Point; |
25 | import java.awt.event.ComponentEvent; |
26 | import java.awt.event.ComponentListener; |
27 | import java.awt.image.BufferedImage; |
28 | import java.awt.image.RenderedImage; |
29 | import java.beans.PropertyChangeEvent; |
30 | import java.beans.PropertyChangeListener; |
31 | import java.io.File; |
32 | import java.text.DecimalFormat; |
33 | import java.util.ArrayList; |
34 | import java.util.Arrays; |
35 | import java.util.Collections; |
36 | import java.util.Iterator; |
37 | import java.util.List; |
38 | |
39 | import javax.swing.BorderFactory; |
40 | import javax.swing.JPanel; |
41 | import javax.swing.JScrollBar; |
42 | import javax.swing.JScrollPane; |
43 | import javax.swing.JViewport; |
44 | import javax.swing.SwingUtilities; |
45 | |
46 | import net.sf.jomic.common.ComicSheetRenderSettings; |
47 | import net.sf.jomic.common.PropertyConstants; |
48 | import net.sf.jomic.common.Settings; |
49 | import net.sf.jomic.tools.ColorBox; |
50 | import net.sf.jomic.tools.ErrorTools; |
51 | import net.sf.jomic.tools.FileTools; |
52 | import net.sf.jomic.tools.ImageRenderSettings; |
53 | import net.sf.jomic.tools.ImageTools; |
54 | import net.sf.jomic.tools.LocaleTools; |
55 | import net.sf.jomic.tools.ProgressFrame; |
56 | import net.sf.jomic.tools.StringTools; |
57 | import net.sf.jomic.ui.FullScreenViewer; |
58 | import net.sf.jomic.ui.RenderedImageView; |
59 | |
60 | import org.apache.commons.logging.Log; |
61 | import org.apache.commons.logging.LogFactory; |
62 | |
63 | /** |
64 | * Panel to show one page or sheet of a comic. Depending on the structure of the images and the |
65 | * value of <code>getTwoPageMode()</code>, this can show one or two images: |
66 | * <ul> |
67 | * <li> With <code>getTwoPageMode()</code> returning <code>false</code>, always show 1 image |
68 | * <li> For the frontpage, always show 1 image |
69 | * <li> With the image being wider than high, show 1 image |
70 | * <li> With two consecutive images being higher than wide and <code>getTwoPageMode()</code> |
71 | * returning <code>true</code>, show 2 images. |
72 | * </ul> |
73 | * For more details about this algorithm, refer to the <a |
74 | * href="http://jomic.sourceforge.net/developer-guide.html">Developer Guide</a> . |
75 | * |
76 | * @author Thomas Aglassinger |
77 | */ |
78 | public class ComicView extends JScrollPane implements ComponentListener, PropertyChangeListener |
79 | { |
80 | private ColorBox bottomBox; |
81 | private ComicCache comicCache; |
82 | private ErrorTools errorTools; |
83 | private int imageCount; |
84 | private int imageIndex; |
85 | private RenderedImageView imagePane; |
86 | private ImageTools imageTools; |
87 | private ColorBox leftBox; |
88 | private LocaleTools localeTools; |
89 | private Log logger; |
90 | private ComicModel model; |
91 | private Color oldFillColor; |
92 | |
93 | /** |
94 | * Number of the last page. |
95 | */ |
96 | private int pageCount; |
97 | private JPanel pane; |
98 | private ProgressFrame progressFrame; |
99 | |
100 | private boolean propertyChangeListenerAdded; |
101 | |
102 | /** |
103 | * Properties that cause ComicView to refresh if changed. |
104 | */ |
105 | private String[] refreshCausingProperties; |
106 | private ColorBox rightBox; |
107 | private String scaleMode; |
108 | private Settings settings; |
109 | |
110 | /** |
111 | * List containing ComicSheets for two page mode. |
112 | */ |
113 | private List sheetList; |
114 | |
115 | private StringTools stringTools; |
116 | private ColorBox topBox; |
117 | |
118 | /** |
119 | * Creates a new, empty view. |
120 | * |
121 | * @see #setModel(ComicModel, ProgressFrame) |
122 | */ |
123 | public ComicView() { |
124 | logger = LogFactory.getLog(ComicView.class); |
125 | settings = Settings.instance(); |
126 | comicCache = ComicCache.instance(); |
127 | errorTools = ErrorTools.instance(); |
128 | imageTools = ImageTools.instance(); |
129 | localeTools = LocaleTools.instance(); |
130 | stringTools = StringTools.instance(); |
131 | |
132 | imagePane = new RenderedImageView(); |
133 | pane = new JPanel(); |
134 | topBox = createColorBox(); |
135 | bottomBox = createColorBox(); |
136 | leftBox = createColorBox(); |
137 | rightBox = createColorBox(); |
138 | |
139 | // Specify which property changes will cause a refresh |
140 | refreshCausingProperties = new String[]{ |
141 | PropertyConstants.BLUR_MODE, |
142 | PropertyConstants.BLUR_RADIUS, |
143 | PropertyConstants.BLUR_THRESHOLD, |
144 | ImageRenderSettings.FILL_COLOR, |
145 | ImageRenderSettings.ROTATION, |
146 | ImageRenderSettings.SCALE_MODE, |
147 | ComicSheetRenderSettings.SWAP_LEFT_AND_RIGHT_IMAGE, |
148 | PropertyConstants.SHOW_TOOLBAR, |
149 | ComicSheetRenderSettings.SHOW_TWO_PAGES, |
150 | PropertyConstants.USE_BLUR |
151 | }; |
152 | Arrays.sort(refreshCausingProperties); |
153 | |
154 | // Use GridBagLayout to center the image |
155 | GridBagConstraints constraints = new GridBagConstraints(); |
156 | |
157 | pane.setLayout(new GridBagLayout()); |
158 | constraints.gridx = 0; |
159 | constraints.gridy = 0; |
160 | constraints.anchor = GridBagConstraints.PAGE_START; |
161 | pane.add(imagePane, constraints); |
162 | setViewportView(pane); |
163 | setViewportView(createImagePanel()); |
164 | scaleMode = ImageTools.SCALE_ACTUAL; |
165 | getVerticalScrollBar().setUnitIncrement(settings.getScrollCount()); |
166 | getHorizontalScrollBar().setUnitIncrement(settings.getScrollCount()); |
167 | settings.addPropertyChangeListener(this); |
168 | propertyChangeListenerAdded = true; |
169 | addComponentListener(this); |
170 | } |
171 | |
172 | public void setImageIndex(int newImageIndex) { |
173 | assertImageIndexIsValid(newImageIndex); |
174 | if (logger.isDebugEnabled()) { |
175 | if (isTwoPageMode()) { |
176 | int sheetIndex = getSheetIndexForImage(newImageIndex); |
177 | ComicSheet sheet = getSheet(sheetIndex); |
178 | String sheetText = "(left=" + sheet.getLeftImageIndex(); |
179 | |
180 | if (sheet.hasRightImage()) { |
181 | sheetText += ",right=" + sheet.getRightImageIndex(); |
182 | } |
183 | sheetText += ")"; |
184 | logger.debug("set sheet to " + sheetIndex + ", " + sheetText); |
185 | } else { |
186 | logger.debug("set imageIndex to " + newImageIndex); |
187 | } |
188 | } |
189 | imageIndex = newImageIndex; |
190 | settings.setMostRecentPage(getPage()); |
191 | updateDisplay(); |
192 | |
193 | // Precache next sheet or image |
194 | if (isTwoPageMode()) { |
195 | int nextSheetIndex = getSheetIndexForImage(imageIndex) + 1; |
196 | |
197 | if (nextSheetIndex < sheetList.size()) { |
198 | ComicSheet nextSheet = getSheet(nextSheetIndex); |
199 | |
200 | precacheComicImage(nextSheet.getLeftImageIndex()); |
201 | if (nextSheet.hasRightImage()) { |
202 | precacheComicImage(nextSheet.getRightImageIndex()); |
203 | } |
204 | } |
205 | } else { |
206 | int nextImageIndex = imageIndex + 1; |
207 | |
208 | if (nextImageIndex < getImageCount()) { |
209 | precacheComicImage(nextImageIndex); |
210 | } |
211 | } |
212 | } |
213 | |
214 | /** |
215 | * Set <code>newModel</code> to be the new model from which the images should be fetched, and |
216 | * show front page. |
217 | */ |
218 | public void setModel(ComicModel newModel, ProgressFrame newProgressFrame) { |
219 | setModel(newModel, newProgressFrame, 0); |
220 | } |
221 | |
222 | /** |
223 | * Set <code>newModel</code> to be the new model from which the images should be fetched, and |
224 | * show page number <code>newPage</code>. If there a no enough pages to go to <code>newPage</code> |
225 | * , log a warning an go to front page. |
226 | */ |
227 | public void setModel(ComicModel newModel, ProgressFrame newProgressFrame, int newPage) { |
228 | assert newModel != null; |
229 | assert newPage >= 0 : "newPage=" + newPage; |
230 | |
231 | ComicSheet lastSheet; |
232 | |
233 | // Page number the next sheet starts at. |
234 | pageCount = 0; |
235 | |
236 | model = newModel; |
237 | imageCount = model.getImageCount(); |
238 | progressFrame = newProgressFrame; |
239 | |
240 | if (progressFrame != null) { |
241 | progressFrame.setNote(localeTools.getMessage("panels.comic.progress.layingOutPages")); |
242 | } |
243 | |
244 | sheetList = new ArrayList(imageCount); |
245 | lastSheet = null; |
246 | |
247 | // Front page always uses own sheet. |
248 | if (model.getComicImage(0).isDoublePage()) { |
249 | addSheet(2, 0); |
250 | } else { |
251 | addSheet(1, 0); |
252 | } |
253 | |
254 | for (int i = 1; i < model.getImageCount(); i += 1) { |
255 | ComicImage image = model.getComicImage(i); |
256 | boolean isDoublePage = image.isDoublePage(); |
257 | |
258 | if (lastSheet == null) { |
259 | if (isDoublePage) { |
260 | addSheet(2, i); |
261 | } else { |
262 | lastSheet = addSheet(1, i); |
263 | } |
264 | } else { |
265 | if (isDoublePage) { |
266 | addSheet(2, i); |
267 | } else { |
268 | lastSheet.setRightImageIndex(i); |
269 | pageCount += 1; |
270 | if (logger.isDebugEnabled()) { |
271 | logger.debug("pageCount <- " + pageCount); |
272 | } |
273 | } |
274 | lastSheet = null; |
275 | } |
276 | if (progressFrame != null) { |
277 | progressFrame.setProgress(model.computeProgress(ComicModel.TASK_LAYOUT, i)); |
278 | } |
279 | } |
280 | if (logger.isInfoEnabled()) { |
281 | logger.info("comic has " + pageCount + " pages, " + model.getImageCount() |
282 | + " images, and " + sheetList.size() + " sheets"); |
283 | } |
284 | |
285 | if (newPage < pageCount) { |
286 | setPage(newPage); |
287 | } else { |
288 | logger.warn("cannot go to page " + (newPage + 1) + " because comic has only " |
289 | + (pageCount + 1) + " pages; reverting to front page"); |
290 | goFirst(); |
291 | } |
292 | } |
293 | |
294 | public void setPage(int newPage) { |
295 | assert newPage >= 0; |
296 | assert newPage < pageCount : "newPage=" + newPage + ", pageCount=" + pageCount; |
297 | setImageIndex(getImageIndexForPage(newPage)); |
298 | } |
299 | |
300 | public void setRotateOnlySinglePortraitImages(boolean newRotate) { |
301 | settings.setRotateOnlySinglePortraitImages(newRotate); |
302 | } |
303 | |
304 | public void setScaleMode(String newScaleMode) { |
305 | imageTools.assertIsValidScaleMode(newScaleMode); |
306 | scaleMode = newScaleMode; |
307 | if (scaleMode.equals(ImageTools.SCALE_ACTUAL)) { |
308 | setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); |
309 | setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED); |
310 | } else if (scaleMode.equals(ImageTools.SCALE_FIT)) { |
311 | setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER); |
312 | setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER); |
313 | } else if (scaleMode.equals(ImageTools.SCALE_HEIGHT)) { |
314 | setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); |
315 | setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_NEVER); |
316 | } else if (scaleMode.equals(ImageTools.SCALE_WIDTH)) { |
317 | setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER); |
318 | setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED); |
319 | } |
320 | if (model != null) { |
321 | updateDisplay(); |
322 | } |
323 | } |
324 | |
325 | public void setSwapLeftAndRightImage(boolean newMangaMode) { |
326 | settings.setSwapLeftAndRightImage(newMangaMode); |
327 | } |
328 | |
329 | /** |
330 | * Set two page mode. |
331 | * |
332 | * @param newMode <code>false</code> means that only a single image should be show, <code>true</code> |
333 | * means that two "lean" images should be used to show two pages at once. |
334 | */ |
335 | public void setTwoPageMode(boolean newMode) { |
336 | settings.setTwoPageMode(newMode); |
337 | } |
338 | |
339 | private void setDefaultCursor() { |
340 | setCursor(Cursor.getDefaultCursor()); |
341 | } |
342 | |
343 | private void setWaitCursor() { |
344 | setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); |
345 | } |
346 | |
347 | public ComicImage getComicImage() { |
348 | return model.getComicImage(getImageIndex()); |
349 | } |
350 | |
351 | public ComicImage getComicImage(int somePage) { |
352 | return model.getComicImage(somePage); |
353 | } |
354 | |
355 | /** |
356 | * Get a generic name for the image at <code>index</code>, for example 17.jpg or 043+044.png. |
357 | */ |
358 | public String getGenericComicImageName(int index) { |
359 | String result; |
360 | ComicImage image = model.getComicImage(index); |
361 | ComicSheet sheet = getSheetForImage(index); |
362 | int page = sheet.getPage() + 1; |
363 | DecimalFormat format = stringTools.getLeadingZeroFormat(getPageCount() + 2); |
364 | |
365 | if (image.isDoublePage()) { |
366 | result = format.format(page) + "+" + format.format(page + 1); |
367 | } else { |
368 | if (sheet.getLeftImageIndex() == index) { |
369 | result = format.format(page); |
370 | } else { |
371 | assert sheet.getRightImageIndex() == index; |
372 | result = format.format(page + 1); |
373 | } |
374 | } |
375 | |
376 | String suffix = FileTools.instance().getSuffix(image.getName()); |
377 | |
378 | result += "." + suffix; |
379 | return result; |
380 | } |
381 | |
382 | /** |
383 | * The number of images. |
384 | */ |
385 | public int getImageCount() { |
386 | return imageCount; |
387 | } |
388 | |
389 | /** |
390 | * Get the index of the image currently viewed. |
391 | */ |
392 | // TODO: Clarify the result if the current sheet contains 2 images. |
393 | public int getImageIndex() { |
394 | assertImageIndexIsValid(imageIndex); |
395 | // TODO: Consider manga mode for export. |
396 | return imageIndex; |
397 | } |
398 | |
399 | /** |
400 | * Get the number of the current page; if showing two pages, get the number of the left page. |
401 | */ |
402 | public int getPage() { |
403 | return getPageForImageIndex(getImageIndex()); |
404 | } |
405 | |
406 | /** |
407 | * Get the number of pages (as perceived by the end user). This can differ from the the number |
408 | * of images. For example, a double image counts as 2 pages. |
409 | */ |
410 | public int getPageCount() { |
411 | return pageCount; |
412 | } |
413 | |
414 | /** |
415 | * Get a human readable description of the page(s) currently viewed. |
416 | */ |
417 | public String getPageText() { |
418 | String result; |
419 | |
420 | if (model != null) { |
421 | ComicSheet sheet = getSheet(getCurrentSheetIndex()); |
422 | boolean leftIsDoubleImage = getComicImage(sheet.getLeftImageIndex()).isDoublePage(); |
423 | int leftPage = sheet.getPage() + 1; |
424 | int leftNumber = leftPage; |
425 | int rightNumber = leftPage; |
426 | boolean showTwoPageNumbers; |
427 | |
428 | if (leftIsDoubleImage || (isTwoPageMode() && sheet.hasRightImage())) { |
429 | // We are really showing two pages right now. |
430 | if (isMangaMode()) { |
431 | leftNumber += 1; |
432 | } else { |
433 | rightNumber += 1; |
434 | } |
435 | showTwoPageNumbers = true; |
436 | } else { |
437 | if (sheet.getLeftImageIndex() != getImageIndex()) { |
438 | // The current page is the right one of the current sheet. |
439 | leftNumber += 1; |
440 | } |
441 | showTwoPageNumbers = false; |
442 | } |
443 | |
444 | if (showTwoPageNumbers) { |
445 | result = localeTools.getMessage("panels.comic.pageText.double", |
446 | new Object[]{new Integer(leftNumber), new Integer(rightNumber), new Integer(pageCount)}); |
447 | } else { |
448 | result = localeTools.getMessage("panels.comic.pageText.single", |
449 | new Object[]{new Integer(leftNumber), new Integer(pageCount)}); |
450 | } |
451 | } else { |
452 | result = ""; |
453 | } |
454 | return result; |
455 | } |
456 | |
457 | /** |
458 | * Render the image to fit in the current JViewport while applying Settings. |
459 | * |
460 | * @see JViewport |
461 | * @see Settings |
462 | */ |
463 | public RenderedImage getRenderedImage() { |
464 | RenderedImage result = null; |
465 | JViewport port = getViewport(); |
466 | int portWidth = port.getWidth(); |
467 | int portHeight = port.getHeight(); |
468 | int viewWidth = portWidth; |
469 | int viewHeight = portHeight; |
470 | |
471 | if ((viewWidth > 2) && (viewHeight > 2)) { |
472 | Dimension expectedViewSize = getExpectedViewSize(viewWidth, viewHeight); |
473 | int expectedViewWidth = expectedViewSize.width; |
474 | int expectedViewHeight = expectedViewSize.height; |
475 | |
476 | if ((viewWidth != expectedViewWidth) || (viewHeight != expectedViewHeight)) { |
477 | // FIXME: move side-effect causing getViewport().setSize() and related code to updateDisplay(). |
478 | getViewport().setSize(expectedViewSize); |
479 | viewWidth = expectedViewWidth; |
480 | viewHeight = expectedViewHeight; |
481 | } |
482 | if ((viewWidth <= 0) || (viewHeight <= 0)) { |
483 | assert false : "view=" + viewWidth + "x" + viewHeight + "; port=" + portWidth + "x" + portHeight |
484 | + "; expected=" + expectedViewWidth + "x" + expectedViewHeight; |
485 | } |
486 | result = getRenderedImage(viewWidth, viewHeight); |
487 | } |
488 | return result; |
489 | } |
490 | |
491 | /** |
492 | * Get the current rotation. |
493 | * |
494 | * @return one of ImageTools.ROTATE_* |
495 | * @see ImageTools#ROTATE_NONE |
496 | * @see ImageTools#ROTATE_CLOCKWISE |
497 | * @see ImageTools#ROTATE_COUNTERCLOCKWISE |
498 | * @see ImageTools#ROTATE_UPSIDE_DOWN |
499 | */ |
500 | public int getRotation() { |
501 | return settings.getRotation(); |
502 | } |
503 | |
504 | public String getScaleMode() { |
505 | return scaleMode; |
506 | } |
507 | |
508 | public ComicSheet getSheetForPage(int somePage) { |
509 | int index = getSheetIndexForPage(somePage); |
510 | |
511 | return (ComicSheet) sheetList.get(index); |
512 | } |
513 | |
514 | /** |
515 | * Is the current page or sheet the first one? |
516 | */ |
517 | public boolean isFirst() { |
518 | boolean result; |
519 | |
520 | if (isTwoPageMode()) { |
521 | result = getSheetIndexForImage(imageIndex) == 0; |
522 | } else { |
523 | result = imageIndex == 0; |
524 | } |
525 | return result; |
526 | } |
527 | |
528 | /** |
529 | * Is the current page or sheet the last one? |
530 | */ |
531 | public boolean isLast() { |
532 | boolean result; |
533 | |
534 | if (isTwoPageMode()) { |
535 | result = getSheetIndexForImage(imageIndex) == sheetList.size() - 1; |
536 | } else { |
537 | result = imageIndex == imageCount - 1; |
538 | } |
539 | return result; |
540 | } |
541 | |
542 | public boolean isMangaMode() { |
543 | return settings.getSwapLeftAndRightImage(); |
544 | } |
545 | |
546 | public boolean isTwoPageMode() { |
547 | return settings.getTwoPageMode(); |
548 | } |
549 | |
550 | int getCurrentSheetIndex() { |
551 | return getSheetIndexForImage(getImageIndex()); |
552 | } |
553 | |
554 | ComicSheet getSheet(int sheetIndex) { |
555 | return (ComicSheet) sheetList.get(sheetIndex); |
556 | } |
557 | |
558 | private Dimension getExpectedViewSize(int viewWidth, int viewHeight) { |
559 | Dimension result; |
560 | int sheetWidth; |
561 | int sheetHeight; |
562 | |
563 | if (isTwoPageMode()) { |
564 | ComicSheet comicSheet = getSheetForPage(getPage()); |
565 | int leftImageIndex = comicSheet.getLeftImageIndex(); |
566 | ComicImage leftImage = getComicImage(leftImageIndex); |
567 | int leftImageWidth = leftImage.getWidth(); |
568 | int leftImageHeight = leftImage.getHeight(); |
569 | int rightImageWidth; |
570 | int rightImageHeight; |
571 | |
572 | if (comicSheet.hasRightImage()) { |
573 | int rightImageIndex = comicSheet.getRightImageIndex(); |
574 | ComicImage rightImage = getComicImage(rightImageIndex); |
575 | |
576 | rightImageWidth = rightImage.getWidth(); |
577 | rightImageHeight = rightImage.getHeight(); |
578 | } else { |
579 | rightImageWidth = 0; |
580 | rightImageHeight = 0; |
581 | } |
582 | |
583 | sheetWidth = leftImageWidth + rightImageWidth; |
584 | sheetHeight = Math.max(leftImageHeight, rightImageHeight); |
585 | } else { |
586 | sheetWidth = getComicImage().getWidth(); |
587 | sheetHeight = getComicImage().getHeight(); |
588 | } |
589 | |
590 | if ((getRotation() == ImageTools.ROTATE_CLOCKWISE) || getRotation() == ImageTools.ROTATE_COUNTERCLOCKWISE) { |
591 | int swapSize; |
592 | |
593 | swapSize = sheetWidth; |
594 | sheetWidth = sheetHeight; |
595 | sheetHeight = swapSize; |
596 | } |
597 | |
598 | int areaWidth; |
599 | int areaHeight; |
600 | |
601 | if (getScaleMode().equals(ImageTools.SCALE_ACTUAL)) { |
602 | areaWidth = sheetWidth; |
603 | areaHeight = sheetHeight; |
604 | } else if (getScaleMode().equals(ImageTools.SCALE_FIT)) { |
605 | areaWidth = viewWidth; |
606 | areaHeight = viewHeight; |
607 | } else if (getScaleMode().equals(ImageTools.SCALE_HEIGHT)) { |
608 | areaWidth = Integer.MAX_VALUE; |
609 | areaHeight = viewHeight; |
610 | } else if (getScaleMode().equals(ImageTools.SCALE_WIDTH)) { |
611 | areaWidth = viewWidth; |
612 | areaHeight = Integer.MAX_VALUE; |
613 | } else { |
614 | areaWidth = 0; |
615 | areaHeight = 0; |
616 | assert false : "scaleMode=" + getScaleMode(); |
617 | } |
618 | result = imageTools.getSqueezedDimension(areaWidth, areaHeight, sheetWidth, sheetHeight, |
619 | settings.getScaleMode()); |
620 | return result; |
621 | } |
622 | |
623 | private int getImageIndexForPage(int newPage) { |
624 | assert newPage >= 0; |
625 | assert newPage < pageCount; |
626 | ComicSheet sheet = getSheetForPage(newPage); |
627 | int imageIndexForPage = sheet.getLeftImageIndex(); |
628 | |
629 | if (sheet.getPage() != newPage) { |
630 | boolean leftIsDoubleImage = getComicImage(sheet.getLeftImageIndex()).isDoublePage(); |
631 | |
632 | assert sheet.hasRightImage() || leftIsDoubleImage |
633 | : "newPage=" + newPage + ", sheet.page=" + sheet.getPage() |
634 | + ", leftIsDoubleImage=" + leftIsDoubleImage; |
635 | if (!leftIsDoubleImage) { |
636 | imageIndexForPage = sheet.getRightImageIndex(); |
637 | } |
638 | } |
639 | return imageIndexForPage; |
640 | } |
641 | |
642 | private int getIntFor(boolean some) { |
643 | // TODO: Isn't there any shorter way to convert a boolean to an int (not counting the "?" operator) |
644 | int result; |
645 | |
646 | if (some) { |
647 | result = 1; |
648 | } else { |
649 | result = 0; |
650 | } |
651 | return result; |
652 | } |
653 | |
654 | private int getPageForImageIndex(int index) { |
655 | assertImageIndexIsValid(index); |
656 | |
657 | ComicSheet sheet = getSheetForImage(index); |
658 | int result = sheet.getPage(); |
659 | |
660 | return result; |
661 | } |
662 | |
663 | private RenderedImage getRenderedImage(int viewWidth, int viewHeight) { |
664 | assert viewWidth > 0 : "viewWidth=" + viewWidth; |
665 | assert viewHeight > 0 : "viewHeight=" + viewHeight; |
666 | |
667 | BufferedImage result = null; |
668 | |
669 | if (Boolean.getBoolean(PropertyConstants.SYSTEM_PROPERTY_PREFIX + PropertyConstants.TEST_BEEP_ON_REPAINT)) { |
670 | // TODO: jomicTools.beep(); |
671 | } |
672 | if (isVisible()) { |
673 | ComicSheet comicSheet = getSheetForPage(getPage()); |
674 | int leftImageIndex = comicSheet.getLeftImageIndex(); |
675 | RenderedImage leftImage = getComicImage(leftImageIndex).getImage(); |
676 | RenderedImage rightImage = null; |
677 | |
678 | result = new BufferedImage(viewWidth, viewHeight, BufferedImage.TYPE_INT_ARGB); |
679 | |
680 | Graphics2D g2d = (Graphics2D) result.getGraphics(); |
681 | |
682 | try { |
683 | if (comicSheet.hasRightImage()) { |
684 | int rightImageIndex = comicSheet.getRightImageIndex(); |
685 | |
686 | rightImage = getComicImage(rightImageIndex).getImage(); |
687 | } |
688 | |
689 | if (settings.useBlur()) { |
690 | String blurMode = settings.getBlurMode(); |
691 | |
692 | leftImage = imageTools.getBluredImage(leftImage, blurMode); |
693 | if (rightImage != null) { |
694 | rightImage = imageTools.getBluredImage(rightImage, blurMode); |
695 | } |
696 | } |
697 | |
698 | ComicSheetLayout renderer = new ComicSheetLayout(); |
699 | ComicSheetRenderSettings renderSettings = new ComicSheetRenderSettings(); |
700 | |
701 | renderSettings.setRotation(getRotation()); |
702 | renderSettings.setRotateOnlySinglePortraitImages(settings.getRotateOnlySinglePortraitImages()); |
703 | renderSettings.setScaleMode(settings.getScaleMode()); |
704 | renderSettings.setSwapLeftAndRightImage(settings.getSwapLeftAndRightImage()); |
705 | renderSettings.setTwoPageMode(settings.getTwoPageMode()); |
706 | |
707 | renderer.renderTo(g2d, viewWidth, viewHeight, getImageIndex(), comicSheet, |
708 | leftImage, rightImage, renderSettings); |
709 | } finally { |
710 | g2d.dispose(); |
711 | } |
712 | } |
713 | return result; |
714 | } |
715 | |
716 | private ComicSheet getSheetForImage(int someImageIndex) { |
717 | int index = getSheetIndexForImage(someImageIndex); |
718 | |
719 | return (ComicSheet) sheetList.get(index); |
720 | } |
721 | |
722 | /** |
723 | * Get the sheet on which the image <code>someImageIndex</code> is placed. |
724 | */ |
725 | private int getSheetIndexForImage(int someImageIndex) { |
726 | int result = 0; |
727 | ComicSheet targetSheet = new ComicSheet(Integer.MAX_VALUE, someImageIndex); |
728 | |
729 | result = Collections.binarySearch(sheetList, targetSheet, new ComicSheetindexOverlapComparator()); |
730 | |
731 | return result; |
732 | } |
733 | |
734 | /** |
735 | * Get the sheet on which the page <code>somePage</code> is placed. |
736 | */ |
737 | private int getSheetIndexForPage(int somePage) { |
738 | int result = 0; |
739 | boolean found = false; |
740 | Iterator rider = sheetList.iterator(); |
741 | |
742 | // TODO: use binary search |
743 | while (rider.hasNext() && !found) { |
744 | ComicSheet sheet = (ComicSheet) rider.next(); |
745 | |
746 | int sheetPage = sheet.getPage(); |
747 | int leftImageIndex = sheet.getLeftImageIndex(); |
748 | ComicImage leftComicImage = getComicImage(leftImageIndex); |
749 | boolean hasTwoPages = sheet.hasRightImage() || leftComicImage.isDoublePage(); |
750 | |
751 | if ((sheetPage == somePage) || (hasTwoPages && (sheetPage + 1 == somePage))) { |
752 | found = true; |
753 | } else { |
754 | result += 1; |
755 | } |
756 | } |
757 | return result; |
758 | } |
759 | |
760 | public void componentHidden(ComponentEvent event) { |
761 | // Do nothing. |
762 | } |
763 | |
764 | public void componentMoved(ComponentEvent event) { |
765 | // Do nothing. |
766 | } |
767 | |
768 | public void componentResized(ComponentEvent event) { |
769 | updateDisplay(); |
770 | } |
771 | |
772 | public void componentShown(ComponentEvent event) { |
773 | // Do nothing. |
774 | } |
775 | |
776 | public void dispose() { |
777 | if (propertyChangeListenerAdded) { |
778 | settings.removePropertyChangeListener(this); |
779 | } |
780 | removeComponentListener(this); |
781 | } |
782 | |
783 | /** |
784 | * Go to first page. |
785 | */ |
786 | public void goFirst() { |
787 | setImageIndex(0); |
788 | } |
789 | |
790 | /** |
791 | * Go to last page. |
792 | */ |
793 | public void goLast() { |
794 | setImageIndex(imageCount - 1); |
795 | } |
796 | |
797 | /** |
798 | * Go to next page or sheet. |
799 | */ |
800 | public void goNext() { |
801 | goNext(1); |
802 | } |
803 | |
804 | /** |
805 | * Go a few pages/sheet forward. |
806 | * |
807 | * @see Settings#getFew() |
808 | */ |
809 | public void goNextFew() { |
810 | goNext(settings.getFew()); |
811 | } |
812 | |
813 | /** |
814 | * Go <code>count</code> pages or sheets backwards. |
815 | */ |
816 | public void goPrevious(int count) { |
817 | assert !isFirst(); |
818 | assert count > 0; |
819 | if (isTwoPageMode()) { |
820 | int previousSheetIndex = getCurrentSheetIndex() - count; |
821 | ComicSheet nextSheet = getSheet(Math.max(previousSheetIndex, 0)); |
822 | |
823 | setImageIndex(nextSheet.getLeftImageIndex()); |
824 | } else { |
825 | setImageIndex(Math.max(getImageIndex() - count, 0)); |
826 | } |
827 | } |
828 | |
829 | /** |
830 | * Go to previous page or sheet. |
831 | */ |
832 | public void goPrevious() { |
833 | goPrevious(1); |
834 | } |
835 | |
836 | |
837 | /** |
838 | * Go a few pages/sheets backwards. |
839 | * |
840 | * @see Settings#getFew() |
841 | */ |
842 | public void goPreviousFew() { |
843 | goPrevious(settings.getFew()); |
844 | } |
845 | |
846 | public void propertyChange(PropertyChangeEvent event) { |
847 | try { |
848 | assert event != null; |
849 | String propertyName = event.getPropertyName(); |
850 | |
851 | assert propertyName != null; |
852 | String oldValue = (String) event.getOldValue(); |
853 | String value = (String) event.getNewValue(); |
854 | boolean refreshComicView = stringTools.equalsAnyOf(refreshCausingProperties, propertyName); |
855 | |
856 | // if necessary, refresh the comic view |
857 | if (refreshComicView) { |
858 | Runnable updateRunner = |
859 | new Runnable() |
860 | { |
861 | public void run() { |
862 | updateDisplay(); |
863 | } |
864 | }; |
865 | |
866 | SwingUtilities.invokeLater(updateRunner); |
867 | } else { |
868 | if (logger.isDebugEnabled()) { |
869 | logger.debug("ignoring change of property " + stringTools.sourced(propertyName) |
870 | + " from " + stringTools.sourced(oldValue) |
871 | + " to " + stringTools.sourced(value)); |
872 | } |
873 | } |
874 | } catch (Throwable error) { |
875 | errorTools.showError(event, error); |
876 | } |
877 | } |
878 | |
879 | public void rotateLeft() { |
880 | settings.setRotation(imageTools.getLeftRotation(getRotation())); |
881 | } |
882 | |
883 | public void rotateRight() { |
884 | settings.setRotation(imageTools.getRightRotation(getRotation())); |
885 | } |
886 | |
887 | /** |
888 | * Scroll to start of page. Normally this means the left top corner, unless the left and right |
889 | * image are swapped, in which case it means the top right corner. |
890 | */ |
891 | public void scrollHome() { |
892 | Point home; |
893 | boolean useTop; |
894 | boolean useLeft; |
895 | JViewport viewPort = getViewport(); |
896 | int viewWidth = viewPort.getWidth(); |
897 | int viewHeight = viewPort.getHeight(); |
898 | boolean rotatedCounterClockwise = getRotation() == ImageTools.ROTATE_COUNTERCLOCKWISE; |
899 | boolean rotatedClockwise = getRotation() == ImageTools.ROTATE_CLOCKWISE; |
900 | |
901 | if (rotatedClockwise) { |
902 | useLeft = false; |
903 | useTop = true; |
904 | } else if (rotatedCounterClockwise) { |
905 | useLeft = true; |
906 | useTop = false; |
907 | } else if (getRotation() == ImageTools.ROTATE_NONE) { |
908 | useLeft = true; |
909 | useTop = true; |
910 | } else if (getRotation() == ImageTools.ROTATE_UPSIDE_DOWN) { |
911 | useLeft = false; |
912 | useTop = false; |
913 | } else { |
914 | assert false : "rotation=" + getRotation(); |
915 | useLeft = true; |
916 | useTop = true; |
917 | } |
918 | |
919 | if (isMangaMode()) { |
920 | if (rotatedClockwise || rotatedCounterClockwise) { |
921 | useTop = !useTop; |
922 | } else { |
923 | useLeft = !useLeft; |
924 | } |
925 | } |
926 | |
927 | home = new Point(viewWidth * getIntFor(!useLeft), viewHeight * getIntFor(!useTop)); |
928 | viewPort.setViewPosition(home); |
929 | } |
930 | |
931 | public void scrollHorizontally(final int notches) { |
932 | scroll(getHorizontalScrollBar(), notches); |
933 | } |
934 | |
935 | public void scrollVertically(final int notches) { |
936 | scroll(getVerticalScrollBar(), notches); |
937 | } |
938 | |
939 | public void updateDisplay() { |
940 | if (!FullScreenViewer.instance().isVisible()) { |
941 | setWaitCursor(); |
942 | try { |
943 | if (model != null) { |
944 | RenderedImage renderedImage = getRenderedImage(); |
945 | |
946 | if (renderedImage != null) { |
947 | imagePane.set(renderedImage); |
948 | } |
949 | } |
950 | updateColorBoxes(); |
951 | } finally { |
952 | setDefaultCursor(); |
953 | } |
954 | } |
955 | } |
956 | |
957 | /** |
958 | * Add a new sheet with the left image identified by <code>newLeftImageIndex</code>. After |
959 | * that, increment <code>pageCount</code> by <code>pageIncrement</code>. |
960 | */ |
961 | private ComicSheet addSheet(int pageIncrement, int newLeftImageIndex) { |
962 | assert (pageIncrement == 1) || (pageIncrement == 2) : "pageIncrement=" + pageIncrement; |
963 | assert newLeftImageIndex >= 0; |
964 | ComicSheet result = new ComicSheet(pageCount, newLeftImageIndex); |
965 | |
966 | sheetList.add(result); |
967 | pageCount += pageIncrement; |
968 | if (logger.isDebugEnabled()) { |
969 | logger.debug("pageCount <- " + pageCount); |
970 | } |
971 | return result; |
972 | } |
973 | |
974 | private void assertImageIndexIsValid(int someImageIndex) { |
975 | assert someImageIndex >= 0 : "someImageIndex=" + someImageIndex; |
976 | assert someImageIndex < imageCount : "someImageIndex=" + someImageIndex + ", imageIndexCount=" + imageCount; |
977 | } |
978 | |
979 | private ColorBox createColorBox() { |
980 | ColorBox result = new ColorBox(settings.getFillColor()); |
981 | |
982 | result.setPreferredSize(new Dimension(0, 0)); |
983 | return result; |
984 | } |
985 | |
986 | private JPanel createImagePanel() { |
987 | assert topBox != null; |
988 | assert bottomBox != null; |
989 | assert leftBox != null; |
990 | assert rightBox != null; |
991 | assert imagePane != null; |
992 | JPanel result = new JPanel(new GridBagLayout()); |
993 | GridBagConstraints c = new GridBagConstraints(); |
994 | |
995 | c.gridx = 0; |
996 | c.gridy = 0; |
997 | c.gridwidth = GridBagConstraints.REMAINDER; |
998 | c.weightx = 1.0; |
999 | c.weighty = 1.0; |
1000 | c.fill = GridBagConstraints.BOTH; |
1001 | result.add(topBox, c); |
1002 | c.gridy += 1; |
1003 | c.gridwidth = 1; |
1004 | c.weighty = 0.0; |
1005 | result.add(leftBox, c); |
1006 | c.gridx += 1; |
1007 | c.weightx = 0.0; |
1008 | c.fill = GridBagConstraints.NONE; |
1009 | result.add(imagePane, c); |
1010 | c.gridx += 1; |
1011 | c.weightx = 1.0; |
1012 | c.fill = GridBagConstraints.BOTH; |
1013 | result.add(rightBox, c); |
1014 | c.gridx = 0; |
1015 | c.gridy += 1; |
1016 | c.weighty = 1.0; |
1017 | c.gridwidth = GridBagConstraints.REMAINDER; |
1018 | result.add(bottomBox, c); |
1019 | result.setBorder(BorderFactory.createEmptyBorder()); |
1020 | return result; |
1021 | } |
1022 | |
1023 | /** |
1024 | * Go <code>count</code> pages or sheets forward. |
1025 | */ |
1026 | private void goNext(int count) { |
1027 | assert !isLast(); |
1028 | assert count > 0; |
1029 | if (isTwoPageMode()) { |
1030 | int nextSheetIndex = getCurrentSheetIndex() + count; |
1031 | ComicSheet nextSheet = getSheet(Math.min(nextSheetIndex, sheetList.size() - 1)); |
1032 | |
1033 | setImageIndex(nextSheet.getLeftImageIndex()); |
1034 | } else { |
1035 | setImageIndex(Math.min(getImageIndex() + count, imageCount - 1)); |
1036 | } |
1037 | } |
1038 | |
1039 | private void precacheComicImage(int index) { |
1040 | assert index >= 0; |
1041 | assert index < model.getImageCount(); |
1042 | |
1043 | if (logger.isDebugEnabled()) { |
1044 | logger.debug("precaching image " + index); |
1045 | } |
1046 | |
1047 | // Compute a thumbnail to make sure all pixels are "touched" |
1048 | // and transferred into the system's or CPU's memory cache. |
1049 | ComicImage comicImage = getComicImage(index); |
1050 | File imageFile = comicImage.getFile(); |
1051 | |
1052 | PrecachedImageInCacheListener precachedListener = new PrecachedImageInCacheListener(); |
1053 | |
1054 | comicCache.getThumbnail(imageFile, precachedListener); |
1055 | } |
1056 | |
1057 | private void scroll(final JScrollBar bar, final int notches) { |
1058 | assert bar != null; |
1059 | bar.setValue(bar.getValue() + notches * settings.getScrollCount()); |
1060 | } |
1061 | |
1062 | private void updateColorBoxes() { |
1063 | Color fillColor = settings.getFillColor(); |
1064 | |
1065 | if (!fillColor.equals(oldFillColor)) { |
1066 | topBox.setColor(fillColor); |
1067 | leftBox.setColor(fillColor); |
1068 | rightBox.setColor(fillColor); |
1069 | bottomBox.setColor(fillColor); |
1070 | } |
1071 | } |
1072 | } |