EMMA Coverage Report (generated Sat Oct 08 11:41:37 CEST 2011)
[all classes][net.sf.jomic.comic]

COVERAGE SUMMARY FOR SOURCE FILE [ComicThumbView.java]

nameclass, %method, %block, %line, %
ComicThumbView.java100% (2/2)33%  (14/43)63%  (633/1003)64%  (129.1/203)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ComicThumbView100% (1/1)26%  (10/39)61%  (415/684)58%  (88.2/152)
addSelectionInterval (int, int): void 0%   (0/1)0%   (0/6)0%   (0/2)
clearSelection (): void 0%   (0/1)0%   (0/4)0%   (0/2)
componentHidden (ComponentEvent): void 0%   (0/1)0%   (0/1)0%   (0/1)
componentMoved (ComponentEvent): void 0%   (0/1)0%   (0/1)0%   (0/1)
componentResized (ComponentEvent): void 0%   (0/1)0%   (0/3)0%   (0/2)
componentShown (ComponentEvent): void 0%   (0/1)0%   (0/1)0%   (0/1)
getAnchorSelectionIndex (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getLeadSelectionIndex (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getMaxSelectionIndex (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getMinSelectionIndex (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getSelectionColor (): Color 0%   (0/1)0%   (0/3)0%   (0/1)
getSelectionMode (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getValueIsAdjusting (): boolean 0%   (0/1)0%   (0/4)0%   (0/1)
insertIndexInterval (int, int, boolean): void 0%   (0/1)0%   (0/7)0%   (0/2)
isSelectedIndex (int): boolean 0%   (0/1)0%   (0/5)0%   (0/1)
isSelectionEmpty (): boolean 0%   (0/1)0%   (0/4)0%   (0/1)
mouseClicked (MouseEvent): void 0%   (0/1)0%   (0/1)0%   (0/1)
mouseExited (MouseEvent): void 0%   (0/1)0%   (0/1)0%   (0/1)
mousePressed (MouseEvent): void 0%   (0/1)0%   (0/1)0%   (0/1)
mouseReleased (MouseEvent): void 0%   (0/1)0%   (0/52)0%   (0/11)
mouseWheelMoved (MouseWheelEvent): void 0%   (0/1)0%   (0/29)0%   (0/5)
removeIndexInterval (int, int): void 0%   (0/1)0%   (0/6)0%   (0/2)
removeSelectionInterval (int, int): void 0%   (0/1)0%   (0/6)0%   (0/2)
setAnchorSelectionIndex (int): void 0%   (0/1)0%   (0/5)0%   (0/2)
setLeadSelectionIndex (int): void 0%   (0/1)0%   (0/5)0%   (0/2)
setSelectionColor (Color): void 0%   (0/1)0%   (0/4)0%   (0/2)
setSelectionInterval (int, int): void 0%   (0/1)0%   (0/6)0%   (0/2)
setSelectionMode (int): void 0%   (0/1)0%   (0/5)0%   (0/2)
setValueIsAdjusting (boolean): void 0%   (0/1)0%   (0/5)0%   (0/2)
stateChanged (ChangeEvent): void 100% (1/1)52%  (17/33)79%  (4/5)
getThumbIndexAtOrAfterY (int): int 100% (1/1)66%  (45/68)72%  (10.1/14)
addListSelectionListener (ListSelectionListener): void 100% (1/1)69%  (9/13)83%  (2.5/3)
removeListSelectionListener (ListSelectionListener): void 100% (1/1)69%  (9/13)83%  (2.5/3)
<static initializer> 100% (1/1)80%  (12/15)80%  (0.8/1)
setComic (ComicModel): void 100% (1/1)87%  (199/228)93%  (36.4/39)
ComicThumbView (): void 100% (1/1)94%  (85/90)100% (19.9/20)
dispose (): void 100% (1/1)100% (15/15)100% (5/5)
mouseEntered (MouseEvent): void 100% (1/1)100% (1/1)100% (1/1)
updateScrollbarModel (): void 100% (1/1)100% (23/23)100% (6/6)
     
class ComicThumbView$ThumbPanel100% (1/1)100% (4/4)68%  (218/319)80%  (40.9/51)
<static initializer> 100% (1/1)53%  (8/15)53%  (0.5/1)
paintComponent (Graphics): void 100% (1/1)68%  (201/295)79%  (35.4/45)
ComicThumbView$ThumbPanel (ComicThumbView): void 100% (1/1)100% (6/6)100% (3/3)
imageCached (File): void 100% (1/1)100% (3/3)100% (2/2)

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/>.
16package net.sf.jomic.comic;
17 
18import java.awt.Adjustable;
19import java.awt.BorderLayout;
20import java.awt.Color;
21import java.awt.Dimension;
22import java.awt.Graphics;
23import java.awt.Graphics2D;
24import java.awt.event.ComponentEvent;
25import java.awt.event.ComponentListener;
26import java.awt.event.MouseEvent;
27import java.awt.event.MouseListener;
28import java.awt.event.MouseWheelEvent;
29import java.awt.event.MouseWheelListener;
30import java.awt.geom.AffineTransform;
31import java.awt.geom.Rectangle2D;
32import java.awt.image.RenderedImage;
33import java.io.File;
34import java.util.Arrays;
35 
36import javax.swing.DefaultListSelectionModel;
37import javax.swing.JComponent;
38import javax.swing.JPanel;
39import javax.swing.JScrollBar;
40import javax.swing.ListSelectionModel;
41import javax.swing.UIManager;
42import javax.swing.event.ChangeEvent;
43import javax.swing.event.ChangeListener;
44import javax.swing.event.ListSelectionListener;
45 
46import net.sf.jomic.tools.ImageInCacheListener;
47import net.sf.jomic.tools.ImageTools;
48import net.sf.jomic.tools.MutexLock;
49 
50import org.apache.commons.logging.Log;
51import org.apache.commons.logging.LogFactory;
52 
53/**
54 *  JPanel to show all pages of a comic as thumbnails.
55 *
56 * @author    Thomas Aglassinger
57 */
58public class ComicThumbView
59         extends JPanel
60         implements ChangeListener, ComponentListener, ListSelectionModel, MouseListener, MouseWheelListener
61{
62    private static final int INSET_X = 5;
63    private static final int INSET_Y = 2;
64    private static final int PREFERRED_THUMBS_IN_VIEW = 5;
65    private ComicModel comic;
66    private ComicCache comicCache;
67    private MutexLock comicLock;
68    private ImageTools imageTools;
69    private Log logger;
70    private int maxThumbHeight;
71    private int maxThumbWidth;
72    private JScrollBar scrollBar;
73    private Color selectionColor;
74    private ListSelectionModel selectionModel;
75    private ThumbPanel thumbPanel;
76    private Dimension[] thumbSizes;
77    private int[] thumbYs;
78    private int totalHeight;
79    private int totalWidth;
80 
81    public ComicThumbView() {
82        super();
83        logger = LogFactory.getLog(ComicThumbView.class);
84        imageTools = ImageTools.instance();
85        comicCache = ComicCache.instance();
86 
87        comicLock = new MutexLock("comic");
88        maxThumbWidth = comicCache.getThumbWidth();
89        maxThumbHeight = comicCache.getThumbHeight();
90        selectionColor = UIManager.getColor("TextArea.selectionBackground");
91        selectionModel = new DefaultListSelectionModel();
92        selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
93        setLayout(new BorderLayout());
94        thumbPanel = new ThumbPanel();
95        scrollBar = new JScrollBar(Adjustable.VERTICAL);
96        scrollBar.getModel().addChangeListener(this);
97 
98        add(thumbPanel, BorderLayout.CENTER);
99        add(scrollBar, BorderLayout.EAST);
100        setComic(null);
101        addMouseListener(this);
102        addMouseWheelListener(this);
103    }
104 
105    public void setAnchorSelectionIndex(int arg0) {
106        selectionModel.setAnchorSelectionIndex(arg0);
107    }
108 
109    public void setComic(ComicModel newComic) {
110        synchronized (comicLock) {
111            comic = newComic;
112            totalHeight = 0;
113            totalWidth = 0;
114            if (comic == null) {
115                thumbSizes = null;
116                thumbYs = null;
117            } else {
118                // Compute thumb sizes
119                int imageCount = comic.getImageCount();
120 
121                thumbSizes = new Dimension[imageCount];
122                thumbYs = new int[imageCount];
123                for (int i = 0; i < imageCount; i += 1) {
124                    ComicImage comicImage = comic.getComicImage(i);
125                    Dimension imageSize = new Dimension(comicImage.getWidth(), comicImage.getHeight());
126                    Dimension thumbSize = ImageTools.instance().getSqueezedDimension(
127                            maxThumbWidth, maxThumbHeight, imageSize.width, imageSize.height, ImageTools.SCALE_FIT);
128                    int thumbWidth = thumbSize.width;
129 
130                    if (thumbWidth > totalWidth) {
131                        totalWidth = thumbWidth;
132                    }
133                    thumbSizes[i] = thumbSize;
134                    thumbYs[i] = totalHeight;
135                    totalHeight += thumbSize.height + INSET_Y;
136                }
137                totalHeight += INSET_Y;
138                if (logger.isDebugEnabled()) {
139                    int lastThumbIndex = thumbYs.length - 1;
140 
141                    logger.debug("thumbYs[" + lastThumbIndex + "=" + thumbYs[lastThumbIndex]);
142                }
143            }
144        }
145        if (logger.isInfoEnabled()) {
146            logger.info("total size=" + totalWidth + "x" + totalHeight);
147        }
148        scrollBar.setEnabled(comic != null);
149        updateScrollbarModel();
150        selectionModel.clearSelection();
151 
152        // Compute preferred size of thumbPanel depending on thumbnails
153        int preferredWidth = totalWidth + 2 * INSET_X;
154        int preferredHeight = Math.max(totalHeight,
155                PREFERRED_THUMBS_IN_VIEW * maxThumbHeight
156                + (PREFERRED_THUMBS_IN_VIEW + 1) * INSET_Y);
157 
158        if (logger.isInfoEnabled()) {
159            logger.info("preferredSize=[" + preferredWidth + ", " + preferredHeight + "]");
160        }
161        Dimension newPreferredSize = new Dimension(preferredWidth, preferredHeight);
162 
163        thumbPanel.setPreferredSize(newPreferredSize);
164        scrollBar.setValue(0);
165        repaint();
166    }
167 
168    public void setLeadSelectionIndex(int newLeadSelectionIndex) {
169        selectionModel.setLeadSelectionIndex(newLeadSelectionIndex);
170    }
171 
172    public void setSelectionColor(Color newSelectionColor) {
173        selectionColor = newSelectionColor;
174    }
175 
176    public void setSelectionInterval(int arg0, int arg1) {
177        selectionModel.setSelectionInterval(arg0, arg1);
178    }
179 
180    public void setSelectionMode(int newMode) {
181        selectionModel.setSelectionMode(newMode);
182    }
183 
184    public void setValueIsAdjusting(boolean adjusting) {
185        selectionModel.setValueIsAdjusting(adjusting);
186    }
187 
188    public int getAnchorSelectionIndex() {
189        return selectionModel.getAnchorSelectionIndex();
190    }
191 
192    public int getLeadSelectionIndex() {
193        return selectionModel.getLeadSelectionIndex();
194    }
195 
196    public int getMaxSelectionIndex() {
197        return selectionModel.getMaxSelectionIndex();
198    }
199 
200    public int getMinSelectionIndex() {
201        return selectionModel.getMaxSelectionIndex();
202    }
203 
204    public Color getSelectionColor() {
205        return selectionColor;
206    }
207 
208    public int getSelectionMode() {
209        return selectionModel.getSelectionMode();
210    }
211 
212    public boolean getValueIsAdjusting() {
213        return selectionModel.getValueIsAdjusting();
214    }
215 
216    public boolean isSelectedIndex(int arg0) {
217        return selectionModel.isSelectedIndex(arg0);
218    }
219 
220    public boolean isSelectionEmpty() {
221        return selectionModel.isSelectionEmpty();
222    }
223 
224    /**
225     *  Get image index of thumbnail starting at or after y.
226     */
227    int getThumbIndexAtOrAfterY(int y) {
228        assert comic != null;
229        assert y >= 0;
230        int result = Arrays.binarySearch(thumbYs, y);
231 
232        if (result < 0) {
233            result = -2 - result;
234        }
235        int lastThumbIndex = comic.getImageCount();
236 
237        if (result < 0) {
238            result = 0;
239        } else if (result == lastThumbIndex) {
240            result = lastThumbIndex - 1;
241        }
242        assert result >= 0;
243        assert result <= lastThumbIndex;
244        int resultTopY = thumbYs[result];
245 
246        // TODO: fix and enable assertion
247        //assert resultTopY >= y : "thumbYs[" + result + "]=" + resultTopY + " must be at least " + y;
248        return result;
249    }
250 
251    public void addListSelectionListener(ListSelectionListener listener) {
252        assert listener != null;
253        selectionModel.addListSelectionListener(listener);
254    }
255 
256    public void addSelectionInterval(int arg0, int arg1) {
257        selectionModel.addSelectionInterval(arg0, arg1);
258    }
259 
260    public void clearSelection() {
261        selectionModel.clearSelection();
262    }
263 
264    public void componentHidden(ComponentEvent event) {
265        // Do nothing.
266    }
267 
268    public void componentMoved(ComponentEvent event) {
269        // Do nothing.
270    }
271 
272    public void componentResized(ComponentEvent event) {
273        updateScrollbarModel();
274    }
275 
276    public void componentShown(ComponentEvent event) {
277        // Do nothing.
278    }
279 
280    public void dispose() {
281        removeMouseWheelListener(this);
282        removeMouseListener(this);
283        if (scrollBar != null) {
284            scrollBar.getModel().removeChangeListener(this);
285        }
286    }
287 
288    public void insertIndexInterval(int arg0, int arg1, boolean arg2) {
289        selectionModel.insertIndexInterval(arg0, arg1, arg2);
290    }
291 
292    public void mouseClicked(MouseEvent event) {
293        // Do nothing.
294    }
295 
296    public void mouseEntered(MouseEvent event) {
297        // Do nothing.
298    }
299 
300    public void mouseExited(MouseEvent event) {
301        // Do nothing.
302    }
303 
304    public void mousePressed(MouseEvent event) {
305        // Do nothing.
306    }
307 
308    public void mouseReleased(MouseEvent event) {
309        if (comic != null) {
310            int mouseY = event.getY();
311 
312            synchronized (comicLock) {
313                int selectionY = mouseY + scrollBar.getValue();
314                int selectedThumbIndex = getThumbIndexAtOrAfterY(selectionY);
315 
316                if (logger.isInfoEnabled()) {
317                    logger.info("thumbnail selected: imageIndex=" + selectedThumbIndex);
318                }
319                // TODO: support more than 1 selected item and add: selectionModel.clearSelection();
320                selectionModel.setSelectionInterval(selectedThumbIndex, selectedThumbIndex);
321            }
322            repaint();
323        }
324    }
325 
326    public void mouseWheelMoved(MouseWheelEvent event) {
327        if (event.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
328            int scrollSpeed = Math.max(1, comicCache.getThumbHeight() / 20);
329            int totalScrollAmount = scrollSpeed * event.getUnitsToScroll() * scrollBar.getUnitIncrement();
330 
331            scrollBar.setValue(scrollBar.getValue() + totalScrollAmount);
332        }
333    }
334 
335    public void removeIndexInterval(int arg0, int arg1) {
336        selectionModel.removeIndexInterval(arg0, arg1);
337    }
338 
339    public void removeListSelectionListener(ListSelectionListener listener) {
340        assert listener != null;
341        selectionModel.removeListSelectionListener(listener);
342    }
343 
344    public void removeSelectionInterval(int arg0, int arg1) {
345        selectionModel.removeSelectionInterval(arg0, arg1);
346    }
347 
348    public void stateChanged(ChangeEvent event) {
349        assert event != null;
350        Object eventSource = event.getSource();
351 
352        assert eventSource == scrollBar.getModel() : "event.source=" + eventSource;
353        repaint();
354    }
355 
356    private void updateScrollbarModel() {
357        int extent = thumbPanel.getSize().height;
358 
359        scrollBar.setMinimum(0);
360        scrollBar.setMaximum(totalHeight);
361        scrollBar.setVisibleAmount(extent);
362        scrollBar.setBlockIncrement(extent);
363    }
364 
365    /**
366     *  JComponent to draw thumbnails for comic images.
367     *
368     * @author    Thomas Aglassinger
369     */
370    private class ThumbPanel extends JComponent implements ImageInCacheListener
371    {
372        public ThumbPanel() {
373            super();
374        }
375 
376        public void imageCached(File imageFile) {
377            // TODO: repaint only if new image is visible
378            repaint();
379        }
380 
381        protected void paintComponent(Graphics g) {
382            if (isOpaque()) {
383                g.setColor(getBackground());
384                g.fillRect(0, 0, getWidth(), getHeight());
385            }
386 
387            if (comic != null) {
388                Graphics2D g2d = (Graphics2D) g.create();
389 
390                try {
391 
392                    g2d.setRenderingHints(imageTools.getRenderingHints());
393                    synchronized (comicLock) {
394                        int topVisualY = scrollBar.getValue();
395                        int bottomVisualY = topVisualY + getSize().height;
396                        int topThumbIndex = Arrays.binarySearch(thumbYs, topVisualY);
397 
398                        if (topThumbIndex < 0) {
399                            // Note: "-2" to ensure to also draw the partially visible previous image.
400                            topThumbIndex = Math.max(0, -topThumbIndex - 2);
401                        } else {
402                            // Ensure that we do not attempt to draw the last+1 image when at bottom
403                            topThumbIndex = Math.min(topThumbIndex, comic.getImageCount() - 1);
404                        }
405                        assert topThumbIndex >= 0 : "topThumbIndex=" + topThumbIndex;
406                        int topThumbY = thumbYs[topThumbIndex];
407 
408                        if (topThumbIndex > 0) {
409                            assert topVisualY >= topThumbY
410                                    : "topVisualY=" + topVisualY + " must be at least topThumbY=" + topThumbY;
411                        }
412                        if (logger.isInfoEnabled()) {
413                            logger.info("paint: visualY=[" + topVisualY
414                                    + ".." + bottomVisualY
415                                    + "], topThumbY=" + topThumbY
416                                    + "@" + topThumbIndex
417                                    + "/" + comic.getImageCount());
418                        }
419                        AffineTransform oldTransform = g2d.getTransform();
420 
421                        try {
422                            int thumbIndex = topThumbIndex;
423                            int y = topThumbY;
424 
425                            g2d.translate(0, -topVisualY);
426                            while ((y <= bottomVisualY) && (thumbIndex < comic.getImageCount())) {
427                                int thumbWidth = thumbSizes[thumbIndex].width;
428                                int thumbHeight = thumbSizes[thumbIndex].height;
429                                int centerX = (totalWidth - thumbWidth) / 2;
430 
431                                if (selectionModel.isSelectedIndex(thumbIndex)) {
432                                    g2d.setColor(getSelectionColor());
433 
434                                    Rectangle2D.Double selectionRectangle = new Rectangle2D.Double(
435                                            0, y, 2 * INSET_X + totalWidth, 2 * INSET_Y + thumbHeight);
436 
437                                    g2d.fill(selectionRectangle);
438                                }
439 
440                                File originalImageFile = comic.getComicImage(thumbIndex).getFile();
441                                RenderedImage image = comicCache.getThumbnail(originalImageFile, this);
442                                AffineTransform imageTransform = AffineTransform.getTranslateInstance(
443                                        INSET_X + centerX, INSET_Y + y);
444 
445                                g2d.drawRenderedImage(image, imageTransform);
446                                thumbIndex += 1;
447                                if (thumbIndex < comic.getImageCount()) {
448                                    y = thumbYs[thumbIndex];
449                                }
450                            }
451                        } finally {
452                            g2d.setTransform(oldTransform);
453                        }
454                    }
455                } finally {
456                    g2d.dispose();
457                }
458            }
459        }
460    }
461}

[all classes][net.sf.jomic.comic]
EMMA 2.0.4217 (C) Vladimir Roubtsov