ComicImageView now uses an array of ImlibImageList instead of an array of ImlibImage...
[qcomicbook-oblomov] / src / imgview.cpp
1 /*
2  * This file is a part of QComicBook.
3  *
4  * Copyright (C) 2005-2006 Pawel Stolowski <yogin@linux.bydg.org>
5  *
6  * QComicBook is free software; you can redestribute it and/or modify it
7  * under terms of GNU General Public License by Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY. See GPL for more details.
11  */
12
13 #include "imgview.h"
14 #include "miscutil.h"
15 #include "imlibimage.h"
16 #include <qpopupmenu.h>
17 #include <qpainter.h>
18 #include <qbitmap.h>
19 #include <qcursor.h>
20 #include <algorithm>
21 #include <cmath>
22
23 using namespace QComicBook;
24
25 const int ComicImageView::EXTRA_WHEEL_SPIN = 2;
26
27 ComicImageView::ComicImageView(QWidget *parent, Size size, const QColor &color): QScrollView(parent), isize(size), iangle(0), xoff(0), yoff(0), lx(-1), wheelupcnt(0), wheeldowncnt(0), smallcursor(NULL)
28 {
29         context_menu = new QPopupMenu(this);
30         viewport()->setPaletteBackgroundColor(color);
31         setFocusPolicy(QWidget::StrongFocus);
32 }
33
34 ComicImageView::~ComicImageView()
35 {
36         orgimage[0].clear();
37         orgimage[1].clear();
38 }
39
40 void ComicImageView::drawContents(QPainter *p, int clipx, int clipy, int clipw, int cliph)
41 {
42         if (orgimage[0].isEmpty())
43                 return;
44
45         ImlibImage *image1, *image2;
46
47
48         if (iangle > 1 && !orgimage[1].isEmpty()) //switch image pointers to reflect rotation angle
49         {
50                 image1 = orgimage[1].current();
51                 image2 = orgimage[0].current();
52         }
53         else
54         {
55                 image1 = orgimage[0].current();
56                 image2 = orgimage[1].current();
57         }
58
59         int px = clipx - xoff;
60         int py = clipy - yoff;
61               
62         if (py < 0 ) //clear strip on the top
63         {
64                 p->eraseRect(clipx, clipy, clipw, -py);
65                 py = 0;
66         }
67         if (px < 0) //clear strip on the left
68         {
69                 p->eraseRect(clipx, yoff, -px, cliph);
70                 px = 0;
71         }
72        
73         if (clipx + clipw < xoff || clipy + cliph < yoff)
74                  return;
75
76         const double img1w = static_cast<double>(image1->width());
77         const double img1h = static_cast<double>(image1->height());
78
79         double sx = w_asp * px;
80         double sy = h_asp * py;
81
82         double sw = ceil(w_asp*clipw); //round up, so it's never 0
83         double sh = ceil(h_asp*cliph);
84
85         double dx = std::max(xoff, clipx) - contentsX();
86         double dy = std::max(yoff, clipy) - contentsY();
87
88         image1->draw(p->device(), static_cast<int>(sx), static_cast<int>(sy), static_cast<int>(sw), static_cast<int>(sh), static_cast<int>(dx), static_cast<int>(dy), clipw, cliph);
89
90         //
91         // number of painted pixels
92         double painted_w = std::min(static_cast<double>(clipw), (img1w - sx)/w_asp);
93         double painted_h = std::min(static_cast<double>(cliph), (img1h - sy)/h_asp);
94
95         if (painted_w < 0)
96                 painted_w = 0;
97         if (painted_h < 0)
98                 painted_h = 0;
99
100         if (image2)
101         {
102                 bool draw = false;
103                 if ((painted_w < clipw) && (iangle & 1) == 0) //angle is 0 or 180 - left-right orientation
104                 {
105                         dx += painted_w;
106                         if (sx > img1w) //1st image was not drawn, part of 2nd image need to be painted only
107                         {
108                                 sx = (static_cast<double>(clipx - xoff) - (img1w/w_asp))*w_asp;;
109                                 dx = std::max(static_cast<double>(xoff), static_cast<double>(clipx) - painted_w) - contentsX();
110                         }
111                         else //whole 2nd image to be painted
112                         {
113                                 sx = 0.0f;
114                         }
115                         draw = true;
116                 }
117                 else if ((painted_h <  cliph) && (iangle & 1)) //angle is 90 or 270 - top-bottom orientation
118                 {
119                         dy += painted_h;
120                         if (sy > img1h) //1st image was not drawn, part of 2nd image need to be painted only
121                         {
122                                 sy = (static_cast<double>(clipy - yoff) - (img1h/h_asp))*h_asp;;
123                                 dy = std::max(static_cast<double>(yoff), static_cast<double>(clipy) - painted_h) - contentsY();
124                         }
125                         else //whole 2nd image to be painted
126                         {
127                                 sy = 0.0f;
128                         }
129                         draw = true;
130                 }
131                 if (draw)
132                         image2->draw(p->device(), static_cast<int>(sx), static_cast<int>(sy), static_cast<int>(sw), static_cast<int>(sh), static_cast<int>(dx), static_cast<int>(dy), clipw, cliph);
133         }
134 }
135
136 QPopupMenu *ComicImageView::contextMenu() const
137 {
138         return context_menu;
139 }
140
141 bool ComicImageView::onTop()
142 {
143         return contentsY() == 0;
144 }
145
146 bool ComicImageView::onBottom()
147 {
148         return contentsY() + viewport()->height() >= contentsHeight();
149 }
150
151 void ComicImageView::contentsContextMenuEvent(QContextMenuEvent *e)
152 {
153         if (!orgimage[0].isEmpty())
154                 context_menu->popup(e->globalPos());
155 }
156
157 void ComicImageView::setImage(ImlibImage *img, bool preserveangle)
158 {
159         if (!preserveangle)
160                 iangle = 0;
161
162         orgimage[0].clear();
163         orgimage[1].clear();
164         orgimage[0].append(img);
165
166         if (iangle != 0 && !orgimage[0].isEmpty())
167                 orgimage[0].current()->rotate(iangle);
168
169         updateImageSize();
170         ensureVisible(1, 1);
171
172         repaintContents(0, 0 , viewport()->width(), viewport()->height());
173 }
174
175 void ComicImageView::setImage(ImlibImage *img1, ImlibImage *img2, bool preserveangle)
176 {
177         if (!preserveangle)
178                 iangle = 0;
179
180         orgimage[0].clear();
181         orgimage[1].clear();
182
183         orgimage[0].append(img1);
184         orgimage[1].append(img2);
185
186         if (iangle != 0)
187         {
188                 if (!orgimage[0].isEmpty())
189                         orgimage[0].current()->rotate(iangle);
190                 if (!orgimage[1].isEmpty())
191                         orgimage[1].current()->rotate(iangle);
192         }
193
194         updateImageSize();
195         ensureVisible(1, 1);
196
197         repaintContents(0, 0 , viewport()->width(), viewport()->height());
198 }
199
200 void ComicImageView::resizeEvent(QResizeEvent *e)
201 {
202         QScrollView::resizeEvent(e);
203         updateImageSize();
204 }
205
206 void ComicImageView::contentsWheelEvent(QWheelEvent *e)
207 {
208         e->accept();
209         if (e->delta() > 0) //scrolling up
210         {
211                 if (contentsHeight()<=viewport()->height() || (onTop() && ++wheelupcnt > EXTRA_WHEEL_SPIN))
212                 {
213                         emit topReached();
214                         wheelupcnt = 0;
215                 }
216                 else
217                 {
218                         scrollBy(0, -3*spdy);
219                         wheeldowncnt = 0; //reset opposite direction counter
220                 }
221         }
222         else //scrolling down
223         {
224                 if (contentsHeight()<=viewport()->height() || (onBottom() && ++wheeldowncnt > EXTRA_WHEEL_SPIN))
225                 {
226                         emit bottomReached();
227                         wheeldowncnt = 0;
228                 }
229                 else
230                 {
231                         scrollBy(0, 3*spdy);
232                         wheelupcnt = 0; //reset opposite direction counter
233                 }
234         }
235 }
236
237 void ComicImageView::contentsMouseMoveEvent(QMouseEvent *e)
238 {
239         if (lx >= 0)
240         {
241                 const int dx = lx - e->x();
242                 const int dy = ly - e->y();
243                 scrollBy(dx, dy);
244                 lx = e->x() + dx; //need to add delta because e->x() is the old position
245                 ly = e->y() + dy;
246         }
247         else 
248         {
249                 lx = e->x();
250                 ly = e->y();
251         }
252 }
253
254 void ComicImageView::contentsMousePressEvent(QMouseEvent *e)
255 {
256         if (!smallcursor)
257                 setCursor(Qt::PointingHandCursor);
258 }
259
260 void ComicImageView::contentsMouseReleaseEvent(QMouseEvent *e)
261 {
262         lx = -1;
263         if (!smallcursor)
264                 setCursor(Qt::ArrowCursor);
265 }
266
267
268 void ComicImageView::contentsMouseDoubleClickEvent(QMouseEvent *e)
269 {
270         emit doubleClick();
271 }
272
273 void ComicImageView::updateImageSize()
274 {
275         if (orgimage[0].isEmpty())
276                 return;
277         if (orgimage[0].current()->width() * orgimage[0].current()->height() == 0)
278                 return;
279
280         Size size = isize;
281
282         //
283         // calculate iw, ih - the size of total image(s) area without scaling;
284         // roatation angle of 2nd image is taken into account
285         double iw = orgimage[0].current()->width();
286         double ih = orgimage[0].current()->height();
287
288         if (!orgimage[1].isEmpty())
289         {
290                 if (iangle & 1)
291                 {
292                         ih += orgimage[1].current()->height();
293                         if (orgimage[1].current()->width() > iw)
294                                 iw = orgimage[1].current()->width();
295                 }
296                 else
297                 {
298                         iw += orgimage[1].current()->width();
299                         if (orgimage[1].current()->height() > ih)
300                                 ih = orgimage[1].current()->height();
301                 }
302         }
303         
304         if (size == BestFit)
305         {
306                 if (iw > ih)
307                 {
308                         if (iangle&1)
309                                 size = FitWidth;
310                         else
311                                 size = FitHeight;
312                 }
313                 else
314                 {
315                         if (iangle&1)
316                                 size = FitHeight;
317                         else
318                                 size = FitWidth;
319                 }
320         }
321
322         int dw, dh;
323                 
324         w_asp = h_asp = 1.0f;
325         if (size == Original)
326         {
327                 dw = static_cast<int>(iw);
328                 dh = static_cast<int>(ih);
329         }
330         else
331         {       
332                 asp = (double)iw / ih;
333
334                 if (size == FitWidth)
335                 {
336                         w_asp = iw / viewport()->width();
337                         h_asp = w_asp * asp;
338                         h_asp = w_asp;
339                 }
340                 else if (size == FitHeight)
341                 {
342                         h_asp = ih / viewport()->height();
343                         w_asp = h_asp;
344                 }
345                 else if (size == WholePage)
346                 {
347                         w_asp = iw / viewport()->width();
348                         h_asp = ih / viewport()->height();
349                         if (w_asp > h_asp)
350                                 h_asp = w_asp;
351                         else
352                                 w_asp = h_asp;
353                 }
354
355                 dw = static_cast<int>(iw / w_asp);
356                 dh = static_cast<int>(ih / h_asp);
357         }
358
359         int d;
360         xoff = yoff = 0;
361
362         //
363         // calculate offsets for image centering
364         if ((d = viewport()->width() - dw) > 0)
365                 xoff = d/2;
366         if ((d = viewport()->height() - dh) > 0)
367                 yoff = d/2;
368           
369         resizeContents(dw + xoff, dh + yoff);
370                 
371         //
372         // update scrolling speeds
373         spdx = dw / 100;
374         spdy = dh / 100;
375 }
376
377 /*void ComicImageView::setScaling(Scaling s)
378 {
379         iscaling = s;
380 }*/
381
382 void ComicImageView::setRotation(Rotation r)
383 {
384         if (r == QComicBook::Right)
385                 ++iangle;
386         else if (r == QComicBook::Left)
387                 --iangle;
388         iangle &= 3;
389
390         for (int i=0; i<2; i++)
391                 if (!orgimage[i].isEmpty())
392                 {
393                         if (r == QComicBook::Right)
394                                 orgimage[i].current()->rotate(1);
395                         else if (r == QComicBook::Left)
396                                 orgimage[i].current()->rotate(3);
397                         else if (r == QComicBook::None && iangle != 0)
398                         {
399                                 orgimage[i].current()->rotate(4-iangle);
400                                 iangle = 0;
401                         }
402                 }
403
404         updateImageSize();
405         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height(), true);
406 }
407
408 void ComicImageView::setSize(Size s)
409 {
410         isize = s;
411         updateImageSize();
412         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height(), true);
413 }
414
415 void ComicImageView::setSizeOriginal()
416 {
417         setSize(Original);
418 }
419
420 void ComicImageView::setSizeFitWidth()
421 {
422         setSize(FitWidth);
423 }
424
425 void ComicImageView::setSizeFitHeight()
426 {
427         setSize(FitHeight);
428 }
429
430 void ComicImageView::setSizeWholePage()
431 {
432         setSize(WholePage);
433 }
434
435 void ComicImageView::setSizeBestFit()
436 {
437         setSize(BestFit);
438 }
439
440 void ComicImageView::scrollToTop()
441 {
442         ensureVisible(1, 1);
443 }
444
445 void ComicImageView::scrollToBottom()
446 {
447         ensureVisible(1, contentsHeight());
448 }
449
450 void ComicImageView::scrollRight()
451 {
452         scrollBy(spdx, 0);
453 }
454
455 void ComicImageView::scrollLeft()
456 {
457         scrollBy(-spdx, 0);
458 }
459
460 void ComicImageView::scrollRightFast()
461 {
462         scrollBy(3*spdx, 0);
463 }
464
465 void ComicImageView::scrollLeftFast()
466 {
467         scrollBy(-3*spdx, 0);
468 }
469
470 void ComicImageView::scrollUp()
471 {
472         if (onTop())
473         {
474                 wheelupcnt = wheeldowncnt = 0;
475                 emit topReached();
476         }
477         else
478                 scrollBy(0, -spdy);
479 }
480
481 void ComicImageView::scrollDown()
482 {
483         if (onBottom())
484         {
485                 wheelupcnt = wheeldowncnt = 0;
486                 emit bottomReached();
487         }
488         else
489                 scrollBy(0, spdy);
490 }
491
492 void ComicImageView::scrollUpFast()
493 {
494         if (onTop())
495                 emit topReached();
496         else
497                 scrollBy(0, -3*spdy);
498 }
499
500 void ComicImageView::scrollDownFast()
501 {       
502         if (onBottom())
503                 emit bottomReached();
504         else
505                 scrollBy(0, 3*spdy);
506 }
507
508 void ComicImageView::rotateRight()
509 {
510         setRotation(QComicBook::Right);
511 }
512
513 void ComicImageView::rotateLeft()
514 {
515         setRotation(QComicBook::Left);
516 }
517
518 void ComicImageView::resetRotation()
519 {
520         setRotation(None);
521 }
522
523 void ComicImageView::jumpUp()
524 {
525         if (onTop())
526                 emit topReached();
527         else
528                 scrollBy(0, -viewport()->height());
529 }
530
531 void ComicImageView::jumpDown()
532 {
533         if (onBottom())
534                 emit bottomReached();
535         else
536                 scrollBy(0, viewport()->height());
537 }
538
539 void ComicImageView::enableScrollbars(bool f)
540 {
541         ScrollBarMode s = f ? Auto : AlwaysOff;
542         setVScrollBarMode(s);
543         setHScrollBarMode(s);
544 }
545
546 void ComicImageView::setBackground(const QColor &color)
547 {
548         viewport()->setPaletteBackgroundColor(color);
549 }
550
551 void ComicImageView::setSmallCursor(bool f)
552 {
553         if (f)
554         {
555                 static unsigned char bmp_bits[4*32];
556                 static unsigned char msk_bits[4*32];
557
558                 if (smallcursor)
559                         return;
560
561                 for (int i=0; i<4*32; i++)
562                         bmp_bits[i] = msk_bits[i] = 0;
563                 bmp_bits[0] = msk_bits[0] = 0xe0;
564                 bmp_bits[4] = 0xa0;
565                 msk_bits[4] = 0xe0;
566                 bmp_bits[8] = msk_bits[8] = 0xe0;
567                 const QBitmap bmp(32, 32, bmp_bits, false);
568                 const QBitmap msk(32, 32, msk_bits, false);
569                 smallcursor = new QCursor(bmp, msk, 0, 0);
570                 setCursor(*smallcursor);
571         }
572         else
573         {
574                 if (smallcursor)
575                         delete smallcursor;
576                 smallcursor = NULL;
577                 setCursor(Qt::ArrowCursor);
578         }
579 }
580
581 void ComicImageView::clear()
582 {
583         orgimage[0].clear();
584         orgimage[1].clear();
585         resizeContents(0, 0);
586 }
587
588 Size ComicImageView::getSize() const
589 {
590         return isize;
591 }
592
593 int ComicImageView::imageWidth() const
594 {
595         if (!orgimage[0].isEmpty())
596                 return orgimage[0].current()->width();
597         return 0;
598 }
599