EMMA Coverage Report (generated Sun Apr 20 22:38:01 CEST 2008)
[all classes][net.sf.jomic.comic]

COVERAGE SUMMARY FOR SOURCE FILE [ComicThumbView.java]

nameclass, %method, %block, %line, %
ComicThumbView.java100% (2/2)33%  (14/43)65%  (654/1000)65%  (131.1/203)

COVERAGE BREAKDOWN BY CLASS AND METHOD

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

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