summaryrefslogtreecommitdiff
path: root/common/view
diff options
context:
space:
mode:
Diffstat (limited to 'common/view')
-rw-r--r--common/view/view.cpp1086
-rw-r--r--common/view/view_controls.cpp47
-rw-r--r--common/view/view_group.cpp162
-rw-r--r--common/view/view_item.cpp109
-rw-r--r--common/view/wx_view_controls.cpp491
5 files changed, 1895 insertions, 0 deletions
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 <tomasz.wlostowski@cern.ch>
+ *
+ * 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 <boost/foreach.hpp>
+
+#include <base_struct.h>
+#include <layers_id_colors_and_visibility.h>
+
+#include <view/view.h>
+#include <view/view_group.h>
+#include <view/view_rtree.h>
+#include <gal/definitions.h>
+#include <gal/graphics_abstraction_layer.h>
+#include <painter.h>
+
+#ifdef PROFILE
+#include <profile.h>
+#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<VIEW_ITEM*>::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 <class Container>
+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<LAYER_ITEM_PAIR>& aResult ) const
+{
+ if( m_orderedLayers.empty() )
+ return 0;
+
+ std::vector<VIEW_LAYER*>::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<std::vector<LAYER_ITEM_PAIR> > 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<unsigned int>::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<unsigned int>::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<VIEW_ITEM*>::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<EDA_ITEM*>( 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<int>::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 <at> gmx.de
+ * Copyright (C) 2013-2015 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * 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 <view/view.h>
+#include <view/view_controls.h>
+#include <gal/graphics_abstraction_layer.h>
+
+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 <maciej.suminski@cern.ch>
+ *
+ * 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 <set>
+#include <algorithm>
+#include <view/view_group.h>
+#include <view/view.h>
+#include <painter.h>
+#include <gal/graphics_abstraction_layer.h>
+#include <boost/foreach.hpp>
+#include <layers_id_colors_and_visibility.h>
+
+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<VIEW_ITEM*>::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<VIEW_ITEM*>::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 <tomasz.wlostowski@cern.ch>
+ *
+ * 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 <gal/definitions.h>
+
+#include <view/view_item.h>
+#include <view/view.h>
+
+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<int> VIEW_ITEM::getAllGroups() const
+{
+ std::vector<int> 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 <at> gmx.de
+ * Copyright (C) 2013-2015 CERN
+ * Copyright (C) 2012-2016 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
+ * @author Maciej Suminski <maciej.suminski@cern.ch>
+ *
+ * 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 <wx/wx.h>
+
+#include <view/view.h>
+#include <view/wx_view_controls.h>
+#include <gal/graphics_abstraction_layer.h>
+#include <tool/tool_dispatcher.h>
+
+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 );
+}