1 /*
2     This file is part of Kig, a KDE program for Interactive Geometry...
3     SPDX-FileCopyrightText: 2002 Dominique Devriese <devriese@kde.org>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "kig_view.h"
9 
10 
11 #include "kig_part.h"
12 #include "kig_document.h"
13 #include "kig_commands.h"
14 #include "../misc/coordinate_system.h"
15 #include "../misc/kiginputdialog.h"
16 #include "../misc/kigpainter.h"
17 #include "../modes/mode.h"
18 #include "../modes/dragrectmode.h"
19 
20 #include <QApplication>
21 #include <QEvent>
22 #include <QLayout>
23 #include <QScrollBar>
24 #include <QWheelEvent>
25 
26 #include <QDebug>
27 
28 #include <cmath>
29 #include <algorithm>
30 #include <iterator>
31 
KigWidget(KigPart * part,KigView * view,QWidget * parent,bool fullscreen)32 KigWidget::KigWidget( KigPart* part,
33                       KigView* view,
34                       QWidget* parent,
35                       bool fullscreen )
36   : QWidget( parent,
37              fullscreen ? Qt::FramelessWindowHint : Qt::Widget ),
38     mpart( part ),
39     mview( view ),
40     stillPix(size()),
41     curPix(size()),
42     msi( Rect(), rect() ),
43     misfullscreen( fullscreen ),
44     mispainting( false ),
45     malreadyresized( false )
46 {
47   part->addWidget(this);
48 
49   setFocusPolicy(Qt::ClickFocus);
50   setAttribute( Qt::WA_OpaquePaintEvent, true );
51   setMouseTracking(true);
52 
53   curPix = QPixmap( size() );
54   stillPix = QPixmap( size() );
55 }
56 
~KigWidget()57 KigWidget::~KigWidget()
58 {
59   mpart->delWidget( this );
60 }
61 
paintEvent(QPaintEvent * e)62 void KigWidget::paintEvent(QPaintEvent* e)
63 {
64   mispainting = true;
65   std::vector<QRect> overlay;
66   overlay.push_back( e->rect() );
67   updateWidget( overlay );
68 }
69 
mousePressEvent(QMouseEvent * e)70 void KigWidget::mousePressEvent (QMouseEvent* e)
71 {
72   if( e->button() & Qt::LeftButton )
73     return mpart->mode()->leftClicked( e, this );
74   if ( e->button() & Qt::MiddleButton )
75     return mpart->mode()->midClicked( e, this );
76   if ( e->button() & Qt::RightButton )
77     return mpart->mode()->rightClicked( e, this );
78 }
79 
mouseMoveEvent(QMouseEvent * e)80 void KigWidget::mouseMoveEvent (QMouseEvent* e)
81 {
82   if( ( e->buttons() & Qt::LeftButton ) == Qt::LeftButton )
83     return mpart->mode()->leftMouseMoved( e, this );
84   if ( ( e->buttons() & Qt::MiddleButton ) == Qt::MidButton )
85     return mpart->mode()->midMouseMoved( e, this );
86   if ( ( e->buttons() & Qt::RightButton ) == Qt::RightButton )
87     return mpart->mode()->rightMouseMoved( e, this );
88   return mpart->mode()->mouseMoved( e, this );
89 }
90 
mouseReleaseEvent(QMouseEvent * e)91 void KigWidget::mouseReleaseEvent (QMouseEvent* e)
92 {
93   if( e->button() & Qt::LeftButton )
94     return mpart->mode()->leftReleased( e, this );
95   if ( e->button() & Qt::MiddleButton )
96     return mpart->mode()->midReleased( e, this );
97   if ( e->button() & Qt::RightButton )
98     return mpart->mode()->rightReleased( e, this );
99 }
100 
updateWidget(const std::vector<QRect> & overlay)101 void KigWidget::updateWidget( const std::vector<QRect>& overlay )
102 {
103   if ( !mispainting )
104   {
105     QRect r;
106     int rectinited = false;
107     for ( std::vector<QRect>::const_iterator i = oldOverlay.begin(); i != oldOverlay.end(); ++i )
108       if ( rectinited )
109         r |= *i;
110       else
111       {
112         r = *i;
113         rectinited = true;
114       }
115     for ( std::vector<QRect>::const_iterator i = overlay.begin(); i != overlay.end(); ++i )
116       if ( rectinited )
117         r |= *i;
118       else
119       {
120         r = *i;
121         rectinited = true;
122       }
123     repaint( r );
124     return;
125   }
126 
127   oldOverlay = overlay;
128 
129   QPainter p( this );
130   p.drawPixmap( overlay.front().topLeft(), curPix, overlay.front() );
131   p.end();
132   mispainting = false;
133 }
134 
updateEntireWidget()135 void KigWidget::updateEntireWidget()
136 {
137   std::vector<QRect> overlay;
138   overlay.push_back( QRect( QPoint( 0, 0 ), size() ) );
139   updateWidget( overlay );
140 }
141 
resizeEvent(QResizeEvent * e)142 void KigWidget::resizeEvent( QResizeEvent* e )
143 {
144   QSize osize = e->oldSize();
145   QSize nsize = e->size();
146   Rect orect = msi.shownRect();
147 
148   curPix = QPixmap( nsize );
149   stillPix = QPixmap( nsize );
150   msi.setViewRect( rect() );
151 
152   Rect nrect( 0., 0.,
153               orect.width() * nsize.width() / osize.width(),
154               orect.height() * nsize.height() / osize.height() );
155   nrect = matchScreenShape( nrect );
156   nrect.setCenter( orect.center() );
157   msi.setShownRect( nrect );
158 
159   // horrible hack...  We need to somehow differentiate between the
160   // resizeEvents we get on startup, and the ones generated by the
161   // user.  The first requires recentering the screen, the latter
162   // does not..
163   if ( !malreadyresized )
164   {
165     recenterScreen();
166     malreadyresized = true;
167   }
168 
169   mpart->redrawScreen( this );
170   updateScrollBars();
171 }
172 
updateCurPix(const std::vector<QRect> & ol)173 void KigWidget::updateCurPix( const std::vector<QRect>& ol )
174 {
175   // we make curPix look like stillPix again...
176   QPainter p( &curPix );
177   for ( std::vector<QRect>::const_iterator i = oldOverlay.begin(); i != oldOverlay.end(); ++i )
178     p.drawPixmap( i->topLeft(), stillPix, *i );
179   for ( std::vector<QRect>::const_iterator i = ol.begin(); i != ol.end(); ++i )
180     p.drawPixmap( i->topLeft(), stillPix, *i );
181   p.end();
182 
183   // we add ol to oldOverlay, so that part of the widget will be
184   // updated too in updateWidget...
185   std::copy( ol.begin(), ol.end(), std::back_inserter( oldOverlay ) );
186 }
187 
recenterScreen()188 void KigWidget::recenterScreen()
189 {
190   msi.setShownRect( matchScreenShape( mpart->document().suggestedRect() ) );
191 }
192 
matchScreenShape(const Rect & r) const193 Rect KigWidget::matchScreenShape( const Rect& r ) const
194 {
195   return r.matchShape( Rect::fromQRect( rect() ) );
196 }
197 
slotZoomIn()198 void KigWidget::slotZoomIn()
199 {
200   Rect nr = msi.shownRect();
201   Coordinate c = nr.center();
202   nr /= 2;
203   nr.setCenter( c );
204   KigCommand* cd =
205     new KigCommand( *mpart,
206                     i18n( "Zoom In" ) );
207   cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
208   mpart->history()->push( cd );
209 }
210 
slotZoomOut()211 void KigWidget::slotZoomOut()
212 {
213   Rect nr = msi.shownRect();
214   Coordinate c = nr.center();
215   nr *= 2;
216   nr.setCenter( c );
217 
218   // zooming in is undoable..  I know this isn't really correct,
219   // because the current view doesn't really belong to the document (
220   // althought KGeo and KSeg both save them along, iirc ).  However,
221   // undoing a zoom or another operation affecting the window seems a
222   // bit too useful to not be available.  Please try to convince me if
223   // you feel otherwise ;-)
224   KigCommand* cd =
225     new KigCommand( *mpart,
226                     i18n( "Zoom Out" ) );
227   cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
228   mpart->history()->push( cd );
229 }
230 
clearStillPix()231 void KigWidget::clearStillPix()
232 {
233   stillPix.fill(Qt::white);
234   oldOverlay.clear();
235   oldOverlay.push_back ( QRect( QPoint(0,0), size() ) );
236 }
237 
redrawScreen(const std::vector<ObjectHolder * > & _selection,bool dos)238 void KigWidget::redrawScreen( const std::vector<ObjectHolder*>& _selection, bool dos )
239 {
240   std::vector<ObjectHolder*> nonselection;
241   std::vector<ObjectHolder*> selection = _selection;
242   std::set<ObjectHolder*> objs = mpart->document().objectsSet();
243   std::sort( selection.begin(), selection.end() );
244   std::set_difference( objs.begin(), objs.end(), selection.begin(), selection.end(),
245                        std::back_inserter( nonselection ) );
246 
247   // update the screen...
248   clearStillPix();
249   KigPainter p( msi, &stillPix, mpart->document() );
250   p.drawGrid( mpart->document().coordinateSystem(), mpart->document().grid(),
251               mpart->document().axes() );
252   p.drawObjects( selection, true );
253   p.drawObjects( nonselection, false );
254   updateCurPix( p.overlay() );
255   if ( dos ) updateEntireWidget();
256 }
257 
screenInfo() const258 const ScreenInfo& KigWidget::screenInfo() const
259 {
260   return msi;
261 }
262 
showingRect() const263 const Rect KigWidget::showingRect() const
264 {
265   return msi.shownRect();
266 }
267 
fromScreen(const QPoint & p)268 const Coordinate KigWidget::fromScreen( const QPoint& p )
269 {
270   return msi.fromScreen( p );
271 }
272 
pixelWidth() const273 double KigWidget::pixelWidth() const
274 {
275   return msi.pixelWidth();
276 }
277 
fromScreen(const QRect & r)278 const Rect KigWidget::fromScreen( const QRect& r )
279 {
280   return msi.fromScreen( r );
281 }
282 
283 
updateScrollBars()284 void KigWidget::updateScrollBars()
285 {
286   mview->updateScrollBars();
287 }
288 
KigView(KigPart * part,bool fullscreen,QWidget * parent)289 KigView::KigView( KigPart* part,
290                   bool fullscreen,
291                   QWidget* parent )
292   : QWidget( parent ),
293     mlayout( 0 ), mrightscroll( 0 ), mbottomscroll( 0 ),
294     mupdatingscrollbars( false ),
295     mrealwidget( 0 ), mpart( part )
296 {
297   connect( part, &KigPart::recenterScreen, this, &KigView::slotInternalRecenterScreen );
298 
299   mlayout = new QGridLayout( this );
300   mlayout->setContentsMargins( 2 ,  2 ,  2 ,  2 );
301   mlayout->setSpacing( 2 );
302   mrightscroll = new QScrollBar( Qt::Vertical, this );
303   mrightscroll->setObjectName( QStringLiteral("Right Scrollbar") );
304   // TODO: make this configurable...
305   mrightscroll->setTracking( true );
306   connect( mrightscroll, &QAbstractSlider::valueChanged,
307            this, &KigView::slotRightScrollValueChanged );
308   connect( mrightscroll, &QAbstractSlider::sliderReleased,
309            this, &KigView::updateScrollBars );
310   mbottomscroll = new QScrollBar( Qt::Horizontal, this );
311   mbottomscroll->setObjectName( QStringLiteral("Bottom Scrollbar") );
312   connect( mbottomscroll, &QAbstractSlider::valueChanged,
313            this, &KigView::slotBottomScrollValueChanged );
314   connect( mbottomscroll, &QAbstractSlider::sliderReleased,
315            this, &KigView::updateScrollBars );
316   mrealwidget = new KigWidget( part, this, this, fullscreen );
317   mrealwidget->setObjectName( QStringLiteral("Kig Widget") );
318   mlayout->addWidget( mbottomscroll, 1, 0 );
319   mlayout->addWidget( mrealwidget, 0, 0 );
320   mlayout->addWidget( mrightscroll, 0, 1 );
321 
322   resize( sizeHint() );
323   mrealwidget->recenterScreen();
324   part->redrawScreen( mrealwidget );
325   updateScrollBars();
326 }
327 
updateScrollBars()328 void KigView::updateScrollBars()
329 {
330   // we update the scrollbars to reflect the new "total size" of the
331   // document...  The total size is scaled in entireDocumentRect().
332   // ( it is scaled as a rect that contains all the points in the
333   // document, and then enlarged a bit, and scaled to match the screen
334   // width/height ratio...)
335   // What we do here is tell the scroll bars what they should show as
336   // their total size..
337 
338   // see the doc of this variable in the header for this...
339   mupdatingscrollbars = true;
340 
341   Rect er = mrealwidget->entireDocumentRect();
342   Rect sr = mrealwidget->screenInfo().shownRect();
343 
344   // we define the total rect to be the smallest rect that contains
345   // both er and sr...
346   er |= sr;
347 
348   // we need ints, not doubles, so since "pixelwidth == widgetcoord /
349   // internalcoord", we use "widgetcoord/pixelwidth", which would then
350   // equal "internalcoord", which has to be an int ( by definition.. )
351   // I know, I'm a freak to think about these sorts of things... ;)
352   double pw = mrealwidget->screenInfo().pixelWidth();
353 
354   // what the scrollbars reflect is the bottom resp. the left side of
355   // the shown rect.  This is why the maximum value is not er.top()
356   // (which would be the maximum value of the top of the shownRect),
357   // but er.top() - sr.height(), which is the maximum value the bottom of
358   // the shownRect can reach...
359 
360   int rightmin = static_cast<int>( er.bottom() / pw );
361   int rightmax = static_cast<int>( ( er.top() - sr.height() ) / pw );
362 
363   mrightscroll->setMinimum( rightmin );
364   mrightscroll->setMaximum( rightmax );
365   mrightscroll->setSingleStep( (int)( sr.height() / pw / 10 ) );
366   mrightscroll->setPageStep( (int)( sr.height() / pw / 1.2 ) );
367 
368   // note that since Qt has a coordinate system with the lowest y
369   // values at the top, and we have it the other way around ( i know I
370   // shouldn't have done this.. :( ), we invert the value that the
371   // scrollbar shows.  This is inverted again in
372   // slotRightScrollValueChanged()...
373   mrightscroll->setValue( (int) ( rightmin + ( rightmax - ( sr.bottom() / pw ) ) ) );
374 
375   mbottomscroll->setMinimum( (int)( er.left() / pw ) );
376   mbottomscroll->setMaximum( (int)( ( er.right() - sr.width() ) / pw ) );
377   mbottomscroll->setSingleStep( (int)( sr.width() / pw / 10 ) );
378   mbottomscroll->setPageStep( (int)( sr.width() / pw / 1.2 ) );
379   mbottomscroll->setValue( (int)( sr.left() / pw ) );
380 
381   mupdatingscrollbars = false;
382 }
383 
entireDocumentRect() const384 Rect KigWidget::entireDocumentRect() const
385 {
386   return matchScreenShape( mpart->document().suggestedRect() );
387 }
388 
slotRightScrollValueChanged(int v)389 void KigView::slotRightScrollValueChanged( int v )
390 {
391   if ( ! mupdatingscrollbars )
392   {
393     // we invert the inversion that was done in updateScrollBars() (
394     // check the documentation there..; )
395     v = mrightscroll->minimum() + ( mrightscroll->maximum() - v );
396     double pw = mrealwidget->screenInfo().pixelWidth();
397     double nb = double( v ) * pw;
398     mrealwidget->scrollSetBottom( nb );
399   };
400 }
401 
slotBottomScrollValueChanged(int v)402 void KigView::slotBottomScrollValueChanged( int v )
403 {
404   if ( ! mupdatingscrollbars )
405   {
406     double pw = mrealwidget->screenInfo().pixelWidth();
407     double nl = double( v ) * pw;
408     mrealwidget->scrollSetLeft( nl );
409   };
410 }
411 
scrollSetBottom(double rhs)412 void KigWidget::scrollSetBottom( double rhs )
413 {
414   Rect sr = msi.shownRect();
415   Coordinate bl = sr.bottomLeft();
416   bl.y = rhs;
417   sr.setBottomLeft( bl );
418   msi.setShownRect( sr );
419   mpart->redrawScreen( this );
420 }
421 
scrollSetLeft(double rhs)422 void KigWidget::scrollSetLeft( double rhs )
423 {
424   Rect sr = msi.shownRect();
425   Coordinate bl = sr.bottomLeft();
426   bl.x = rhs;
427   sr.setBottomLeft( bl );
428   msi.setShownRect( sr );
429   mpart->redrawScreen( this );
430 }
431 
screenInfo() const432 const ScreenInfo& KigView::screenInfo() const
433 {
434   return mrealwidget->screenInfo();
435 }
436 
~KigView()437 KigView::~KigView()
438 {
439 }
440 
realWidget() const441 KigWidget* KigView::realWidget() const
442 {
443   return mrealwidget;
444 }
445 
document() const446 const KigDocument& KigWidget::document() const
447 {
448   return mpart->document();
449 }
450 
sizeHint() const451 QSize KigWidget::sizeHint() const
452 {
453   return QSize( 630, 450 );
454 }
455 
wheelEvent(QWheelEvent * e)456 void KigWidget::wheelEvent( QWheelEvent* e )
457 {
458     mview->scrollVertical( e->angleDelta().y() );
459     mview->scrollHorizontal( e->angleDelta().x() );
460 }
461 
scrollHorizontal(int delta)462 void KigView::scrollHorizontal( int delta )
463 {
464   if ( delta >= 0 )
465     for ( int i = 0; i < delta; i += 120 )
466       mbottomscroll->triggerAction( QScrollBar::SliderSingleStepSub );
467   else
468     for ( int i = 0; i >= delta; i -= 120 )
469       mbottomscroll->triggerAction( QScrollBar::SliderSingleStepAdd );
470 }
471 
scrollVertical(int delta)472 void KigView::scrollVertical( int delta )
473 {
474   if ( delta >= 0 )
475     for ( int i = 0; i < delta; i += 120 )
476       mrightscroll->triggerAction( QScrollBar::SliderSingleStepSub );
477   else
478     for ( int i = 0; i >= delta; i -= 120 )
479       mrightscroll->triggerAction( QScrollBar::SliderSingleStepAdd );
480 }
481 
isFullScreen() const482 bool KigWidget::isFullScreen() const
483 {
484   return misfullscreen;
485 }
486 
slotZoomIn()487 void KigView::slotZoomIn()
488 {
489   mrealwidget->slotZoomIn();
490 }
491 
slotZoomOut()492 void KigView::slotZoomOut()
493 {
494   mrealwidget->slotZoomOut();
495 }
496 
slotRecenterScreen()497 void KigWidget::slotRecenterScreen()
498 {
499   Rect nr = mpart->document().suggestedRect();
500   KigCommand* cd =
501     new KigCommand( *mpart,
502                     i18n( "Recenter View" ) );
503 
504   cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
505   mpart->history()->push( cd );
506 }
507 
toggleFullScreen()508 void KigView::toggleFullScreen()
509 {
510   mrealwidget->setFullScreen( ! mrealwidget->isFullScreen() );
511   if ( mrealwidget->isFullScreen() )
512     topLevelWidget()->setWindowState( topLevelWidget()->windowState() | Qt::WindowFullScreen ); // set
513   else
514     topLevelWidget()->setWindowState( topLevelWidget()->windowState() & ~Qt::WindowFullScreen ); // reset
515 }
516 
setFullScreen(bool f)517 void KigWidget::setFullScreen( bool f )
518 {
519   misfullscreen = f;
520 }
521 
zoomRect()522 void KigWidget::zoomRect()
523 {
524   mpart->emitStatusBarText( i18n( "Select the rectangle that should be shown." ) );
525   DragRectMode d( *mpart, *this );
526   mpart->runMode( &d );
527   if ( ! d.cancelled() )
528   {
529     Rect nr = d.rect();
530     KigCommand* cd =
531       new KigCommand( *mpart,
532                       i18n( "Change Shown Part of Screen" ) );
533 
534     cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
535     mpart->history()->push( cd );
536   };
537 
538   mpart->redrawScreen( this );
539   updateScrollBars();
540 }
541 
zoomRect()542 void KigView::zoomRect()
543 {
544   mrealwidget->zoomRect();
545 }
546 
setShowingRect(const Rect & r)547 void KigWidget::setShowingRect( const Rect& r )
548 {
549   msi.setShownRect( r.matchShape( Rect::fromQRect( rect() ) ) );
550 }
551 
slotRecenterScreen()552 void KigView::slotRecenterScreen()
553 {
554   mrealwidget->slotRecenterScreen();
555 }
556 
slotInternalRecenterScreen()557 void KigView::slotInternalRecenterScreen()
558 {
559   mrealwidget->recenterScreen();
560 }
561 
zoomArea()562 void KigWidget::zoomArea()
563 {
564 //  mpart->emitStatusBarText( i18n( "Select the area that should be shown." ) );
565   Rect oldrect = showingRect();
566   Coordinate tl = oldrect.topLeft();
567   Coordinate br = oldrect.bottomRight();
568   bool ok = true;
569   KigInputDialog::getTwoCoordinates( i18n( "Select Zoom Area" ),
570         i18n( "Select the zoom area by entering the coordinates<br />"
571               "of the upper left corner and the lower right corner." ) +
572         QLatin1String( "<br />" ) +
573         mpart->document().coordinateSystem().coordinateFormatNoticeMarkup(),
574         this, &ok, mpart->document(), &tl, &br );
575   if ( ok )
576   {
577     Coordinate nc1( tl.x, br.y );
578     Coordinate nc2( br.x, tl.y );
579     Rect nr( nc1, nc2 );
580     KigCommand* cd = new KigCommand( *mpart, i18n( "Change Shown Part of Screen" ) );
581 
582     cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
583     mpart->history()->push( cd );
584   }
585 
586   mpart->redrawScreen( this );
587   updateScrollBars();
588 }
589 
zoomArea()590 void KigView::zoomArea()
591 {
592   mrealwidget->zoomArea();
593 }
594 
595