From 039ac92480a09266146fc5b0c9ec67a32a2565ad Mon Sep 17 00:00:00 2001 From: saurabhb17 Date: Wed, 26 Feb 2020 16:04:40 +0530 Subject: Added secondary files --- common/view/view.cpp | 1086 ++++++++++++++++++++++++++++++++++++++ common/view/view_controls.cpp | 47 ++ common/view/view_group.cpp | 162 ++++++ common/view/view_item.cpp | 109 ++++ common/view/wx_view_controls.cpp | 491 +++++++++++++++++ 5 files changed, 1895 insertions(+) create mode 100644 common/view/view.cpp create mode 100644 common/view/view_controls.cpp create mode 100644 common/view/view_group.cpp create mode 100644 common/view/view_item.cpp create mode 100644 common/view/wx_view_controls.cpp (limited to 'common/view') diff --git a/common/view/view.cpp b/common/view/view.cpp new file mode 100644 index 0000000..8f1933c --- /dev/null +++ b/common/view/view.cpp @@ -0,0 +1,1086 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef PROFILE +#include +#endif /* PROFILE */ + +using namespace KIGFX; + +VIEW::VIEW( bool aIsDynamic ) : + m_enableOrderModifier( true ), + m_scale( 4.0 ), + m_minScale( 4.0 ), m_maxScale( 15000 ), + m_painter( NULL ), + m_gal( NULL ), + m_dynamic( aIsDynamic ) +{ + m_boundary.SetMaximum(); + m_needsUpdate.reserve( 32768 ); + + // Redraw everything at the beginning + MarkDirty(); + + // View uses layers to display EDA_ITEMs (item may be displayed on several layers, for example + // pad may be shown on pad, pad hole and solder paste layers). There are usual copper layers + // (eg. F.Cu, B.Cu, internal and so on) and layers for displaying objects such as texts, + // silkscreen, pads, vias, etc. + for( int i = 0; i < VIEW_MAX_LAYERS; i++ ) + AddLayer( i ); +} + + +VIEW::~VIEW() +{ + BOOST_FOREACH( LAYER_MAP::value_type& l, m_layers ) + delete l.second.items; +} + + +void VIEW::AddLayer( int aLayer, bool aDisplayOnly ) +{ + if( m_layers.find( aLayer ) == m_layers.end() ) + { + m_layers[aLayer] = VIEW_LAYER(); + m_layers[aLayer].id = aLayer; + m_layers[aLayer].items = new VIEW_RTREE(); + m_layers[aLayer].renderingOrder = aLayer; + m_layers[aLayer].visible = true; + m_layers[aLayer].displayOnly = aDisplayOnly; + m_layers[aLayer].target = TARGET_CACHED; + } + + sortLayers(); +} + + +void VIEW::Add( VIEW_ITEM* aItem ) +{ + int layers[VIEW_MAX_LAYERS], layers_count; + + aItem->ViewGetLayers( layers, layers_count ); + aItem->saveLayers( layers, layers_count ); + + if( m_dynamic ) + aItem->viewAssign( this ); + + for( int i = 0; i < layers_count; ++i ) + { + VIEW_LAYER& l = m_layers[layers[i]]; + l.items->Insert( aItem ); + MarkTargetDirty( l.target ); + } + + aItem->ViewUpdate( VIEW_ITEM::ALL ); +} + + +void VIEW::Remove( VIEW_ITEM* aItem ) +{ + if( m_dynamic ) + aItem->m_view = NULL; + + if( aItem->viewRequiredUpdate() != VIEW_ITEM::NONE ) // prevent from updating a removed item + { + std::vector::iterator item = std::find( m_needsUpdate.begin(), + m_needsUpdate.end(), aItem ); + + if( item != m_needsUpdate.end() ) + { + m_needsUpdate.erase( item ); + aItem->clearUpdateFlags(); + } + } + + int layers[VIEW::VIEW_MAX_LAYERS], layers_count; + aItem->getLayers( layers, layers_count ); + + for( int i = 0; i < layers_count; ++i ) + { + VIEW_LAYER& l = m_layers[layers[i]]; + l.items->Remove( aItem ); + MarkTargetDirty( l.target ); + + // Clear the GAL cache + int prevGroup = aItem->getGroup( layers[i] ); + + if( prevGroup >= 0 ) + m_gal->DeleteGroup( prevGroup ); + } + + aItem->deleteGroups(); +} + + +void VIEW::SetRequired( int aLayerId, int aRequiredId, bool aRequired ) +{ + wxASSERT( (unsigned) aLayerId < m_layers.size() ); + wxASSERT( (unsigned) aRequiredId < m_layers.size() ); + + if( aRequired ) + m_layers[aLayerId].requiredLayers.insert( aRequiredId ); + else + m_layers[aLayerId].requiredLayers.erase( aRequired ); +} + + +// stupid C++... python lambda would do this in one line +template +struct queryVisitor +{ + typedef typename Container::value_type item_type; + + queryVisitor( Container& aCont, int aLayer ) : + m_cont( aCont ), m_layer( aLayer ) + { + } + + bool operator()( VIEW_ITEM* aItem ) + { + if( aItem->ViewIsVisible() ) + m_cont.push_back( VIEW::LAYER_ITEM_PAIR( aItem, m_layer ) ); + + return true; + } + + Container& m_cont; + int m_layer; +}; + + +int VIEW::Query( const BOX2I& aRect, std::vector& aResult ) const +{ + if( m_orderedLayers.empty() ) + return 0; + + std::vector::const_reverse_iterator i; + + // execute queries in reverse direction, so that items that are on the top of + // the rendering stack are returned first. + for( i = m_orderedLayers.rbegin(); i != m_orderedLayers.rend(); ++i ) + { + // ignore layers that do not contain actual items (i.e. the selection box, menus, floats) + if( ( *i )->displayOnly ) + continue; + + queryVisitor > visitor( aResult, ( *i )->id ); + ( *i )->items->Query( aRect, visitor ); + } + + return aResult.size(); +} + + +VECTOR2D VIEW::ToWorld( const VECTOR2D& aCoord, bool aAbsolute ) const +{ + const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix(); + + if( aAbsolute ) + return VECTOR2D( matrix * aCoord ); + else + return VECTOR2D( matrix.GetScale().x * aCoord.x, matrix.GetScale().y * aCoord.y ); +} + + +double VIEW::ToWorld( double aSize ) const +{ + const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix(); + + return matrix.GetScale().x * aSize; +} + + +VECTOR2D VIEW::ToScreen( const VECTOR2D& aCoord, bool aAbsolute ) const +{ + const MATRIX3x3D& matrix = m_gal->GetWorldScreenMatrix(); + + if( aAbsolute ) + return VECTOR2D( matrix * aCoord ); + else + return VECTOR2D( matrix.GetScale().x * aCoord.x, matrix.GetScale().y * aCoord.y ); +} + + +double VIEW::ToScreen( double aSize ) const +{ + const MATRIX3x3D& matrix = m_gal->GetWorldScreenMatrix(); + + return matrix.GetScale().x * aSize; +} + + +void VIEW::CopySettings( const VIEW* aOtherView ) +{ + wxASSERT_MSG( false, wxT( "This is not implemented" ) ); +} + + +void VIEW::SetGAL( GAL* aGal ) +{ + m_gal = aGal; + + // clear group numbers, so everything is going to be recached + clearGroupCache(); + + // every target has to be refreshed + MarkDirty(); + + // force the new GAL to display the current viewport. + SetCenter( m_center ); + SetScale( m_scale ); +} + + +BOX2D VIEW::GetViewport() const +{ + BOX2D rect; + VECTOR2D screenSize = m_gal->GetScreenPixelSize(); + + rect.SetOrigin( ToWorld( VECTOR2D( 0, 0 ) ) ); + rect.SetEnd( ToWorld( screenSize ) ); + + return rect.Normalize(); +} + + +void VIEW::SetViewport( const BOX2D& aViewport ) +{ + VECTOR2D ssize = ToWorld( m_gal->GetScreenPixelSize(), false ); + + wxASSERT( ssize.x > 0 && ssize.y > 0 ); + + VECTOR2D centre = aViewport.Centre(); + VECTOR2D vsize = aViewport.GetSize(); + double zoom = 1.0 / std::max( fabs( vsize.x / ssize.x ), fabs( vsize.y / ssize.y ) ); + + SetCenter( centre ); + SetScale( GetScale() * zoom ); +} + + +void VIEW::SetMirror( bool aMirrorX, bool aMirrorY ) +{ + m_gal->SetFlip( aMirrorX, aMirrorY ); +} + + +void VIEW::SetScale( double aScale, const VECTOR2D& aAnchor ) +{ + VECTOR2D a = ToScreen( aAnchor ); + + if( aScale < m_minScale ) + m_scale = m_minScale; + else if( aScale > m_maxScale ) + m_scale = m_maxScale; + else + m_scale = aScale; + + m_gal->SetZoomFactor( m_scale ); + m_gal->ComputeWorldScreenMatrix(); + + VECTOR2D delta = ToWorld( a ) - aAnchor; + + SetCenter( m_center - delta ); + + // Redraw everything after the viewport has changed + MarkDirty(); +} + + +void VIEW::SetCenter( const VECTOR2D& aCenter ) +{ + m_center = aCenter; + + if( !m_boundary.Contains( aCenter ) ) + { + if( m_center.x < m_boundary.GetLeft() ) + m_center.x = m_boundary.GetLeft(); + else if( aCenter.x > m_boundary.GetRight() ) + m_center.x = m_boundary.GetRight(); + + if( m_center.y < m_boundary.GetTop() ) + m_center.y = m_boundary.GetTop(); + else if( m_center.y > m_boundary.GetBottom() ) + m_center.y = m_boundary.GetBottom(); + } + + m_gal->SetLookAtPoint( m_center ); + m_gal->ComputeWorldScreenMatrix(); + + // Redraw everything after the viewport has changed + MarkDirty(); +} + + +void VIEW::SetLayerOrder( int aLayer, int aRenderingOrder ) +{ + m_layers[aLayer].renderingOrder = aRenderingOrder; + + sortLayers(); +} + + +int VIEW::GetLayerOrder( int aLayer ) const +{ + return m_layers.at( aLayer ).renderingOrder; +} + + +void VIEW::SortLayers( int aLayers[], int& aCount ) const +{ + int maxLay, maxOrd, maxIdx; + + for( int i = 0; i < aCount; ++i ) + { + maxLay = aLayers[i]; + maxOrd = GetLayerOrder( maxLay ); + maxIdx = i; + + // Look for the max element in the range (j..aCount) + for( int j = i; j < aCount; ++j ) + { + if( maxOrd < GetLayerOrder( aLayers[j] ) ) + { + maxLay = aLayers[j]; + maxOrd = GetLayerOrder( maxLay ); + maxIdx = j; + } + } + + // Swap elements + aLayers[maxIdx] = aLayers[i]; + aLayers[i] = maxLay; + } +} + + +struct VIEW::updateItemsColor +{ + updateItemsColor( int aLayer, PAINTER* aPainter, GAL* aGal ) : + layer( aLayer ), painter( aPainter ), gal( aGal ) + { + } + + bool operator()( VIEW_ITEM* aItem ) + { + // Obtain the color that should be used for coloring the item + const COLOR4D color = painter->GetSettings()->GetColor( aItem, layer ); + int group = aItem->getGroup( layer ); + + if( group >= 0 ) + gal->ChangeGroupColor( group, color ); + + return true; + } + + int layer; + PAINTER* painter; + GAL* gal; +}; + + +void VIEW::UpdateLayerColor( int aLayer ) +{ + // There is no point in updating non-cached layers + if( !IsCached( aLayer ) ) + return; + + BOX2I r; + + r.SetMaximum(); + + updateItemsColor visitor( aLayer, m_painter, m_gal ); + m_layers[aLayer].items->Query( r, visitor ); + MarkTargetDirty( m_layers[aLayer].target ); +} + + +void VIEW::UpdateAllLayersColor() +{ + BOX2I r; + + r.SetMaximum(); + + for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) + { + VIEW_LAYER* l = &( ( *i ).second ); + + // There is no point in updating non-cached layers + if( !IsCached( l->id ) ) + continue; + + updateItemsColor visitor( l->id, m_painter, m_gal ); + l->items->Query( r, visitor ); + } + + MarkDirty(); +} + + +struct VIEW::changeItemsDepth +{ + changeItemsDepth( int aLayer, int aDepth, GAL* aGal ) : + layer( aLayer ), depth( aDepth ), gal( aGal ) + { + } + + bool operator()( VIEW_ITEM* aItem ) + { + int group = aItem->getGroup( layer ); + + if( group >= 0 ) + gal->ChangeGroupDepth( group, depth ); + + return true; + } + + int layer, depth; + GAL* gal; +}; + + +void VIEW::ChangeLayerDepth( int aLayer, int aDepth ) +{ + // There is no point in updating non-cached layers + if( !IsCached( aLayer ) ) + return; + + BOX2I r; + + r.SetMaximum(); + + changeItemsDepth visitor( aLayer, aDepth, m_gal ); + m_layers[aLayer].items->Query( r, visitor ); + MarkTargetDirty( m_layers[aLayer].target ); +} + + +int VIEW::GetTopLayer() const +{ + if( m_topLayers.size() == 0 ) + return 0; + + return *m_topLayers.begin(); +} + + +void VIEW::SetTopLayer( int aLayer, bool aEnabled ) +{ + if( aEnabled ) + { + if( m_topLayers.count( aLayer ) == 1 ) + return; + + m_topLayers.insert( aLayer ); + + // Move the layer closer to front + if( m_enableOrderModifier ) + m_layers[aLayer].renderingOrder += TOP_LAYER_MODIFIER; + } + else + { + if( m_topLayers.count( aLayer ) == 0 ) + return; + + m_topLayers.erase( aLayer ); + + // Restore the previous rendering order + if( m_enableOrderModifier ) + m_layers[aLayer].renderingOrder -= TOP_LAYER_MODIFIER; + } +} + + +void VIEW::EnableTopLayer( bool aEnable ) +{ + if( aEnable == m_enableOrderModifier ) + return; + + m_enableOrderModifier = aEnable; + + std::set::iterator it; + + if( aEnable ) + { + for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it ) + m_layers[*it].renderingOrder += TOP_LAYER_MODIFIER; + } + else + { + for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it ) + m_layers[*it].renderingOrder -= TOP_LAYER_MODIFIER; + } + + UpdateAllLayersOrder(); + UpdateAllLayersColor(); +} + + +void VIEW::ClearTopLayers() +{ + std::set::iterator it; + + if( m_enableOrderModifier ) + { + // Restore the previous rendering order for layers that were marked as top + for( it = m_topLayers.begin(); it != m_topLayers.end(); ++it ) + m_layers[*it].renderingOrder -= TOP_LAYER_MODIFIER; + } + + m_topLayers.clear(); +} + + +void VIEW::UpdateAllLayersOrder() +{ + sortLayers(); + + BOOST_FOREACH( LAYER_MAP::value_type& l, m_layers ) + { + ChangeLayerDepth( l.first, l.second.renderingOrder ); + } + + MarkDirty(); +} + + +struct VIEW::drawItem +{ + drawItem( VIEW* aView, int aLayer ) : + view( aView ), layer( aLayer ) + { + } + + bool operator()( VIEW_ITEM* aItem ) + { + // Conditions that have te be fulfilled for an item to be drawn + bool drawCondition = aItem->isRenderable() && + aItem->ViewGetLOD( layer ) < view->m_scale; + if( !drawCondition ) + return true; + + view->draw( aItem, layer ); + + return true; + } + + VIEW* view; + int layer, layers[VIEW_MAX_LAYERS]; +}; + + +void VIEW::redrawRect( const BOX2I& aRect ) +{ + BOOST_FOREACH( VIEW_LAYER* l, m_orderedLayers ) + { + if( l->visible && IsTargetDirty( l->target ) && areRequiredLayersEnabled( l->id ) ) + { + drawItem drawFunc( this, l->id ); + + m_gal->SetTarget( l->target ); + m_gal->SetLayerDepth( l->renderingOrder ); + l->items->Query( aRect, drawFunc ); + } + } +} + + +void VIEW::draw( VIEW_ITEM* aItem, int aLayer, bool aImmediate ) +{ + if( IsCached( aLayer ) && !aImmediate ) + { + // Draw using cached information or create one + int group = aItem->getGroup( aLayer ); + + if( group >= 0 ) + { + m_gal->DrawGroup( group ); + } + else + { + group = m_gal->BeginGroup(); + aItem->setGroup( aLayer, group ); + + if( !m_painter->Draw( aItem, aLayer ) ) + aItem->ViewDraw( aLayer, m_gal ); // Alternative drawing method + + m_gal->EndGroup(); + } + } + else + { + // Immediate mode + if( !m_painter->Draw( aItem, aLayer ) ) + aItem->ViewDraw( aLayer, m_gal ); // Alternative drawing method + } +} + + +void VIEW::draw( VIEW_ITEM* aItem, bool aImmediate ) +{ + int layers[VIEW_MAX_LAYERS], layers_count; + + aItem->ViewGetLayers( layers, layers_count ); + + // Sorting is needed for drawing order dependent GALs (like Cairo) + SortLayers( layers, layers_count ); + + for( int i = 0; i < layers_count; ++i ) + { + m_gal->SetLayerDepth( m_layers.at( layers[i] ).renderingOrder ); + draw( aItem, layers[i], aImmediate ); + } +} + + +void VIEW::draw( VIEW_GROUP* aGroup, bool aImmediate ) +{ + std::set::const_iterator it; + + for( it = aGroup->Begin(); it != aGroup->End(); ++it ) + draw( *it, aImmediate ); +} + + +struct VIEW::unlinkItem +{ + bool operator()( VIEW_ITEM* aItem ) + { + aItem->m_view = NULL; + + return true; + } +}; + + +struct VIEW::recacheItem +{ + recacheItem( VIEW* aView, GAL* aGal, int aLayer, bool aImmediately ) : + view( aView ), gal( aGal ), layer( aLayer ), immediately( aImmediately ) + { + } + + bool operator()( VIEW_ITEM* aItem ) + { + // Remove previously cached group + int group = aItem->getGroup( layer ); + + if( group >= 0 ) + gal->DeleteGroup( group ); + + if( immediately ) + { + group = gal->BeginGroup(); + aItem->setGroup( layer, group ); + + if( !view->m_painter->Draw( aItem, layer ) ) + aItem->ViewDraw( layer, gal ); // Alternative drawing method + + gal->EndGroup(); + } + else + { + aItem->ViewUpdate( VIEW_ITEM::ALL ); + aItem->setGroup( layer, -1 ); + } + + return true; + } + + VIEW* view; + GAL* gal; + int layer; + bool immediately; +}; + + +void VIEW::Clear() +{ + BOX2I r; + + r.SetMaximum(); + + BOOST_FOREACH( VIEW_ITEM* item, m_needsUpdate ) + item->clearUpdateFlags(); + + m_needsUpdate.clear(); + + for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) + { + VIEW_LAYER* l = &( ( *i ).second ); + unlinkItem v; + + if( m_dynamic ) + l->items->Query( r, v ); + + l->items->RemoveAll(); + } + + m_gal->ClearCache(); +} + + +void VIEW::ClearTargets() +{ + if( IsTargetDirty( TARGET_CACHED ) || IsTargetDirty( TARGET_NONCACHED ) ) + { + // TARGET_CACHED and TARGET_NONCACHED have to be redrawn together, as they contain + // layers that rely on each other (eg. netnames are noncached, but tracks - are cached) + m_gal->ClearTarget( TARGET_NONCACHED ); + m_gal->ClearTarget( TARGET_CACHED ); + + MarkDirty(); + } + + if( IsTargetDirty( TARGET_OVERLAY ) ) + { + m_gal->ClearTarget( TARGET_OVERLAY ); + } +} + + +void VIEW::Redraw() +{ +#ifdef PROFILE + prof_counter totalRealTime; + prof_start( &totalRealTime ); +#endif /* PROFILE */ + + VECTOR2D screenSize = m_gal->GetScreenPixelSize(); + BOX2I rect( ToWorld( VECTOR2D( 0, 0 ) ), + ToWorld( screenSize ) - ToWorld( VECTOR2D( 0, 0 ) ) ); + rect.Normalize(); + + redrawRect( rect ); + + // All targets were redrawn, so nothing is dirty + markTargetClean( TARGET_CACHED ); + markTargetClean( TARGET_NONCACHED ); + markTargetClean( TARGET_OVERLAY ); + +#ifdef PROFILE + prof_end( &totalRealTime ); + + wxLogDebug( wxT( "Redraw: %.1f ms" ), totalRealTime.msecs() ); +#endif /* PROFILE */ +} + + +const VECTOR2I& VIEW::GetScreenPixelSize() const +{ + return m_gal->GetScreenPixelSize(); +} + + +struct VIEW::clearLayerCache +{ + clearLayerCache( VIEW* aView ) : + view( aView ) + { + } + + bool operator()( VIEW_ITEM* aItem ) + { + aItem->deleteGroups(); + + return true; + } + + VIEW* view; +}; + + +void VIEW::clearGroupCache() +{ + BOX2I r; + + r.SetMaximum(); + clearLayerCache visitor( this ); + + for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) + { + VIEW_LAYER* l = &( ( *i ).second ); + l->items->Query( r, visitor ); + } +} + + +void VIEW::invalidateItem( VIEW_ITEM* aItem, int aUpdateFlags ) +{ + // updateLayers updates geometry too, so we do not have to update both of them at the same time + if( aUpdateFlags & VIEW_ITEM::LAYERS ) + updateLayers( aItem ); + else if( aUpdateFlags & VIEW_ITEM::GEOMETRY ) + updateBbox( aItem ); + + int layers[VIEW_MAX_LAYERS], layers_count; + aItem->ViewGetLayers( layers, layers_count ); + + // Iterate through layers used by the item and recache it immediately + for( int i = 0; i < layers_count; ++i ) + { + int layerId = layers[i]; + + if( IsCached( layerId ) ) + { + if( aUpdateFlags & ( VIEW_ITEM::GEOMETRY | VIEW_ITEM::LAYERS ) ) + updateItemGeometry( aItem, layerId ); + else if( aUpdateFlags & VIEW_ITEM::COLOR ) + updateItemColor( aItem, layerId ); + } + + // Mark those layers as dirty, so the VIEW will be refreshed + MarkTargetDirty( m_layers[layerId].target ); + } + + aItem->clearUpdateFlags(); +} + + +void VIEW::sortLayers() +{ + int n = 0; + + m_orderedLayers.resize( m_layers.size() ); + + for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) + m_orderedLayers[n++] = &i->second; + + sort( m_orderedLayers.begin(), m_orderedLayers.end(), compareRenderingOrder ); + + MarkDirty(); +} + + +void VIEW::updateItemColor( VIEW_ITEM* aItem, int aLayer ) +{ + wxASSERT( (unsigned) aLayer < m_layers.size() ); + wxASSERT( IsCached( aLayer ) ); + + // Obtain the color that should be used for coloring the item on the specific layerId + const COLOR4D color = m_painter->GetSettings()->GetColor( aItem, aLayer ); + int group = aItem->getGroup( aLayer ); + + // Change the color, only if it has group assigned + if( group >= 0 ) + m_gal->ChangeGroupColor( group, color ); +} + + +void VIEW::updateItemGeometry( VIEW_ITEM* aItem, int aLayer ) +{ + wxASSERT( (unsigned) aLayer < m_layers.size() ); + wxASSERT( IsCached( aLayer ) ); + + VIEW_LAYER& l = m_layers.at( aLayer ); + + m_gal->SetTarget( l.target ); + m_gal->SetLayerDepth( l.renderingOrder ); + + // Redraw the item from scratch + int group = aItem->getGroup( aLayer ); + + if( group >= 0 ) + m_gal->DeleteGroup( group ); + + group = m_gal->BeginGroup(); + aItem->setGroup( aLayer, group ); + + if( !m_painter->Draw( static_cast( aItem ), aLayer ) ) + aItem->ViewDraw( aLayer, m_gal ); // Alternative drawing method + + m_gal->EndGroup(); +} + + +void VIEW::updateBbox( VIEW_ITEM* aItem ) +{ + int layers[VIEW_MAX_LAYERS], layers_count; + + aItem->ViewGetLayers( layers, layers_count ); + + for( int i = 0; i < layers_count; ++i ) + { + VIEW_LAYER& l = m_layers[layers[i]]; + l.items->Remove( aItem ); + l.items->Insert( aItem ); + MarkTargetDirty( l.target ); + } +} + + +void VIEW::updateLayers( VIEW_ITEM* aItem ) +{ + int layers[VIEW_MAX_LAYERS], layers_count; + + // Remove the item from previous layer set + aItem->getLayers( layers, layers_count ); + + for( int i = 0; i < layers_count; ++i ) + { + VIEW_LAYER& l = m_layers[layers[i]]; + l.items->Remove( aItem ); + MarkTargetDirty( l.target ); + + if( IsCached( l.id ) ) + { + // Redraw the item from scratch + int prevGroup = aItem->getGroup( layers[i] ); + + if( prevGroup >= 0 ) + { + m_gal->DeleteGroup( prevGroup ); + aItem->setGroup( l.id, -1 ); + } + } + } + + // Add the item to new layer set + aItem->ViewGetLayers( layers, layers_count ); + aItem->saveLayers( layers, layers_count ); + + for( int i = 0; i < layers_count; i++ ) + { + VIEW_LAYER& l = m_layers[layers[i]]; + l.items->Insert( aItem ); + MarkTargetDirty( l.target ); + } +} + + +bool VIEW::areRequiredLayersEnabled( int aLayerId ) const +{ + wxASSERT( (unsigned) aLayerId < m_layers.size() ); + + std::set::iterator it, it_end; + + for( it = m_layers.at( aLayerId ).requiredLayers.begin(), + it_end = m_layers.at( aLayerId ).requiredLayers.end(); it != it_end; ++it ) + { + // That is enough if just one layer is not enabled + if( !m_layers.at( *it ).visible || !areRequiredLayersEnabled( *it ) ) + return false; + } + + return true; +} + + +void VIEW::RecacheAllItems( bool aImmediately ) +{ + BOX2I r; + + r.SetMaximum(); + +#ifdef PROFILE + prof_counter totalRealTime; + prof_start( &totalRealTime ); +#endif /* PROFILE */ + + for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i ) + { + VIEW_LAYER* l = &( ( *i ).second ); + + if( IsCached( l->id ) ) + { + m_gal->SetTarget( l->target ); + m_gal->SetLayerDepth( l->renderingOrder ); + recacheItem visitor( this, m_gal, l->id, aImmediately ); + l->items->Query( r, visitor ); + MarkTargetDirty( l->target ); + } + } + +#ifdef PROFILE + prof_end( &totalRealTime ); + + wxLogDebug( wxT( "RecacheAllItems::immediately: %u %.1f ms" ), + aImmediately, totalRealTime.msecs() ); +#endif /* PROFILE */ +} + + +void VIEW::UpdateItems() +{ + // Update items that need this + BOOST_FOREACH( VIEW_ITEM* item, m_needsUpdate ) + { + assert( item->viewRequiredUpdate() != VIEW_ITEM::NONE ); + + invalidateItem( item, item->viewRequiredUpdate() ); + } + + m_needsUpdate.clear(); +} + + +struct VIEW::extentsVisitor +{ + BOX2I extents; + bool first; + + extentsVisitor() + { + first = true; + } + + bool operator()( VIEW_ITEM* aItem ) + { + if( first ) + extents = aItem->ViewBBox(); + else + extents.Merge ( aItem->ViewBBox() ); + return false; + } +}; + + +const BOX2I VIEW::CalculateExtents() +{ + extentsVisitor v; + BOX2I fullScene; + fullScene.SetMaximum(); + + BOOST_FOREACH( VIEW_LAYER* l, m_orderedLayers ) + { + l->items->Query( fullScene, v ); + } + + return v.extents; +} + + +const int VIEW::TOP_LAYER_MODIFIER = -VIEW_MAX_LAYERS; diff --git a/common/view/view_controls.cpp b/common/view/view_controls.cpp new file mode 100644 index 0000000..850cf0b --- /dev/null +++ b/common/view/view_controls.cpp @@ -0,0 +1,47 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2012 Torsten Hueter, torstenhtr gmx.de + * Copyright (C) 2013-2015 CERN + * @author Tomasz Wlostowski + * @author Maciej Suminski + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include + +using namespace KIGFX; + +void VIEW_CONTROLS::ShowCursor( bool aEnabled ) +{ + m_view->GetGAL()->SetCursorEnabled( aEnabled ); +} + + +void VIEW_CONTROLS::Reset() +{ + SetSnapping( false ); + SetAutoPan( false ); + ForceCursorPosition( false ); + ShowCursor( false ); + CaptureCursor( false ); + SetGrabMouse( false ); +} diff --git a/common/view/view_group.cpp b/common/view/view_group.cpp new file mode 100644 index 0000000..1459954 --- /dev/null +++ b/common/view/view_group.cpp @@ -0,0 +1,162 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Maciej Suminski + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * + */ + +/** + * @file view_group.cpp + * @brief VIEW_GROUP extends VIEW_ITEM by possibility of grouping items into a single object. + * VIEW_GROUP does not take over ownership of the held items. The main purpose of this class is + * to group items and draw them on a single layer (in particular the overlay). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KIGFX; + +VIEW_GROUP::VIEW_GROUP( VIEW* aView ) : + m_layer( ITEM_GAL_LAYER( GP_OVERLAY ) ) +{ + m_view = aView; +} + + +VIEW_GROUP::~VIEW_GROUP() +{ +} + + +void VIEW_GROUP::Add( VIEW_ITEM* aItem ) +{ + m_items.insert( aItem ); +} + + +void VIEW_GROUP::Remove( VIEW_ITEM* aItem ) +{ + m_items.erase( aItem ); +} + + +void VIEW_GROUP::Clear() +{ + m_items.clear(); +} + + +unsigned int VIEW_GROUP::GetSize() const +{ + return m_items.size(); +} + + +const BOX2I VIEW_GROUP::ViewBBox() const +{ + BOX2I maxBox; + + maxBox.SetMaximum(); + return maxBox; +} + + +void VIEW_GROUP::ViewDraw( int aLayer, GAL* aGal ) const +{ + PAINTER* painter = m_view->GetPainter(); + + // Draw all items immediately (without caching) + BOOST_FOREACH( VIEW_ITEM* item, m_items ) + { + aGal->PushDepth(); + + int layers[VIEW::VIEW_MAX_LAYERS], layers_count; + item->ViewGetLayers( layers, layers_count ); + m_view->SortLayers( layers, layers_count ); + + for( int i = 0; i < layers_count; i++ ) + { + if( m_view->IsCached( layers[i] ) && m_view->IsLayerVisible( layers[i] ) ) + { + aGal->AdvanceDepth(); + + if( !painter->Draw( item, layers[i] ) ) + item->ViewDraw( layers[i], aGal ); // Alternative drawing method + } + } + + aGal->PopDepth(); + } +} + + +void VIEW_GROUP::ViewGetLayers( int aLayers[], int& aCount ) const +{ + // Everything is displayed on a single layer + aLayers[0] = m_layer; + aCount = 1; +} + + +void VIEW_GROUP::FreeItems() +{ + BOOST_FOREACH( VIEW_ITEM* item, m_items ) + { + delete item; + } + m_items.clear(); +} + + +void VIEW_GROUP::ItemsSetVisibility( bool aVisible ) +{ + std::set::const_iterator it, it_end; + + for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it ) + (*it)->ViewSetVisible( aVisible ); +} + + +void VIEW_GROUP::ItemsViewUpdate( VIEW_ITEM::VIEW_UPDATE_FLAGS aFlags ) +{ + std::set::const_iterator it, it_end; + + for( it = m_items.begin(), it_end = m_items.end(); it != it_end; ++it ) + (*it)->ViewUpdate( aFlags ); +} + + +void VIEW_GROUP::updateBbox() +{ + // Save the used VIEW, as it used nulled during Remove() + VIEW* view = m_view; + + // Reinsert the group, so the bounding box can be updated + view->Remove( this ); + view->Add( this ); +} diff --git a/common/view/view_item.cpp b/common/view/view_item.cpp new file mode 100644 index 0000000..f1713f3 --- /dev/null +++ b/common/view/view_item.cpp @@ -0,0 +1,109 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 CERN + * @author Tomasz Wlostowski + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include + +using namespace KIGFX; + +void VIEW_ITEM::ViewRelease() +{ + if( m_view && m_view->IsDynamic() ) + m_view->Remove( this ); +} + + +void VIEW_ITEM::getLayers( int* aLayers, int& aCount ) const +{ + int* layersPtr = aLayers; + + for( unsigned int i = 0; i < m_layers.size(); ++i ) + { + if( m_layers[i] ) + *layersPtr++ = i; + } + + aCount = m_layers.count(); +} + + +int VIEW_ITEM::getGroup( int aLayer ) const +{ + for( int i = 0; i < m_groupsSize; ++i ) + { + if( m_groups[i].first == aLayer ) + return m_groups[i].second; + } + + return -1; +} + + +std::vector VIEW_ITEM::getAllGroups() const +{ + std::vector groups( m_groupsSize ); + + for( int i = 0; i < m_groupsSize; ++i ) + { + groups[i] = m_groups[i].second; + } + + return groups; +} + + +void VIEW_ITEM::setGroup( int aLayer, int aId ) +{ + // Look if there is already an entry for the layer + for( int i = 0; i < m_groupsSize; ++i ) + { + if( m_groups[i].first == aLayer ) + { + m_groups[i].second = aId; + return; + } + } + + // If there was no entry for the given layer - create one + GroupPair* newGroups = new GroupPair[m_groupsSize + 1]; + + if( m_groupsSize > 0 ) + { + std::copy( m_groups, m_groups + m_groupsSize, newGroups ); + delete[] m_groups; + } + + m_groups = newGroups; + newGroups[m_groupsSize++] = GroupPair( aLayer, aId ); +} + + +void VIEW_ITEM::deleteGroups() +{ + delete[] m_groups; + m_groups = NULL; + m_groupsSize = 0; +} diff --git a/common/view/wx_view_controls.cpp b/common/view/wx_view_controls.cpp new file mode 100644 index 0000000..ac6c778 --- /dev/null +++ b/common/view/wx_view_controls.cpp @@ -0,0 +1,491 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2012 Torsten Hueter, torstenhtr gmx.de + * Copyright (C) 2013-2015 CERN + * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors. + * + * @author Tomasz Wlostowski + * @author Maciej Suminski + * + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include +#include +#include + +using namespace KIGFX; + +const wxEventType WX_VIEW_CONTROLS::EVT_REFRESH_MOUSE = wxNewEventType(); + +WX_VIEW_CONTROLS::WX_VIEW_CONTROLS( VIEW* aView, wxScrolledCanvas* aParentPanel ) : + VIEW_CONTROLS( aView ), m_state( IDLE ), m_parentPanel( aParentPanel ), m_scrollScale( 1.0, 1.0 ) +{ + m_parentPanel->Connect( wxEVT_MOTION, + wxMouseEventHandler( WX_VIEW_CONTROLS::onMotion ), NULL, this ); +#ifdef USE_OSX_MAGNIFY_EVENT + m_parentPanel->Connect( wxEVT_MAGNIFY, + wxMouseEventHandler( WX_VIEW_CONTROLS::onMagnify ), NULL, this ); +#endif + m_parentPanel->Connect( wxEVT_MOUSEWHEEL, + wxMouseEventHandler( WX_VIEW_CONTROLS::onWheel ), NULL, this ); + m_parentPanel->Connect( wxEVT_MIDDLE_UP, + wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), NULL, this ); + m_parentPanel->Connect( wxEVT_MIDDLE_DOWN, + wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), NULL, this ); + m_parentPanel->Connect( wxEVT_LEFT_UP, + wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), NULL, this ); + m_parentPanel->Connect( wxEVT_LEFT_DOWN, + wxMouseEventHandler( WX_VIEW_CONTROLS::onButton ), NULL, this ); +#if defined _WIN32 || defined _WIN64 + m_parentPanel->Connect( wxEVT_ENTER_WINDOW, + wxMouseEventHandler( WX_VIEW_CONTROLS::onEnter ), NULL, this ); +#endif + m_parentPanel->Connect( wxEVT_LEAVE_WINDOW, + wxMouseEventHandler( WX_VIEW_CONTROLS::onLeave ), NULL, this ); + m_parentPanel->Connect( wxEVT_SCROLLWIN_THUMBTRACK, + wxScrollWinEventHandler( WX_VIEW_CONTROLS::onScroll ), NULL, this ); + + m_panTimer.SetOwner( this ); + this->Connect( wxEVT_TIMER, + wxTimerEventHandler( WX_VIEW_CONTROLS::onTimer ), NULL, this ); +} + + +void WX_VIEW_CONTROLS::onMotion( wxMouseEvent& aEvent ) +{ + bool isAutoPanning = false; + + if( m_autoPanEnabled ) + isAutoPanning = handleAutoPanning( aEvent ); + + if( !isAutoPanning && aEvent.Dragging() ) + { + if( m_state == DRAG_PANNING ) + { + VECTOR2D d = m_dragStartPoint - VECTOR2D( aEvent.GetX(), aEvent.GetY() ); + VECTOR2D delta = m_view->ToWorld( d, false ); + + m_view->SetCenter( m_lookStartPoint + delta ); + aEvent.StopPropagation(); + } + } + + aEvent.Skip(); +} + + +void WX_VIEW_CONTROLS::onWheel( wxMouseEvent& aEvent ) +{ + const double wheelPanSpeed = 0.001; + + if( aEvent.ControlDown() || aEvent.ShiftDown() || m_enableMousewheelPan ) + { + // Scrolling + VECTOR2D scrollVec = m_view->ToWorld( m_view->GetScreenPixelSize(), false ) * + ( (double) aEvent.GetWheelRotation() * wheelPanSpeed ); + int axis = aEvent.GetWheelAxis(); + double scrollX = 0.0; + double scrollY = 0.0; + + if ( m_enableMousewheelPan ) + { + if ( axis == wxMOUSE_WHEEL_HORIZONTAL ) + scrollX = scrollVec.x; + else + scrollY = -scrollVec.y; + } + else + { + if ( aEvent.ControlDown() ) + scrollX = -scrollVec.x; + else + scrollY = -scrollVec.y; + } + + VECTOR2D delta( scrollX, scrollY ); + + m_view->SetCenter( m_view->GetCenter() + delta ); + } + else + { + // Zooming + wxLongLong timeStamp = wxGetLocalTimeMillis(); + double timeDiff = timeStamp.ToDouble() - m_timeStamp.ToDouble(); + int rotation = aEvent.GetWheelRotation(); + double zoomScale; + +#ifdef __WXMAC__ + // The following is to support Apple pointer devices (MagicMouse & + // Macbook touchpad), which send events more frequently, but with smaller + // wheel rotation. + // + // It should not break other platforms, but I prefer to be safe than + // sorry. If you find a device that behaves in the same way on another + // platform, feel free to remove #ifdef directives. + if( timeDiff > 0 && timeDiff < 100 && std::abs( rotation ) < 20 ) + { + aEvent.Skip(); + return; + } +#endif + + m_timeStamp = timeStamp; + + // Set scaling speed depending on scroll wheel event interval + if( timeDiff < 500 && timeDiff > 0 ) + { + zoomScale = 2.05 - timeDiff / 500; + + if( rotation < 0 ) + zoomScale = 1.0 / zoomScale; + } + else + { + zoomScale = ( rotation > 0 ) ? 1.05 : 0.95; + } + + if( IsCursorWarpingEnabled() ) + { + CenterOnCursor(); + m_view->SetScale( m_view->GetScale() * zoomScale ); + } + else + { + VECTOR2D anchor = m_view->ToWorld( VECTOR2D( aEvent.GetX(), aEvent.GetY() ) ); + m_view->SetScale( m_view->GetScale() * zoomScale, anchor ); + } + } + + aEvent.Skip(); +} + + +#ifdef USE_OSX_MAGNIFY_EVENT +void WX_VIEW_CONTROLS::onMagnify( wxMouseEvent& aEvent ) +{ + // Scale based on the magnification from our underlying magnification event. + VECTOR2D anchor = m_view->ToWorld( VECTOR2D( aEvent.GetX(), aEvent.GetY() ) ); + m_view->SetScale( m_view->GetScale() * ( aEvent.GetMagnification() + 1.0f ), anchor ); + + aEvent.Skip(); +} +#endif + + +void WX_VIEW_CONTROLS::onButton( wxMouseEvent& aEvent ) +{ + switch( m_state ) + { + case IDLE: + case AUTO_PANNING: + if( aEvent.MiddleDown() ) + { + m_dragStartPoint = VECTOR2D( aEvent.GetX(), aEvent.GetY() ); + m_lookStartPoint = m_view->GetCenter(); + m_state = DRAG_PANNING; + } + + if( aEvent.LeftUp() ) + m_state = IDLE; // Stop autopanning when user release left mouse button + + break; + + case DRAG_PANNING: + if( aEvent.MiddleUp() ) + m_state = IDLE; + + break; + } + + aEvent.Skip(); +} + + +void WX_VIEW_CONTROLS::onEnter( wxMouseEvent& aEvent ) +{ + m_parentPanel->SetFocus(); +} + + +void WX_VIEW_CONTROLS::onLeave( wxMouseEvent& aEvent ) +{ + if( m_cursorCaptured ) + { + bool warp = false; + int x = aEvent.GetX(); + int y = aEvent.GetY(); + wxSize parentSize = m_parentPanel->GetClientSize(); + + if( x < 0 ) + { + x = 0; + warp = true; + } + else if( x >= parentSize.x ) + { + x = parentSize.x - 1; + warp = true; + } + + if( y < 0 ) + { + y = 0; + warp = true; + } + else if( y >= parentSize.y ) + { + y = parentSize.y - 1; + warp = true; + } + + if( warp ) + m_parentPanel->WarpPointer( x, y ); + } +} + + +void WX_VIEW_CONTROLS::onTimer( wxTimerEvent& aEvent ) +{ + switch( m_state ) + { + case AUTO_PANNING: + { +#if wxCHECK_VERSION( 3, 0, 0 ) + if( !m_parentPanel->HasFocus() ) + break; +#endif + + double borderSize = std::min( m_autoPanMargin * m_view->GetScreenPixelSize().x, + m_autoPanMargin * m_view->GetScreenPixelSize().y ); + + VECTOR2D dir( m_panDirection ); + + if( dir.EuclideanNorm() > borderSize ) + dir = dir.Resize( borderSize ); + + dir = m_view->ToWorld( dir, false ); + m_view->SetCenter( m_view->GetCenter() + dir * m_autoPanSpeed ); + + // Notify tools that the cursor position has changed in the world coordinates + wxMouseEvent moveEvent( EVT_REFRESH_MOUSE ); + + // Set the modifiers state +#if wxCHECK_VERSION( 3, 0, 0 ) + moveEvent.SetControlDown( wxGetKeyState( WXK_CONTROL ) ); + moveEvent.SetShiftDown( wxGetKeyState( WXK_SHIFT ) ); + moveEvent.SetAltDown( wxGetKeyState( WXK_ALT ) ); +#else + // wx <3.0 do not have accessors, but the fields are exposed + moveEvent.m_controlDown = wxGetKeyState( WXK_CONTROL ); + moveEvent.m_shiftDown = wxGetKeyState( WXK_SHIFT ); + moveEvent.m_altDown = wxGetKeyState( WXK_ALT ); +#endif + + wxPostEvent( m_parentPanel, moveEvent ); + } + break; + + case IDLE: // Just remove unnecessary warnings + case DRAG_PANNING: + break; + } +} + + +void WX_VIEW_CONTROLS::onScroll( wxScrollWinEvent& aEvent ) +{ + VECTOR2D center = m_view->GetCenter(); + const BOX2I& boundary = m_view->GetBoundary(); + + if( aEvent.GetOrientation() == wxHORIZONTAL ) + center.x = (double) aEvent.GetPosition() * boundary.GetWidth() / m_scrollScale.x + boundary.GetLeft(); + else if( aEvent.GetOrientation() == wxVERTICAL ) + center.y = (double) aEvent.GetPosition() * boundary.GetHeight() / m_scrollScale.y + boundary.GetTop(); + + m_view->SetCenter( center ); + m_parentPanel->Refresh(); +} + + +void WX_VIEW_CONTROLS::SetGrabMouse( bool aEnabled ) +{ + if( aEnabled && !m_grabMouse ) + m_parentPanel->CaptureMouse(); + else if( !aEnabled && m_grabMouse ) + m_parentPanel->ReleaseMouse(); + + VIEW_CONTROLS::SetGrabMouse( aEnabled ); +} + + +VECTOR2I WX_VIEW_CONTROLS::GetMousePosition() const +{ + wxPoint msp = wxGetMousePosition(); + wxPoint winp = m_parentPanel->GetScreenPosition(); + + return VECTOR2I( msp.x - winp.x, msp.y - winp.y ); +} + + +VECTOR2D WX_VIEW_CONTROLS::GetCursorPosition() const +{ + if( m_forceCursorPosition ) + { + return m_forcedPosition; + } + else + { + VECTOR2D mousePosition = GetMousePosition(); + + if( m_snappingEnabled ) + return m_view->GetGAL()->GetGridPoint( m_view->ToWorld( mousePosition ) ); + else + return m_view->ToWorld( mousePosition ); + } +} + + +void WX_VIEW_CONTROLS::WarpCursor( const VECTOR2D& aPosition, bool aWorldCoordinates, + bool aWarpView ) const +{ + if( aWorldCoordinates ) + { + const VECTOR2I& screenSize = m_view->GetGAL()->GetScreenPixelSize(); + BOX2I screen( VECTOR2I( 0, 0 ), screenSize ); + VECTOR2D screenPos = m_view->ToScreen( aPosition ); + + if( !screen.Contains( screenPos ) ) + { + if( aWarpView ) + { + m_view->SetCenter( aPosition ); + m_parentPanel->WarpPointer( screenSize.x / 2, screenSize.y / 2 ); + } + } + else + { + m_parentPanel->WarpPointer( screenPos.x, screenPos.y ); + } + } + else + { + m_parentPanel->WarpPointer( aPosition.x, aPosition.y ); + } +} + + +void WX_VIEW_CONTROLS::CenterOnCursor() const +{ + const VECTOR2I& screenSize = m_view->GetGAL()->GetScreenPixelSize(); + VECTOR2I screenCenter( screenSize / 2 ); + + if( GetMousePosition() != screenCenter ) + { + m_view->SetCenter( GetCursorPosition() ); + m_parentPanel->WarpPointer( KiROUND( screenSize.x / 2 ), KiROUND( screenSize.y / 2 ) ); + } +} + + +bool WX_VIEW_CONTROLS::handleAutoPanning( const wxMouseEvent& aEvent ) +{ + VECTOR2D p( aEvent.GetX(), aEvent.GetY() ); + + // Compute areas where autopanning is active + double borderStart = std::min( m_autoPanMargin * m_view->GetScreenPixelSize().x, + m_autoPanMargin * m_view->GetScreenPixelSize().y ); + double borderEndX = m_view->GetScreenPixelSize().x - borderStart; + double borderEndY = m_view->GetScreenPixelSize().y - borderStart; + + if( p.x < borderStart ) + m_panDirection.x = -( borderStart - p.x ); + else if( p.x > borderEndX ) + m_panDirection.x = ( p.x - borderEndX ); + else + m_panDirection.x = 0; + + if( p.y < borderStart ) + m_panDirection.y = -( borderStart - p.y ); + else if( p.y > borderEndY ) + m_panDirection.y = ( p.y - borderEndY ); + else + m_panDirection.y = 0; + + bool borderHit = ( m_panDirection.x != 0 || m_panDirection.y != 0 ); + + switch( m_state ) + { + case AUTO_PANNING: + if( !borderHit ) + { + m_panTimer.Stop(); + m_state = IDLE; + + return false; + } + + return true; + break; + + case IDLE: + if( borderHit ) + { + m_state = AUTO_PANNING; + m_panTimer.Start( (int) ( 1000.0 / 60.0 ) ); + + return true; + } + + return false; + break; + + case DRAG_PANNING: + return false; + } + + wxASSERT_MSG( false, wxT( "This line should never be reached" ) ); + return false; // Should not be reached, just avoid the compiler warnings.. +} + + +void WX_VIEW_CONTROLS::UpdateScrollbars() +{ + const BOX2D viewport = m_view->GetViewport(); + const BOX2I& boundary = m_view->GetBoundary(); + + m_scrollScale.x = 2e3 * boundary.GetWidth() / viewport.GetWidth(); + m_scrollScale.y = 2e3 * boundary.GetHeight() / viewport.GetHeight(); + + // Another example of wxWidgets being broken by design: scroll position is determined by the + // left (or top, if vertical) edge of the slider. Fortunately, slider size seems to be constant + // (at least for wxGTK 3.0), so we have to add its size to allow user to scroll the workspace + // till the end. + m_parentPanel->SetScrollbars( 1, 1, +#ifdef __LINUX__ + m_scrollScale.x + 1623, m_scrollScale.y + 1623, +#else + m_scrollScale.x, m_scrollScale.y, +#endif + ( viewport.Centre().x - boundary.GetLeft() ) / boundary.GetWidth() * m_scrollScale.x, + ( viewport.Centre().y - boundary.GetTop() ) / boundary.GetHeight() * m_scrollScale.y ); +} -- cgit