updateImageSize() now takes the whole ImlibImageList into consideration
[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         orgimage[0].setAutoDelete(true);
33         orgimage[1].setAutoDelete(true);
34 }
35
36 ComicImageView::~ComicImageView()
37 {
38         orgimage[0].clear();
39         orgimage[1].clear();
40 }
41
42 void ComicImageView::drawContents(QPainter *p, int clipx, int clipy, int clipw, int cliph)
43 {
44         if (orgimage[0].isEmpty())
45                 return;
46
47         ImlibImage *image1, *image2;
48
49
50         if (iangle > 1 && !orgimage[1].isEmpty()) //switch image pointers to reflect rotation angle
51         {
52                 image1 = orgimage[1].getFirst();
53                 image2 = orgimage[0].getFirst();
54         }
55         else
56         {
57                 image1 = orgimage[0].getFirst();
58                 image2 = orgimage[1].getFirst();
59         }
60
61         int px = clipx - xoff;
62         int py = clipy - yoff;
63               
64         if (py < 0 ) //clear strip on the top
65         {
66                 p->eraseRect(clipx, clipy, clipw, -py);
67                 py = 0;
68         }
69         if (px < 0) //clear strip on the left
70         {
71                 p->eraseRect(clipx, yoff, -px, cliph);
72                 px = 0;
73         }
74        
75         if (clipx + clipw < xoff || clipy + cliph < yoff)
76                  return;
77
78         const double img1w = static_cast<double>(image1->width());
79         const double img1h = static_cast<double>(image1->height());
80
81         double sx = w_asp * px;
82         double sy = h_asp * py;
83
84         double sw = ceil(w_asp*clipw); //round up, so it's never 0
85         double sh = ceil(h_asp*cliph);
86
87         double dx = std::max(xoff, clipx) - contentsX();
88         double dy = std::max(yoff, clipy) - contentsY();
89
90         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);
91
92         //
93         // number of painted pixels
94         double painted_w = std::min(static_cast<double>(clipw), (img1w - sx)/w_asp);
95         double painted_h = std::min(static_cast<double>(cliph), (img1h - sy)/h_asp);
96
97         if (painted_w < 0)
98                 painted_w = 0;
99         if (painted_h < 0)
100                 painted_h = 0;
101
102         if (image2)
103         {
104                 bool draw = false;
105                 if ((painted_w < clipw) && (iangle & 1) == 0) //angle is 0 or 180 - left-right orientation
106                 {
107                         dx += painted_w;
108                         if (sx > img1w) //1st image was not drawn, part of 2nd image need to be painted only
109                         {
110                                 sx = (static_cast<double>(clipx - xoff) - (img1w/w_asp))*w_asp;;
111                                 dx = std::max(static_cast<double>(xoff), static_cast<double>(clipx) - painted_w) - contentsX();
112                         }
113                         else //whole 2nd image to be painted
114                         {
115                                 sx = 0.0f;
116                         }
117                         draw = true;
118                 }
119                 else if ((painted_h <  cliph) && (iangle & 1)) //angle is 90 or 270 - top-bottom orientation
120                 {
121                         dy += painted_h;
122                         if (sy > img1h) //1st image was not drawn, part of 2nd image need to be painted only
123                         {
124                                 sy = (static_cast<double>(clipy - yoff) - (img1h/h_asp))*h_asp;;
125                                 dy = std::max(static_cast<double>(yoff), static_cast<double>(clipy) - painted_h) - contentsY();
126                         }
127                         else //whole 2nd image to be painted
128                         {
129                                 sy = 0.0f;
130                         }
131                         draw = true;
132                 }
133                 if (draw)
134                         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);
135         }
136 }
137
138 QPopupMenu *ComicImageView::contextMenu() const
139 {
140         return context_menu;
141 }
142
143 bool ComicImageView::onTop()
144 {
145         return contentsY() == 0;
146 }
147
148 bool ComicImageView::onBottom()
149 {
150         return contentsY() + viewport()->height() >= contentsHeight();
151 }
152
153 void ComicImageView::contentsContextMenuEvent(QContextMenuEvent *e)
154 {
155         if (!orgimage[0].isEmpty())
156                 context_menu->popup(e->globalPos());
157 }
158
159 void ComicImageView::setImage(ImlibImage *img, bool preserveangle)
160 {
161         if (!preserveangle)
162                 iangle = 0;
163
164         orgimage[0].clear();
165         orgimage[1].clear();
166         orgimage[0].append(img);
167
168         if (iangle != 0 && !orgimage[0].isEmpty())
169                 orgimage[0].getFirst()->rotate(iangle);
170
171         updateImageSize();
172         ensureVisible(1, 1);
173
174         repaintContents(0, 0 , viewport()->width(), viewport()->height());
175 }
176
177 void ComicImageView::setImage(ImlibImageList &imglist, bool preserveangle)
178 {
179         if (!preserveangle)
180                 iangle = 0;
181
182         orgimage[0].clear();
183         orgimage[1].clear();
184
185         orgimage[0] = imglist;
186
187         if (iangle != 0 && !orgimage[0].isEmpty())
188                 orgimage[0].getFirst()->rotate(iangle);
189
190         updateImageSize();
191         ensureVisible(1, 1);
192
193         repaintContents(0, 0 , viewport()->width(), viewport()->height());
194 }
195
196 void ComicImageView::setImage(ImlibImage *img1, ImlibImage *img2, bool preserveangle)
197 {
198         if (!preserveangle)
199                 iangle = 0;
200
201         orgimage[0].clear();
202         orgimage[1].clear();
203
204         orgimage[0].append(img1);
205         orgimage[1].append(img2);
206
207         if (iangle != 0)
208         {
209                 if (!orgimage[0].isEmpty())
210                         orgimage[0].getFirst()->rotate(iangle);
211                 if (!orgimage[1].isEmpty())
212                         orgimage[1].getFirst()->rotate(iangle);
213         }
214
215         updateImageSize();
216         ensureVisible(1, 1);
217
218         repaintContents(0, 0 , viewport()->width(), viewport()->height());
219 }
220
221 void ComicImageView::setImage(ImlibImageList &imglist1, ImlibImageList &imglist2, bool preserveangle)
222 {
223         if (!preserveangle)
224                 iangle = 0;
225
226         orgimage[0].clear();
227         orgimage[1].clear();
228
229         orgimage[0] = imglist1;
230         orgimage[1] = imglist2;
231
232         if (iangle != 0)
233         {
234                 if (!orgimage[0].isEmpty())
235                         orgimage[0].getFirst()->rotate(iangle);
236                 if (!orgimage[1].isEmpty())
237                         orgimage[1].getFirst()->rotate(iangle);
238         }
239
240         updateImageSize();
241         ensureVisible(1, 1);
242
243         repaintContents(0, 0 , viewport()->width(), viewport()->height());
244 }
245
246 void ComicImageView::resizeEvent(QResizeEvent *e)
247 {
248         QScrollView::resizeEvent(e);
249         updateImageSize();
250 }
251
252 void ComicImageView::contentsWheelEvent(QWheelEvent *e)
253 {
254         e->accept();
255         if (e->delta() > 0) //scrolling up
256         {
257                 if (contentsHeight()<=viewport()->height() || (onTop() && ++wheelupcnt > EXTRA_WHEEL_SPIN))
258                 {
259                         emit topReached();
260                         wheelupcnt = 0;
261                 }
262                 else
263                 {
264                         scrollBy(0, -3*spdy);
265                         wheeldowncnt = 0; //reset opposite direction counter
266                 }
267         }
268         else //scrolling down
269         {
270                 if (contentsHeight()<=viewport()->height() || (onBottom() && ++wheeldowncnt > EXTRA_WHEEL_SPIN))
271                 {
272                         emit bottomReached();
273                         wheeldowncnt = 0;
274                 }
275                 else
276                 {
277                         scrollBy(0, 3*spdy);
278                         wheelupcnt = 0; //reset opposite direction counter
279                 }
280         }
281 }
282
283 void ComicImageView::contentsMouseMoveEvent(QMouseEvent *e)
284 {
285         if (lx >= 0)
286         {
287                 const int dx = lx - e->x();
288                 const int dy = ly - e->y();
289                 scrollBy(dx, dy);
290                 lx = e->x() + dx; //need to add delta because e->x() is the old position
291                 ly = e->y() + dy;
292         }
293         else 
294         {
295                 lx = e->x();
296                 ly = e->y();
297         }
298 }
299
300 void ComicImageView::contentsMousePressEvent(QMouseEvent *e)
301 {
302         if (!smallcursor)
303                 setCursor(Qt::PointingHandCursor);
304 }
305
306 void ComicImageView::contentsMouseReleaseEvent(QMouseEvent *e)
307 {
308         lx = -1;
309         if (!smallcursor)
310                 setCursor(Qt::ArrowCursor);
311 }
312
313
314 void ComicImageView::contentsMouseDoubleClickEvent(QMouseEvent *e)
315 {
316         emit doubleClick();
317 }
318
319 void ComicImageView::updateImageSize()
320 {
321         if (orgimage[0].isEmpty())
322                 return;
323
324         Size size = isize;
325         ImlibImage *img;
326
327         // calculate iw, ih - the size of total image(s) area without scaling;
328         // roatation angle of 2nd image is taken into account
329         double lw(0), lh(0);
330         double rw(0), rh(0);
331         double iw(0), ih(0);
332
333         // We calculate the width and height of the left column of images:
334         // the width is the maximum width of the components.
335         for (img = orgimage[0].first(); img != NULL; img = orgimage[0].next())
336         {
337                 if (img->width() > lw)
338                         lw = img->width();
339         }
340         if (lw == 0)
341                 return;
342
343         // The height is the sum of all the heights when the items have been
344         // scaled to have all the same (maximum) width
345         for (img = orgimage[0].first(); img != NULL; img = orgimage[0].next())
346         {
347                 if (img->width() > 0)
348                         lh += img->height()*lw/img->width();
349         }
350
351         // Total area is initially the same as the left area
352         iw = lw;
353         ih = lh;
354
355         if (!orgimage[1].isEmpty())
356         {
357                 // If we have a second column of images too, first we
358                 // calculate its width and height, as we did for the left side
359                 for (img = orgimage[1].first(); img != NULL; img = orgimage[1].next())
360                 {
361                         if (img->width() > rw)
362                                 rw = img->width();
363                 }
364                 if (rw != 0)
365                 {
366                         for (img = orgimage[1].first(); img != NULL; img = orgimage[1].next())
367                         {
368                                 if (img->width() > 0)
369                                         rh += img->height()*rw/img->width();
370                         }
371
372
373                         if (iangle & 1)
374                         {
375                                 ih += rh;
376                                 if (rw > iw)
377                                         iw = rw;
378                         }
379                         else
380                         {
381                                 iw += rw;
382                                 if (rh > ih)
383                                         ih = rh;
384                         }
385                 }
386         }
387
388         if (size == BestFit)
389         {
390                 if (iw > ih)
391                 {
392                         if (iangle&1)
393                                 size = FitWidth;
394                         else
395                                 size = FitHeight;
396                 }
397                 else
398                 {
399                         if (iangle&1)
400                                 size = FitHeight;
401                         else
402                                 size = FitWidth;
403                 }
404         }
405
406         int dw, dh;
407                 
408         w_asp = h_asp = 1.0f;
409         if (size == Original)
410         {
411                 dw = static_cast<int>(iw);
412                 dh = static_cast<int>(ih);
413         }
414         else
415         {       
416                 asp = (double)iw / ih;
417
418                 if (size == FitWidth)
419                 {
420                         w_asp = iw / viewport()->width();
421                         h_asp = w_asp * asp;
422                         h_asp = w_asp;
423                 }
424                 else if (size == FitHeight)
425                 {
426                         h_asp = ih / viewport()->height();
427                         w_asp = h_asp;
428                 }
429                 else if (size == WholePage)
430                 {
431                         w_asp = iw / viewport()->width();
432                         h_asp = ih / viewport()->height();
433                         if (w_asp > h_asp)
434                                 h_asp = w_asp;
435                         else
436                                 w_asp = h_asp;
437                 }
438
439                 dw = static_cast<int>(iw / w_asp);
440                 dh = static_cast<int>(ih / h_asp);
441         }
442
443         int d;
444         xoff = yoff = 0;
445
446         //
447         // calculate offsets for image centering
448         if ((d = viewport()->width() - dw) > 0)
449                 xoff = d/2;
450         if ((d = viewport()->height() - dh) > 0)
451                 yoff = d/2;
452           
453         resizeContents(dw + xoff, dh + yoff);
454                 
455         //
456         // update scrolling speeds
457         spdx = dw / 100;
458         spdy = dh / 100;
459 }
460
461 /*void ComicImageView::setScaling(Scaling s)
462 {
463         iscaling = s;
464 }*/
465
466 void ComicImageView::setRotation(Rotation r)
467 {
468         if (r == QComicBook::Right)
469                 ++iangle;
470         else if (r == QComicBook::Left)
471                 --iangle;
472         iangle &= 3;
473
474         for (int i=0; i<2; i++)
475                 if (!orgimage[i].isEmpty())
476                 {
477                         if (r == QComicBook::Right)
478                                 orgimage[i].getFirst()->rotate(1);
479                         else if (r == QComicBook::Left)
480                                 orgimage[i].getFirst()->rotate(3);
481                         else if (r == QComicBook::None && iangle != 0)
482                         {
483                                 orgimage[i].getFirst()->rotate(4-iangle);
484                                 iangle = 0;
485                         }
486                 }
487
488         updateImageSize();
489         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height(), true);
490 }
491
492 void ComicImageView::setSize(Size s)
493 {
494         isize = s;
495         updateImageSize();
496         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height(), true);
497 }
498
499 void ComicImageView::setSizeOriginal()
500 {
501         setSize(Original);
502 }
503
504 void ComicImageView::setSizeFitWidth()
505 {
506         setSize(FitWidth);
507 }
508
509 void ComicImageView::setSizeFitHeight()
510 {
511         setSize(FitHeight);
512 }
513
514 void ComicImageView::setSizeWholePage()
515 {
516         setSize(WholePage);
517 }
518
519 void ComicImageView::setSizeBestFit()
520 {
521         setSize(BestFit);
522 }
523
524 void ComicImageView::scrollToTop()
525 {
526         ensureVisible(1, 1);
527 }
528
529 void ComicImageView::scrollToBottom()
530 {
531         ensureVisible(1, contentsHeight());
532 }
533
534 void ComicImageView::scrollRight()
535 {
536         scrollBy(spdx, 0);
537 }
538
539 void ComicImageView::scrollLeft()
540 {
541         scrollBy(-spdx, 0);
542 }
543
544 void ComicImageView::scrollRightFast()
545 {
546         scrollBy(3*spdx, 0);
547 }
548
549 void ComicImageView::scrollLeftFast()
550 {
551         scrollBy(-3*spdx, 0);
552 }
553
554 void ComicImageView::scrollUp()
555 {
556         if (onTop())
557         {
558                 wheelupcnt = wheeldowncnt = 0;
559                 emit topReached();
560         }
561         else
562                 scrollBy(0, -spdy);
563 }
564
565 void ComicImageView::scrollDown()
566 {
567         if (onBottom())
568         {
569                 wheelupcnt = wheeldowncnt = 0;
570                 emit bottomReached();
571         }
572         else
573                 scrollBy(0, spdy);
574 }
575
576 void ComicImageView::scrollUpFast()
577 {
578         if (onTop())
579                 emit topReached();
580         else
581                 scrollBy(0, -3*spdy);
582 }
583
584 void ComicImageView::scrollDownFast()
585 {       
586         if (onBottom())
587                 emit bottomReached();
588         else
589                 scrollBy(0, 3*spdy);
590 }
591
592 void ComicImageView::rotateRight()
593 {
594         setRotation(QComicBook::Right);
595 }
596
597 void ComicImageView::rotateLeft()
598 {
599         setRotation(QComicBook::Left);
600 }
601
602 void ComicImageView::resetRotation()
603 {
604         setRotation(None);
605 }
606
607 void ComicImageView::jumpUp()
608 {
609         if (onTop())
610                 emit topReached();
611         else
612                 scrollBy(0, -viewport()->height());
613 }
614
615 void ComicImageView::jumpDown()
616 {
617         if (onBottom())
618                 emit bottomReached();
619         else
620                 scrollBy(0, viewport()->height());
621 }
622
623 void ComicImageView::enableScrollbars(bool f)
624 {
625         ScrollBarMode s = f ? Auto : AlwaysOff;
626         setVScrollBarMode(s);
627         setHScrollBarMode(s);
628 }
629
630 void ComicImageView::setBackground(const QColor &color)
631 {
632         viewport()->setPaletteBackgroundColor(color);
633 }
634
635 void ComicImageView::setSmallCursor(bool f)
636 {
637         if (f)
638         {
639                 static unsigned char bmp_bits[4*32];
640                 static unsigned char msk_bits[4*32];
641
642                 if (smallcursor)
643                         return;
644
645                 for (int i=0; i<4*32; i++)
646                         bmp_bits[i] = msk_bits[i] = 0;
647                 bmp_bits[0] = msk_bits[0] = 0xe0;
648                 bmp_bits[4] = 0xa0;
649                 msk_bits[4] = 0xe0;
650                 bmp_bits[8] = msk_bits[8] = 0xe0;
651                 const QBitmap bmp(32, 32, bmp_bits, false);
652                 const QBitmap msk(32, 32, msk_bits, false);
653                 smallcursor = new QCursor(bmp, msk, 0, 0);
654                 setCursor(*smallcursor);
655         }
656         else
657         {
658                 if (smallcursor)
659                         delete smallcursor;
660                 smallcursor = NULL;
661                 setCursor(Qt::ArrowCursor);
662         }
663 }
664
665 void ComicImageView::clear()
666 {
667         orgimage[0].clear();
668         orgimage[1].clear();
669         resizeContents(0, 0);
670 }
671
672 Size ComicImageView::getSize() const
673 {
674         return isize;
675 }
676
677 int ComicImageView::imageWidth() const
678 {
679         if (!orgimage[0].isEmpty())
680                 return orgimage[0].getFirst()->width();
681         return 0;
682 }
683