kicad/common/view/view.cpp

1366 lines
31 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2016 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 <base_struct.h>
#include <layers_id_colors_and_visibility.h>
#include <view/view.h>
#include <view/view_group.h>
#include <view/view_item.h>
#include <view/view_rtree.h>
#include <gal/definitions.h>
#include <gal/graphics_abstraction_layer.h>
#include <painter.h>
#ifdef __WXDEBUG__
#include <profile.h>
#endif /* __WXDEBUG__ */
namespace KIGFX {
class VIEW;
class VIEW_ITEM_DATA
{
public:
VIEW_ITEM_DATA() :
m_view( nullptr ),
m_flags( KIGFX::VISIBLE ),
m_requiredUpdate( KIGFX::NONE ),
m_groups( nullptr ),
m_groupsSize( 0 ) {}
~VIEW_ITEM_DATA()
{
deleteGroups();
}
int getFlags() const
{
return m_flags;
}
private:
friend class VIEW;
/**
* Function getLayers()
* Returns layer numbers used by the item.
*
* @param aLayers[]: output layer index array
* @param aCount: number of layer indices in aLayers[]
*/
void 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();
}
VIEW* m_view; ///< Current dynamic view the item is assigned to.
int m_flags; ///< Visibility flags
int m_requiredUpdate; ///< Flag required for updating
///> Helper for storing cached items group ids
typedef std::pair<int, int> GroupPair;
///> Indexes of cached GAL display lists corresponding to the item (for every layer it occupies).
///> (in the std::pair "first" stores layer number, "second" stores group id).
GroupPair* m_groups;
int m_groupsSize;
/**
* Function getGroup()
* Returns number of the group id for the given layer, or -1 in case it was not cached before.
*
* @param aLayer is the layer number for which group id is queried.
* @return group id or -1 in case there is no group id (ie. item is not cached).
*/
int 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;
}
/**
* Function getAllGroups()
* Returns all group ids for the item (collected from all layers the item occupies).
*
* @return vector of group ids.
*/
std::vector<int> getAllGroups() const
{
std::vector<int> groups( m_groupsSize );
for( int i = 0; i < m_groupsSize; ++i )
{
groups[i] = m_groups[i].second;
}
return groups;
}
/**
* Function setGroup()
* Sets a group id for the item and the layer combination.
*
* @param aLayer is the layer numbe.
* @param aGroup is the group id.
*/
void setGroup( int aLayer, int aGroup )
{
// 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 = aGroup;
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, aGroup );
}
/**
* Function deleteGroups()
* Removes all of the stored group ids. Forces recaching of the item.
*/
void deleteGroups()
{
delete[] m_groups;
m_groups = nullptr;
m_groupsSize = 0;
}
/**
* Function storesGroups()
* Returns information if the item uses at least one group id (ie. if it is cached at all).
*
* @returns true in case it is cached at least for one layer.
*/
inline bool storesGroups() const
{
return m_groupsSize > 0;
}
/// Stores layer numbers used by the item.
std::bitset<VIEW::VIEW_MAX_LAYERS> m_layers;
/**
* Function saveLayers()
* Saves layers used by the item.
*
* @param aLayers is an array containing layer numbers to be saved.
* @param aCount is the size of the array.
*/
void saveLayers( int* aLayers, int aCount )
{
m_layers.reset();
for( int i = 0; i < aCount; ++i )
{
// this fires on some eagle board after EAGLE_PLUGIN::Load()
wxASSERT( unsigned( aLayers[i] ) <= unsigned( VIEW::VIEW_MAX_LAYERS ) );
m_layers.set( aLayers[i] );
}
}
/**
* Function viewRequiredUpdate()
* Returns current update flag for an item.
*/
int requiredUpdate() const
{
return m_requiredUpdate;
}
/**
* Function clearUpdateFlags()
* Marks an item as already updated, so it is not going to be redrawn.
*/
void clearUpdateFlags()
{
m_requiredUpdate = NONE;
}
/**
* Function isRenderable()
* Returns if the item should be drawn or not.
*/
bool isRenderable() const
{
return m_flags == VISIBLE;
}
};
void VIEW::OnDestroy( VIEW_ITEM* aItem )
{
auto data = aItem->viewPrivData();
if( !data )
return;
data->m_view->Remove( aItem );
}
VIEW::VIEW( bool aIsDynamic ) :
m_enableOrderModifier( true ),
m_scale( 4.0 ),
m_minScale( 4.0 ), m_maxScale( 15000 ),
m_mirrorX( false ), m_mirrorY( false ),
m_painter( NULL ),
m_gal( NULL ),
m_dynamic( aIsDynamic )
{
m_boundary.SetMaximum();
m_allItems.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()
{
for( 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->m_viewPrivData = new VIEW_ITEM_DATA;
aItem->m_viewPrivData->m_view = this;
aItem->ViewGetLayers( layers, layers_count );
aItem->viewPrivData()->saveLayers( layers, layers_count );
m_allItems.push_back( aItem );
for( int i = 0; i < layers_count; ++i )
{
VIEW_LAYER& l = m_layers[layers[i]];
l.items->Insert( aItem );
MarkTargetDirty( l.target );
}
SetVisible( aItem, true );
Update( aItem, KIGFX::ALL );
}
void VIEW::Remove( VIEW_ITEM* aItem )
{
if( !aItem )
return;
auto viewData = aItem->viewPrivData();
if( !viewData )
return;
auto item = std::find( m_allItems.begin(), m_allItems.end(), aItem );
if( item != m_allItems.end() )
{
m_allItems.erase( item );
viewData->clearUpdateFlags();
}
int layers[VIEW::VIEW_MAX_LAYERS], layers_count;
viewData->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 = viewData->getGroup( layers[i] );
if( prevGroup >= 0 )
m_gal->DeleteGroup( prevGroup );
}
viewData->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->viewPrivData()->getFlags() & VISIBLE )
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 );
SetMirror( m_mirrorX, m_mirrorY );
}
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 )
{
wxASSERT_MSG( !aMirrorY, _( "Mirroring for Y axis is not supported yet" ) );
m_mirrorX = aMirrorX;
m_mirrorY = aMirrorY;
m_gal->SetFlip( aMirrorX, aMirrorY );
// Redraw everything
MarkDirty();
}
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->viewPrivData()->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();
m_gal->BeginUpdate();
updateItemsColor visitor( aLayer, m_painter, m_gal );
m_layers[aLayer].items->Query( r, visitor );
MarkTargetDirty( m_layers[aLayer].target );
m_gal->EndUpdate();
}
void VIEW::UpdateAllLayersColor()
{
BOX2I r;
r.SetMaximum();
m_gal->BeginUpdate();
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 );
}
m_gal->EndUpdate();
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->viewPrivData()->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();
m_gal->BeginUpdate();
changeItemsDepth visitor( aLayer, aDepth, m_gal );
m_layers[aLayer].items->Query( r, visitor );
m_gal->EndUpdate();
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();
for( 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 )
{
assert( aItem->viewPrivData() );
// Conditions that have te be fulfilled for an item to be drawn
bool drawCondition = aItem->viewPrivData()->isRenderable() &&
aItem->ViewGetLOD( layer, view ) < 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 )
{
for( 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 )
{
auto viewData = aItem->viewPrivData();
if( !viewData )
return;
if( IsCached( aLayer ) && !aImmediate )
{
// Draw using cached information or create one
int group = viewData->getGroup( aLayer );
if( group >= 0 )
{
m_gal->DrawGroup( group );
}
else
{
group = m_gal->BeginGroup();
viewData->setGroup( aLayer, group );
if( !m_painter->Draw( aItem, aLayer ) )
aItem->ViewDraw( aLayer, this ); // Alternative drawing method
m_gal->EndGroup();
}
}
else
{
// Immediate mode
if( !m_painter->Draw( aItem, aLayer ) )
aItem->ViewDraw( aLayer, this ); // 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 )
{
for( unsigned int i = 0; i < aGroup->GetSize(); i++)
draw( aGroup->GetItem(i), aImmediate );
}
struct VIEW::recacheItem
{
recacheItem( VIEW* aView, GAL* aGal, int aLayer ) :
view( aView ), gal( aGal ), layer( aLayer )
{
}
bool operator()( VIEW_ITEM* aItem )
{
auto viewData = aItem->viewPrivData();
if( !viewData )
return false;
// Remove previously cached group
int group = viewData->getGroup( layer );
if( group >= 0 )
gal->DeleteGroup( group );
viewData->setGroup( layer, -1 );
view->Update( aItem );
return true;
}
VIEW* view;
GAL* gal;
int layer;
};
void VIEW::Clear()
{
BOX2I r;
r.SetMaximum();
m_allItems.clear();
for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i )
i->second.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 __WXDEBUG__
PROF_COUNTER totalRealTime;
#endif /* __WXDEBUG__ */
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 __WXDEBUG__
totalRealTime.Stop();
wxLogTrace( "GAL_PROFILE", wxT( "VIEW::Redraw(): %.1f ms" ), totalRealTime.msecs() );
#endif /* __WXDEBUG__ */
}
const VECTOR2I& VIEW::GetScreenPixelSize() const
{
return m_gal->GetScreenPixelSize();
}
struct VIEW::clearLayerCache
{
clearLayerCache( VIEW* aView ) :
view( aView )
{
}
bool operator()( VIEW_ITEM* aItem )
{
aItem->viewPrivData()->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 & LAYERS )
updateLayers( aItem );
else if( aUpdateFlags & 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 & ( GEOMETRY | LAYERS ) )
updateItemGeometry( aItem, layerId );
else if( aUpdateFlags & COLOR )
updateItemColor( aItem, layerId );
}
// Mark those layers as dirty, so the VIEW will be refreshed
MarkTargetDirty( m_layers[layerId].target );
}
aItem->viewPrivData()->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 )
{
auto viewData = aItem->viewPrivData();
wxASSERT( (unsigned) aLayer < m_layers.size() );
wxASSERT( IsCached( aLayer ) );
if( !viewData )
return;
// 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 = viewData->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 )
{
auto viewData = aItem->viewPrivData();
wxASSERT( (unsigned) aLayer < m_layers.size() );
wxASSERT( IsCached( aLayer ) );
if( !viewData )
return;
VIEW_LAYER& l = m_layers.at( aLayer );
m_gal->SetTarget( l.target );
m_gal->SetLayerDepth( l.renderingOrder );
// Redraw the item from scratch
int group = viewData->getGroup( aLayer );
if( group >= 0 )
m_gal->DeleteGroup( group );
group = m_gal->BeginGroup();
viewData->setGroup( aLayer, group );
if( !m_painter->Draw( static_cast<EDA_ITEM*>( aItem ), aLayer ) )
aItem->ViewDraw( aLayer, this ); // 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 )
{
auto viewData = aItem->viewPrivData();
int layers[VIEW_MAX_LAYERS], layers_count;
if( !viewData )
return;
// Remove the item from previous layer set
viewData->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 = viewData->getGroup( layers[i] );
if( prevGroup >= 0 )
{
m_gal->DeleteGroup( prevGroup );
viewData->setGroup( l.id, -1 );
}
}
}
// Add the item to new layer set
aItem->ViewGetLayers( layers, layers_count );
viewData->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>::const_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()
{
BOX2I r;
r.SetMaximum();
for( LAYER_MAP_ITER i = m_layers.begin(); i != m_layers.end(); ++i )
{
VIEW_LAYER* l = &( ( *i ).second );
if( IsCached( l->id ) )
{
recacheItem visitor( this, m_gal, l->id );
l->items->Query( r, visitor );
}
}
}
void VIEW::UpdateItems()
{
m_gal->BeginUpdate();
for( VIEW_ITEM* item : m_allItems )
{
auto viewData = item->viewPrivData();
if( !viewData )
continue;
if( viewData->m_requiredUpdate != NONE )
{
invalidateItem( item, viewData->m_requiredUpdate );
viewData->m_requiredUpdate = NONE;
}
}
m_gal->EndUpdate();
}
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();
for( VIEW_LAYER* l : m_orderedLayers )
{
l->items->Query( fullScene, v );
}
return v.extents;
}
void VIEW::SetVisible( VIEW_ITEM* aItem, bool aIsVisible )
{
auto viewData = aItem->viewPrivData();
if( !viewData )
return;
bool cur_visible = viewData->m_flags & VISIBLE;
if( cur_visible != aIsVisible )
{
if( aIsVisible )
viewData->m_flags |= VISIBLE;
else
viewData->m_flags &= ~VISIBLE;
Update( aItem, APPEARANCE | COLOR );
}
}
void VIEW::Hide( VIEW_ITEM* aItem, bool aHide )
{
auto viewData = aItem->viewPrivData();
if( !viewData )
return;
if( !( viewData->m_flags & VISIBLE ) )
return;
if( aHide )
viewData->m_flags |= HIDDEN;
else
viewData->m_flags &= ~HIDDEN;
Update( aItem, APPEARANCE );
}
bool VIEW::IsVisible( const VIEW_ITEM* aItem ) const
{
const auto viewData = aItem->viewPrivData();
return viewData->m_flags & VISIBLE;
}
void VIEW::Update( VIEW_ITEM* aItem )
{
Update( aItem, ALL );
}
void VIEW::Update( VIEW_ITEM* aItem, int aUpdateFlags )
{
auto viewData = aItem->viewPrivData();
if( !viewData )
return;
assert( aUpdateFlags != NONE );
viewData->m_requiredUpdate |= aUpdateFlags;
}
const int VIEW::TOP_LAYER_MODIFIER = -VIEW_MAX_LAYERS;
};