Pass ImlibImageLists over to the view
[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].current();
53                 image2 = orgimage[0].current();
54         }
55         else
56         {
57                 image1 = orgimage[0].current();
58                 image2 = orgimage[1].current();
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].current()->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].current()->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].current()->rotate(iangle);
211                 if (!orgimage[1].isEmpty())
212                         orgimage[1].current()->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].current()->rotate(iangle);
236                 if (!orgimage[1].isEmpty())
237                         orgimage[1].current()->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         if (orgimage[0].current()->width() * orgimage[0].current()->height() == 0)
324                 return;
325
326         Size size = isize;
327
328         //
329         // calculate iw, ih - the size of total image(s) area without scaling;
330         // roatation angle of 2nd image is taken into account
331         double iw = orgimage[0].current()->width();
332         double ih = orgimage[0].current()->height();
333
334         if (!orgimage[1].isEmpty())
335         {
336                 if (iangle & 1)
337                 {
338                         ih += orgimage[1].current()->height();
339                         if (orgimage[1].current()->width() > iw)
340                                 iw = orgimage[1].current()->width();
341                 }
342                 else
343                 {
344                         iw += orgimage[1].current()->width();
345                         if (orgimage[1].current()->height() > ih)
346                                 ih = orgimage[1].current()->height();
347                 }
348         }
349         
350         if (size == BestFit)
351         {
352                 if (iw > ih)
353                 {
354                         if (iangle&1)
355                                 size = FitWidth;
356                         else
357                                 size = FitHeight;
358                 }
359                 else
360                 {
361                         if (iangle&1)
362                                 size = FitHeight;
363                         else
364                                 size = FitWidth;
365                 }
366         }
367
368         int dw, dh;
369                 
370         w_asp = h_asp = 1.0f;
371         if (size == Original)
372         {
373                 dw = static_cast<int>(iw);
374                 dh = static_cast<int>(ih);
375         }
376         else
377         {       
378                 asp = (double)iw / ih;
379
380                 if (size == FitWidth)
381                 {
382                         w_asp = iw / viewport()->width();
383                         h_asp = w_asp * asp;
384                         h_asp = w_asp;
385                 }
386                 else if (size == FitHeight)
387                 {
388                         h_asp = ih / viewport()->height();
389                         w_asp = h_asp;
390                 }
391                 else if (size == WholePage)
392                 {
393                         w_asp = iw / viewport()->width();
394                         h_asp = ih / viewport()->height();
395                         if (w_asp > h_asp)
396                                 h_asp = w_asp;
397                         else
398                                 w_asp = h_asp;
399                 }
400
401                 dw = static_cast<int>(iw / w_asp);
402                 dh = static_cast<int>(ih / h_asp);
403         }
404
405         int d;
406         xoff = yoff = 0;
407
408         //
409         // calculate offsets for image centering
410         if ((d = viewport()->width() - dw) > 0)
411                 xoff = d/2;
412         if ((d = viewport()->height() - dh) > 0)
413                 yoff = d/2;
414           
415         resizeContents(dw + xoff, dh + yoff);
416                 
417         //
418         // update scrolling speeds
419         spdx = dw / 100;
420         spdy = dh / 100;
421 }
422
423 /*void ComicImageView::setScaling(Scaling s)
424 {
425         iscaling = s;
426 }*/
427
428 void ComicImageView::setRotation(Rotation r)
429 {
430         if (r == QComicBook::Right)
431                 ++iangle;
432         else if (r == QComicBook::Left)
433                 --iangle;
434         iangle &= 3;
435
436         for (int i=0; i<2; i++)
437                 if (!orgimage[i].isEmpty())
438                 {
439                         if (r == QComicBook::Right)
440                                 orgimage[i].current()->rotate(1);
441                         else if (r == QComicBook::Left)
442                                 orgimage[i].current()->rotate(3);
443                         else if (r == QComicBook::None && iangle != 0)
444                         {
445                                 orgimage[i].current()->rotate(4-iangle);
446                                 iangle = 0;
447                         }
448                 }
449
450         updateImageSize();
451         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height(), true);
452 }
453
454 void ComicImageView::setSize(Size s)
455 {
456         isize = s;
457         updateImageSize();
458         repaintContents(contentsX(), contentsY(), viewport()->width(), viewport()->height(), true);
459 }
460
461 void ComicImageView::setSizeOriginal()
462 {
463         setSize(Original);
464 }
465
466 void ComicImageView::setSizeFitWidth()
467 {
468         setSize(FitWidth);
469 }
470
471 void ComicImageView::setSizeFitHeight()
472 {
473         setSize(FitHeight);
474 }
475
476 void ComicImageView::setSizeWholePage()
477 {
478         setSize(WholePage);
479 }
480
481 void ComicImageView::setSizeBestFit()
482 {
483         setSize(BestFit);
484 }
485
486 void ComicImageView::scrollToTop()
487 {
488         ensureVisible(1, 1);
489 }
490
491 void ComicImageView::scrollToBottom()
492 {
493         ensureVisible(1, contentsHeight());
494 }
495
496 void ComicImageView::scrollRight()
497 {
498         scrollBy(spdx, 0);
499 }
500
501 void ComicImageView::scrollLeft()
502 {
503         scrollBy(-spdx, 0);
504 }
505
506 void ComicImageView::scrollRightFast()
507 {
508         scrollBy(3*spdx, 0);
509 }
510
511 void ComicImageView::scrollLeftFast()
512 {
513         scrollBy(-3*spdx, 0);
514 }
515
516 void ComicImageView::scrollUp()
517 {
518         if (onTop())
519         {
520                 wheelupcnt = wheeldowncnt = 0;
521                 emit topReached();
522         }
523         else
524                 scrollBy(0, -spdy);
525 }
526
527 void ComicImageView::scrollDown()
528 {
529         if (onBottom())
530         {
531                 wheelupcnt = wheeldowncnt = 0;
532                 emit bottomReached();
533         }
534         else
535                 scrollBy(0, spdy);
536 }
537
538 void ComicImageView::scrollUpFast()
539 {
540         if (onTop())
541                 emit topReached();
542         else
543                 scrollBy(0, -3*spdy);
544 }
545
546 void ComicImageView::scrollDownFast()
547 {       
548         if (onBottom())
549                 emit bottomReached();
550         else
551                 scrollBy(0, 3*spdy);
552 }
553
554 void ComicImageView::rotateRight()
555 {
556         setRotation(QComicBook::Right);
557 }
558
559 void ComicImageView::rotateLeft()
560 {
561         setRotation(QComicBook::Left);
562 }
563
564 void ComicImageView::resetRotation()
565 {
566         setRotation(None);
567 }
568
569 void ComicImageView::jumpUp()
570 {
571         if (onTop())
572                 emit topReached();
573         else
574                 scrollBy(0, -viewport()->height());
575 }
576
577 void ComicImageView::jumpDown()
578 {
579         if (onBottom())
580                 emit bottomReached();
581         else
582                 scrollBy(0, viewport()->height());
583 }
584
585 void ComicImageView::enableScrollbars(bool f)
586 {
587         ScrollBarMode s = f ? Auto : AlwaysOff;
588         setVScrollBarMode(s);
589         setHScrollBarMode(s);
590 }
591
592 void ComicImageView::setBackground(const QColor &color)
593 {
594         viewport()->setPaletteBackgroundColor(color);
595 }
596
597 void ComicImageView::setSmallCursor(bool f)
598 {
599         if (f)
600         {
601                 static unsigned char bmp_bits[4*32];
602                 static unsigned char msk_bits[4*32];
603
604                 if (smallcursor)
605                         return;
606
607                 for (int i=0; i<4*32; i++)
608                         bmp_bits[i] = msk_bits[i] = 0;
609                 bmp_bits[0] = msk_bits[0] = 0xe0;
610                 bmp_bits[4] = 0xa0;
611                 msk_bits[4] = 0xe0;
612                 bmp_bits[8] = msk_bits[8] = 0xe0;
613                 const QBitmap bmp(32, 32, bmp_bits, false);
614                 const QBitmap msk(32, 32, msk_bits, false);
615                 smallcursor = new QCursor(bmp, msk, 0, 0);
616                 setCursor(*smallcursor);
617         }
618         else
619         {
620                 if (smallcursor)
621                         delete smallcursor;
622                 smallcursor = NULL;
623                 setCursor(Qt::ArrowCursor);
624         }
625 }
626
627 void ComicImageView::clear()
628 {
629         orgimage[0].clear();
630         orgimage[1].clear();
631         resizeContents(0, 0);
632 }
633
634 Size ComicImageView::getSize() const
635 {
636         return isize;
637 }
638
639 int ComicImageView::imageWidth() const
640 {
641         if (!orgimage[0].isEmpty())
642                 return orgimage[0].current()->width();
643         return 0;
644 }
645