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