You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeedu/kig/kig/kig_view.cpp

594 lines
16 KiB

/**
This file is part of Kig, a KDE program for Interactive Geometry...
Copyright (C) 2002 Dominique Devriese <devriese@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
**/
#include "kig_view.h"
#include "kig_view.moc"
#include "kig_part.h"
#include "kig_document.h"
#include "kig_commands.h"
#include "../misc/coordinate_system.h"
#include "../misc/kiginputdialog.h"
#include "../misc/kigpainter.h"
#include "../modes/mode.h"
#include "../modes/dragrectmode.h"
#include <tqdialog.h>
#include <tqevent.h>
#include <tqwhatsthis.h>
#include <tqlayout.h>
#include <tqscrollbar.h>
#include <kdebug.h>
#include <kcursor.h>
#include <tdelocale.h>
#include <tdeapplication.h>
#include <kstdaction.h>
#include <tdeaction.h>
#include <kiconloader.h>
#include <cmath>
#include <algorithm>
kdbgstream& operator<< ( kdbgstream& s, const TQPoint& t )
{
s << "x: " << t.x() << " y: " << t.y();
return s;
}
KigWidget::KigWidget( KigPart* part,
KigView* view,
TQWidget* parent,
const char* name,
bool fullscreen )
: TQWidget( parent, name,
fullscreen ? WStyle_Customize | WStyle_NoBorder : 0 ),
mpart( part ),
mview( view ),
stillPix(size()),
curPix(size()),
msi( Rect(), rect() ),
misfullscreen( fullscreen )
{
part->addWidget(this);
setFocusPolicy(TQWidget::ClickFocus);
setBackgroundMode( TQt::NoBackground );
setMouseTracking(true);
curPix.resize( size() );
stillPix.resize( size() );
}
KigWidget::~KigWidget()
{
mpart->delWidget( this );
}
void KigWidget::paintEvent(TQPaintEvent*)
{
updateEntireWidget();
}
void KigWidget::mousePressEvent (TQMouseEvent* e)
{
if( e->button() & TQt::LeftButton )
return mpart->mode()->leftClicked( e, this );
if ( e->button() & TQt::MidButton )
return mpart->mode()->midClicked( e, this );
if ( e->button() & TQt::RightButton )
return mpart->mode()->rightClicked( e, this );
}
void KigWidget::mouseMoveEvent (TQMouseEvent* e)
{
if( e->state() & TQt::LeftButton )
return mpart->mode()->leftMouseMoved( e, this );
if ( e->state() & TQt::MidButton )
return mpart->mode()->midMouseMoved( e, this );
if ( e->state() & TQt::RightButton )
return mpart->mode()->rightMouseMoved( e, this );
return mpart->mode()->mouseMoved( e, this );
}
void KigWidget::mouseReleaseEvent (TQMouseEvent* e)
{
if( e->state() & TQt::LeftButton )
return mpart->mode()->leftReleased( e, this );
if ( e->state() & TQt::MidButton )
return mpart->mode()->midReleased( e, this );
if ( e->state() & TQt::RightButton )
return mpart->mode()->rightReleased( e, this );
}
void KigWidget::updateWidget( const std::vector<TQRect>& overlay )
{
#undef SHOW_OVERLAY_RECTS
#ifdef SHOW_OVERLAY_RECTS
TQPainter debug (this, this);
debug.setPen(TQt::yellow);
#endif // SHOW_OVERLAY_RECTS
// we undo our old changes...
for ( std::vector<TQRect>::const_iterator i = oldOverlay.begin(); i != oldOverlay.end(); ++i )
bitBlt( this, i->topLeft(), &curPix, *i );
// we add our new changes...
for ( std::vector<TQRect>::const_iterator i = overlay.begin(); i != overlay.end(); ++i )
{
bitBlt( this, i->topLeft(), &curPix, *i );
#ifdef SHOW_OVERLAY_RECTS
debug.drawRect(*i);
#endif
};
oldOverlay = overlay;
}
void KigWidget::updateEntireWidget()
{
std::vector<TQRect> overlay;
overlay.push_back( TQRect( TQPoint( 0, 0 ), size() ) );
updateWidget( overlay );
}
void KigWidget::resizeEvent( TQResizeEvent* e )
{
TQSize osize = e->oldSize();
TQSize nsize = e->size();
Rect orect = msi.shownRect();
curPix.resize( nsize );
stillPix.resize( nsize );
msi.setViewRect( rect() );
Rect nrect( 0., 0.,
orect.width() * nsize.width() / osize.width(),
orect.height() * nsize.height() / osize.height() );
nrect = matchScreenShape( nrect );
nrect.setCenter( orect.center() );
msi.setShownRect( nrect );
// horrible hack... We need to somehow differentiate between the
// resizeEvents we get on startup, and the ones generated by the
// user. The first require recentering the screen, the latter
// don't..
if ( nsize.width() / osize.width() > 4 ) recenterScreen();
mpart->redrawScreen( this );
updateScrollBars();
}
void KigWidget::updateCurPix( const std::vector<TQRect>& ol )
{
// we make curPix look like stillPix again...
for ( std::vector<TQRect>::const_iterator i = oldOverlay.begin(); i != oldOverlay.end(); ++i )
bitBlt( &curPix, i->topLeft(), &stillPix, *i );
for ( std::vector<TQRect>::const_iterator i = ol.begin(); i != ol.end(); ++i )
bitBlt( &curPix, i->topLeft(), &stillPix, *i );
// we add ol to oldOverlay, so that part of the widget will be
// updated too in updateWidget...
std::copy( ol.begin(), ol.end(), std::back_inserter( oldOverlay ) );
}
void KigWidget::recenterScreen()
{
msi.setShownRect( matchScreenShape( mpart->document().suggestedRect() ) );
}
Rect KigWidget::matchScreenShape( const Rect& r ) const
{
return r.matchShape( Rect::fromTQRect( rect() ) );
}
void KigWidget::slotZoomIn()
{
Rect nr = msi.shownRect();
Coordinate c = nr.center();
nr /= 2;
nr.setCenter( c );
KigCommand* cd =
new KigCommand( *mpart,
i18n( "Zoom In" ) );
cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
mpart->history()->addCommand( cd );
}
void KigWidget::slotZoomOut()
{
Rect nr = msi.shownRect();
Coordinate c = nr.center();
nr *= 2;
nr.setCenter( c );
// zooming in is undoable.. I know this isn't really correct,
// because the current view doesn't really belong to the document (
// althought KGeo and KSeg both save them along, iirc ). However,
// undoing a zoom or another operation affecting the window seems a
// bit too useful to not be available. Please try to convince me if
// you feel otherwise ;-)
KigCommand* cd =
new KigCommand( *mpart,
i18n( "Zoom Out" ) );
cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
mpart->history()->addCommand( cd );
}
void KigWidget::clearStillPix()
{
stillPix.fill(TQt::white);
oldOverlay.clear();
oldOverlay.push_back ( TQRect( TQPoint(0,0), size() ) );
}
void KigWidget::redrawScreen( const std::vector<ObjectHolder*>& selection, bool dos )
{
std::vector<ObjectHolder*> nonselection;
std::set<ObjectHolder*> objs = mpart->document().objectsSet();
std::set_difference( objs.begin(), objs.end(), selection.begin(), selection.end(),
std::back_inserter( nonselection ) );
// update the screen...
clearStillPix();
KigPainter p( msi, &stillPix, mpart->document() );
p.drawGrid( mpart->document().coordinateSystem(), mpart->document().grid(),
mpart->document().axes() );
p.drawObjects( selection, true );
p.drawObjects( nonselection, false );
updateCurPix( p.overlay() );
if ( dos ) updateEntireWidget();
}
const ScreenInfo& KigWidget::screenInfo() const
{
return msi;
}
const Rect KigWidget::showingRect() const
{
return msi.shownRect();
}
const Coordinate KigWidget::fromScreen( const TQPoint& p )
{
return msi.fromScreen( p );
}
double KigWidget::pixelWidth() const
{
return msi.pixelWidth();
}
const Rect KigWidget::fromScreen( const TQRect& r )
{
return msi.fromScreen( r );
}
void KigWidget::updateScrollBars()
{
mview->updateScrollBars();
}
KigView::KigView( KigPart* part,
bool fullscreen,
TQWidget* parent,
const char* name )
: TQWidget( parent, name ),
mlayout( 0 ), mrightscroll( 0 ), mbottomscroll( 0 ),
mupdatingscrollbars( false ),
mrealwidget( 0 ), mpart( part )
{
connect( part, TQ_SIGNAL( recenterScreen() ), this, TQ_SLOT( slotInternalRecenterScreen() ) );
mlayout = new TQGridLayout( this, 2, 2 );
mrightscroll = new TQScrollBar(TQt::Vertical, this, "Right Scrollbar" );
// TODO: make this configurable...
mrightscroll->setTracking( true );
connect( mrightscroll, TQ_SIGNAL( valueChanged( int ) ),
this, TQ_SLOT( slotRightScrollValueChanged( int ) ) );
connect( mrightscroll, TQ_SIGNAL( sliderReleased() ),
this, TQ_SLOT( updateScrollBars() ) );
mbottomscroll = new TQScrollBar(TQt::Horizontal, this, "Bottom Scrollbar" );
connect( mbottomscroll, TQ_SIGNAL( valueChanged( int ) ),
this, TQ_SLOT( slotBottomScrollValueChanged( int ) ) );
connect( mbottomscroll, TQ_SIGNAL( sliderReleased() ),
this, TQ_SLOT( updateScrollBars() ) );
mrealwidget = new KigWidget( part, this, this, "Kig Widget", fullscreen );
mlayout->addWidget( mbottomscroll, 1, 0 );
mlayout->addWidget( mrealwidget, 0, 0 );
mlayout->addWidget( mrightscroll, 0, 1 );
resize( sizeHint() );
mrealwidget->recenterScreen();
part->redrawScreen( mrealwidget );
updateScrollBars();
}
void KigView::updateScrollBars()
{
// we update the scrollbars to reflect the new "total size" of the
// document... The total size is calced in entireDocumentRect().
// ( it is calced as a rect that contains all the points in the
// document, and then enlarged a bit, and scaled to match the screen
// width/height ratio...
// What we do here is tell the scroll bars what they should show as
// their total size..
// see the doc of this variable in the header for this...
mupdatingscrollbars = true;
Rect er = mrealwidget->entireDocumentRect();
Rect sr = mrealwidget->screenInfo().shownRect();
// we define the total rect to be the smallest rect that contains
// both er and sr...
er |= sr;
// we need ints, not doubles, so since "pixelwidth == widgetcoord /
// internalcoord", we use "widgetcoord/pixelwidth", which would then
// equal "internalcoord", which has to be an int ( by definition.. )
// i know, i'm a freak to think about these sorts of things... ;)
double pw = mrealwidget->screenInfo().pixelWidth();
// what the scrollbars reflect is the bottom resp. the left side of
// the shown rect. This is why the maximum value is not er.top()
// (which would be the maximum value of the top of the shownRect),
// but er.top() - sr.height(), which is the maximum value the bottom of
// the shownRect can reach...
int rightmin = static_cast<int>( er.bottom() / pw );
int rightmax = static_cast<int>( ( er.top() - sr.height() ) / pw );
mrightscroll->setMinValue( rightmin );
mrightscroll->setMaxValue( rightmax );
mrightscroll->setLineStep( (int)( sr.height() / pw / 10 ) );
mrightscroll->setPageStep( (int)( sr.height() / pw / 1.2 ) );
// note that since TQt has a coordinate system with the lowest y
// values at the top, and we have it the other way around ( i know i
// shouldn't have done this.. :( ), we invert the value that the
// scrollbar shows. This is inverted again in
// slotRightScrollValueChanged()...
mrightscroll->setValue( (int) ( rightmin + ( rightmax - ( sr.bottom() / pw ) ) ) );
mbottomscroll->setMinValue( (int)( er.left() / pw ) );
mbottomscroll->setMaxValue( (int)( ( er.right() - sr.width() ) / pw ) );
mbottomscroll->setLineStep( (int)( sr.width() / pw / 10 ) );
mbottomscroll->setPageStep( (int)( sr.width() / pw / 1.2 ) );
mbottomscroll->setValue( (int)( sr.left() / pw ) );
mupdatingscrollbars = false;
}
Rect KigWidget::entireDocumentRect() const
{
return matchScreenShape( mpart->document().suggestedRect() );
}
void KigView::slotRightScrollValueChanged( int v )
{
if ( ! mupdatingscrollbars )
{
// we invert the inversion that was done in updateScrollBars() (
// check the documentation there..; )
v = mrightscroll->minValue() + ( mrightscroll->maxValue() - v );
double pw = mrealwidget->screenInfo().pixelWidth();
double nb = double( v ) * pw;
mrealwidget->scrollSetBottom( nb );
};
}
void KigView::slotBottomScrollValueChanged( int v )
{
if ( ! mupdatingscrollbars )
{
double pw = mrealwidget->screenInfo().pixelWidth();
double nl = double( v ) * pw;
mrealwidget->scrollSetLeft( nl );
};
}
void KigWidget::scrollSetBottom( double rhs )
{
Rect sr = msi.shownRect();
Coordinate bl = sr.bottomLeft();
bl.y = rhs;
sr.setBottomLeft( bl );
msi.setShownRect( sr );
mpart->redrawScreen( this );
}
void KigWidget::scrollSetLeft( double rhs )
{
Rect sr = msi.shownRect();
Coordinate bl = sr.bottomLeft();
bl.x = rhs;
sr.setBottomLeft( bl );
msi.setShownRect( sr );
mpart->redrawScreen( this );
}
const ScreenInfo& KigView::screenInfo() const
{
return mrealwidget->screenInfo();
}
KigView::~KigView()
{
}
KigWidget* KigView::realWidget() const
{
return mrealwidget;
}
const KigDocument& KigWidget::document() const
{
return mpart->document();
}
TQSize KigWidget::sizeHint() const
{
return TQSize( 630, 450 );
}
void KigWidget::wheelEvent( TQWheelEvent* e )
{
int delta = e->delta();
TQt::Orientation orient = e->orientation();
if ( orient == TQt::Vertical )
mview->scrollVertical( delta );
else
mview->scrollHorizontal( delta );
}
void KigView::scrollHorizontal( int delta )
{
if ( delta >= 0 )
for ( int i = 0; i < delta; i += 120 )
mbottomscroll->subtractLine();
else
for ( int i = 0; i >= delta; i -= 120 )
mbottomscroll->addLine();
}
void KigView::scrollVertical( int delta )
{
if ( delta >= 0 )
for ( int i = 0; i < delta; i += 120 )
mrightscroll->subtractLine();
else
for ( int i = 0; i >= delta; i -= 120 )
mrightscroll->addLine();
}
bool KigWidget::isFullScreen() const
{
return misfullscreen;
}
void KigView::slotZoomIn()
{
mrealwidget->slotZoomIn();
}
void KigView::slotZoomOut()
{
mrealwidget->slotZoomOut();
}
void KigWidget::slotRecenterScreen()
{
Rect nr = mpart->document().suggestedRect();
KigCommand* cd =
new KigCommand( *mpart,
i18n( "Recenter View" ) );
cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
mpart->history()->addCommand( cd );
}
void KigView::toggleFullScreen()
{
mrealwidget->setFullScreen( ! mrealwidget->isFullScreen() );
if ( mrealwidget->isFullScreen() )
topLevelWidget()->showFullScreen();
else
topLevelWidget()->showNormal();
}
void KigWidget::setFullScreen( bool f )
{
misfullscreen = f;
}
void KigWidget::zoomRect()
{
mpart->emitStatusBarText( i18n( "Select the rectangle that should be shown." ) );
DragRectMode d( *mpart, *this );
mpart->runMode( &d );
if ( ! d.cancelled() )
{
Rect nr = d.rect();
KigCommand* cd =
new KigCommand( *mpart,
i18n( "Change Shown Part of Screen" ) );
cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
mpart->history()->addCommand( cd );
};
mpart->redrawScreen( this );
updateScrollBars();
}
void KigView::zoomRect()
{
mrealwidget->zoomRect();
}
void KigWidget::setShowingRect( const Rect& r )
{
msi.setShownRect( r.matchShape( Rect::fromTQRect( rect() ) ) );
}
void KigView::slotRecenterScreen()
{
mrealwidget->slotRecenterScreen();
}
void KigView::slotInternalRecenterScreen()
{
mrealwidget->recenterScreen();
}
void KigWidget::zoomArea()
{
// mpart->emitStatusBarText( i18n( "Select the area that should be shown." ) );
Rect oldrect = showingRect();
Coordinate tl = oldrect.topLeft();
Coordinate br = oldrect.bottomRight();
bool ok = true;
KigInputDialog::getTwoCoordinates( i18n( "Select Zoom Area" ),
i18n( "Select the zoom area by entering the coordinates of "
"the upper left corner and the lower right corner." ) +
TQString::fromLatin1("<br>") +
mpart->document().coordinateSystem().coordinateFormatNoticeMarkup(),
this, &ok, mpart->document(), &tl, &br );
if ( ok )
{
Coordinate nc1( tl.x, br.y );
Coordinate nc2( br.x, tl.y );
Rect nr( nc1, nc2 );
KigCommand* cd = new KigCommand( *mpart, i18n( "Change Shown Part of Screen" ) );
cd->addTask( new KigViewShownRectChangeTask( *this, nr ) );
mpart->history()->addCommand( cd );
}
mpart->redrawScreen( this );
updateScrollBars();
}
void KigView::zoomArea()
{
mrealwidget->zoomArea();
}