818 lines
24 KiB
C++
818 lines
24 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
|
|
* Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
|
|
* Copyright (C) 2023 CERN
|
|
* Copyright (C) 2014-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <eda_base_frame.h>
|
|
#include <eda_pattern_match.h>
|
|
#include <kiface_base.h>
|
|
#include <kiplatform/ui.h>
|
|
#include <lib_tree_model_adapter.h>
|
|
#include <project/project_file.h>
|
|
#include <settings/app_settings.h>
|
|
#include <widgets/ui_common.h>
|
|
#include <wx/tokenzr.h>
|
|
#include <wx/wupdlock.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/dc.h>
|
|
#include <string_utils.h>
|
|
|
|
|
|
static const int kDataViewIndent = 20;
|
|
|
|
|
|
class LIB_TREE_RENDERER : public wxDataViewCustomRenderer
|
|
{
|
|
public:
|
|
LIB_TREE_RENDERER() :
|
|
m_canvasItem( false )
|
|
{}
|
|
|
|
wxSize GetSize() const override
|
|
{
|
|
return wxSize( GetOwner()->GetWidth(), GetTextExtent( m_text ).y + 2 );
|
|
}
|
|
|
|
bool GetValue( wxVariant& aValue ) const override
|
|
{
|
|
aValue = m_text;
|
|
return true;
|
|
}
|
|
|
|
bool SetValue( const wxVariant& aValue ) override
|
|
{
|
|
m_text = aValue.GetString();
|
|
return true;
|
|
}
|
|
|
|
void SetAttr( const wxDataViewItemAttr& aAttr ) override
|
|
{
|
|
// Use strikethrough as a proxy for is-canvas-item
|
|
m_canvasItem = aAttr.GetStrikethrough();
|
|
|
|
wxDataViewItemAttr realAttr = aAttr;
|
|
realAttr.SetStrikethrough( false );
|
|
|
|
wxDataViewCustomRenderer::SetAttr( realAttr );
|
|
}
|
|
|
|
bool Render( wxRect aRect, wxDC *dc, int aState ) override
|
|
{
|
|
RenderBackground( dc, aRect );
|
|
|
|
if( m_canvasItem )
|
|
{
|
|
wxPoint points[6];
|
|
points[0] = aRect.GetTopLeft();
|
|
points[1] = aRect.GetTopRight() + wxPoint( -4, 0 );
|
|
points[2] = aRect.GetTopRight() + wxPoint( 0, aRect.GetHeight() / 2 );
|
|
points[3] = aRect.GetBottomRight() + wxPoint( -4, 1 );
|
|
points[4] = aRect.GetBottomLeft() + wxPoint( 0, 1 );
|
|
points[5] = aRect.GetTopLeft();
|
|
|
|
dc->SetPen( KIPLATFORM::UI::IsDarkTheme() ? *wxWHITE_PEN : *wxBLACK_PEN );
|
|
dc->DrawLines( 6, points );
|
|
}
|
|
|
|
aRect.Deflate( 1 );
|
|
|
|
#ifdef __WXOSX__
|
|
// We should be able to pass wxDATAVIEW_CELL_SELECTED into RenderText() and have it do
|
|
// the right thing -- but it picks wxSYS_COLOUR_HIGHLIGHTTEXT on MacOS (instead
|
|
// of wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT).
|
|
if( aState & wxDATAVIEW_CELL_SELECTED )
|
|
dc->SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT ) );
|
|
|
|
RenderText( m_text, 0, aRect, dc, 0 );
|
|
#else
|
|
RenderText( m_text, 0, aRect, dc, aState );
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool m_canvasItem;
|
|
wxString m_text;
|
|
};
|
|
|
|
|
|
wxDataViewItem LIB_TREE_MODEL_ADAPTER::ToItem( const LIB_TREE_NODE* aNode )
|
|
{
|
|
return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aNode ) ) );
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ToNode( wxDataViewItem aItem )
|
|
{
|
|
return static_cast<LIB_TREE_NODE*>( aItem.GetID() );
|
|
}
|
|
|
|
|
|
LIB_TREE_MODEL_ADAPTER::LIB_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent,
|
|
const wxString& aPinnedKey ) :
|
|
m_widget( nullptr ),
|
|
m_parent( aParent ),
|
|
m_sort_mode( BEST_MATCH ),
|
|
m_show_units( true ),
|
|
m_preselect_unit( 0 ),
|
|
m_freeze( 0 ),
|
|
m_filter( nullptr )
|
|
{
|
|
// Default column widths. Do not translate these names.
|
|
m_colWidths[ _HKI( "Item" ) ] = 300;
|
|
m_colWidths[ _HKI( "Description" ) ] = 600;
|
|
|
|
m_availableColumns = { _HKI( "Item" ), _HKI( "Description" ) };
|
|
|
|
APP_SETTINGS_BASE* cfg = Kiface().KifaceSettings();
|
|
|
|
for( const std::pair<const wxString, int>& pair : cfg->m_LibTree.column_widths )
|
|
m_colWidths[pair.first] = pair.second;
|
|
|
|
m_shownColumns = cfg->m_LibTree.columns;
|
|
|
|
if( m_shownColumns.empty() )
|
|
m_shownColumns = { _HKI( "Item" ), _HKI( "Description" ) };
|
|
|
|
if( m_shownColumns[0] != _HKI( "Item" ) )
|
|
m_shownColumns.insert( m_shownColumns.begin(), _HKI( "Item" ) );
|
|
}
|
|
|
|
|
|
LIB_TREE_MODEL_ADAPTER::~LIB_TREE_MODEL_ADAPTER()
|
|
{}
|
|
|
|
|
|
std::vector<wxString> LIB_TREE_MODEL_ADAPTER::GetOpenLibs() const
|
|
{
|
|
std::vector<wxString> openLibs;
|
|
wxDataViewItem rootItem( nullptr );
|
|
wxDataViewItemArray children;
|
|
|
|
GetChildren( rootItem, children );
|
|
|
|
for( const wxDataViewItem& child : children )
|
|
{
|
|
if( m_widget->IsExpanded( child ) )
|
|
openLibs.emplace_back( ToNode( child )->m_LibId.GetLibNickname().wx_str() );
|
|
}
|
|
|
|
return openLibs;
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::OpenLibs( const std::vector<wxString>& aLibs )
|
|
{
|
|
wxWindowUpdateLocker updateLock( m_widget );
|
|
|
|
for( const wxString& lib : aLibs )
|
|
{
|
|
wxDataViewItem item = FindItem( LIB_ID( lib, wxEmptyString ) );
|
|
|
|
if( item.IsOk() )
|
|
m_widget->Expand( item );
|
|
}
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::SaveSettings()
|
|
{
|
|
if( m_widget )
|
|
{
|
|
APP_SETTINGS_BASE* cfg = Kiface().KifaceSettings();
|
|
|
|
cfg->m_LibTree.columns = GetShownColumns();
|
|
cfg->m_LibTree.column_widths.clear();
|
|
|
|
for( const std::pair<const wxString, wxDataViewColumn*>& pair : m_colNameMap )
|
|
cfg->m_LibTree.column_widths[pair.first] = pair.second->GetWidth();
|
|
|
|
cfg->m_LibTree.open_libs = GetOpenLibs();
|
|
}
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::ShowUnits( bool aShow )
|
|
{
|
|
m_show_units = aShow;
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::SetPreselectNode( const LIB_ID& aLibId, int aUnit )
|
|
{
|
|
m_preselect_lib_id = aLibId;
|
|
m_preselect_unit = aUnit;
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE_LIBRARY& LIB_TREE_MODEL_ADAPTER::DoAddLibraryNode( const wxString& aNodeName,
|
|
const wxString& aDesc,
|
|
bool pinned )
|
|
{
|
|
LIB_TREE_NODE_LIBRARY& lib_node = m_tree.AddLib( aNodeName, aDesc );
|
|
|
|
lib_node.m_Pinned = pinned;
|
|
|
|
return lib_node;
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName, const wxString& aDesc,
|
|
const std::vector<LIB_TREE_ITEM*>& aItemList,
|
|
bool pinned, bool presorted )
|
|
{
|
|
LIB_TREE_NODE_LIBRARY& lib_node = DoAddLibraryNode( aNodeName, aDesc, pinned );
|
|
|
|
for( LIB_TREE_ITEM* item: aItemList )
|
|
lib_node.AddItem( item );
|
|
|
|
lib_node.AssignIntrinsicRanks( presorted );
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState )
|
|
{
|
|
{
|
|
wxWindowUpdateLocker updateLock( m_widget );
|
|
|
|
// Even with the updateLock, wxWidgets sometimes ties its knickers in a knot trying to
|
|
// run a wxdataview_selection_changed_callback() on a row that has been deleted.
|
|
// https://bugs.launchpad.net/kicad/+bug/1756255
|
|
m_widget->UnselectAll();
|
|
|
|
// This collapse is required before the call to "Freeze()" below. Once Freeze()
|
|
// is called, GetParent() will return nullptr. While this works for some calls, it
|
|
// segfaults when we have any expanded elements b/c the sub units in the tree don't
|
|
// have explicit references that are maintained over a search
|
|
// The tree will be expanded again below when we get our matches
|
|
//
|
|
// Also note that this cannot happen when we have deleted a symbol as GTK will also
|
|
// iterate over the tree in this case and find a symbol that has an invalid link
|
|
// and crash https://gitlab.com/kicad/code/kicad/-/issues/6910
|
|
if( !aState && !aSearch.IsNull() && m_tree.m_Children.size() )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& child: m_tree.m_Children )
|
|
m_widget->Collapse( wxDataViewItem( &*child ) );
|
|
}
|
|
|
|
// DO NOT REMOVE THE FREEZE/THAW. This freeze/thaw is a flag for this model adapter
|
|
// that tells it when it shouldn't trust any of the data in the model. When set, it will
|
|
// not return invalid data to the UI, since this invalid data can cause crashes.
|
|
// This is different than the update locker, which locks the UI aspects only.
|
|
Freeze();
|
|
BeforeReset();
|
|
|
|
m_tree.ResetScore();
|
|
|
|
wxStringTokenizer tokenizer( aSearch );
|
|
bool firstTerm = true;
|
|
|
|
while( tokenizer.HasMoreTokens() )
|
|
{
|
|
// First search for the full token, in case it appears in a search string
|
|
wxString term = tokenizer.GetNextToken().Lower();
|
|
EDA_COMBINED_MATCHER termMatcher( term, CTX_LIBITEM );
|
|
|
|
m_tree.UpdateScore( &termMatcher, wxEmptyString, firstTerm ? m_filter : nullptr );
|
|
firstTerm = false;
|
|
|
|
if( term.Contains( ":" ) )
|
|
{
|
|
// Next search for the library:item_name
|
|
wxString lib = term.BeforeFirst( ':' );
|
|
wxString itemName = term.AfterFirst( ':' );
|
|
EDA_COMBINED_MATCHER itemNameMatcher( itemName, CTX_LIBITEM );
|
|
|
|
m_tree.UpdateScore( &itemNameMatcher, lib, nullptr );
|
|
}
|
|
}
|
|
|
|
if( firstTerm )
|
|
{
|
|
// No terms processed; just run the filter
|
|
m_tree.UpdateScore( nullptr, wxEmptyString, m_filter );
|
|
}
|
|
|
|
m_tree.SortNodes( m_sort_mode == BEST_MATCH );
|
|
AfterReset();
|
|
Thaw();
|
|
}
|
|
|
|
const LIB_TREE_NODE* firstMatch = ShowResults();
|
|
|
|
if( firstMatch )
|
|
{
|
|
wxDataViewItem item = ToItem( firstMatch );
|
|
m_widget->Select( item );
|
|
|
|
// Make sure the *parent* item is visible. The selected item is the first (shown) child
|
|
// of the parent. So it's always right below the parent, and this way the user can also
|
|
// see what library the selected part belongs to, without having a case where the selection
|
|
// is off the screen (unless the window is a single row high, which is unlikely).
|
|
//
|
|
// This also happens to circumvent https://bugs.launchpad.net/kicad/+bug/1804400 which
|
|
// appears to be a GTK+3 bug.
|
|
{
|
|
wxDataViewItem parent = GetParent( item );
|
|
|
|
if( parent.IsOk() )
|
|
m_widget->EnsureVisible( parent );
|
|
}
|
|
|
|
m_widget->EnsureVisible( item );
|
|
}
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::AttachTo( wxDataViewCtrl* aDataViewCtrl )
|
|
{
|
|
m_widget = aDataViewCtrl;
|
|
aDataViewCtrl->SetIndent( kDataViewIndent );
|
|
aDataViewCtrl->AssociateModel( this );
|
|
recreateColumns();
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::recreateColumns()
|
|
{
|
|
m_widget->ClearColumns();
|
|
|
|
m_columns.clear();
|
|
m_colIdxMap.clear();
|
|
m_colNameMap.clear();
|
|
|
|
// The Item column is always shown
|
|
doAddColumn( wxT( "Item" ) );
|
|
|
|
for( const wxString& colName : m_shownColumns )
|
|
{
|
|
if( !m_colNameMap.count( colName ) )
|
|
doAddColumn( colName, colName == wxT( "Description" ) );
|
|
}
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::resortTree()
|
|
{
|
|
Freeze();
|
|
BeforeReset();
|
|
|
|
m_tree.SortNodes( m_sort_mode == BEST_MATCH );
|
|
|
|
AfterReset();
|
|
Thaw();
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode )
|
|
{
|
|
m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() );
|
|
aTreeNode->m_Pinned = true;
|
|
|
|
resortTree();
|
|
m_widget->EnsureVisible( ToItem( aTreeNode ) );
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::UnpinLibrary( LIB_TREE_NODE* aTreeNode )
|
|
{
|
|
m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() );
|
|
aTreeNode->m_Pinned = false;
|
|
|
|
resortTree();
|
|
// Keep focus at top when unpinning
|
|
}
|
|
|
|
|
|
wxDataViewColumn* LIB_TREE_MODEL_ADAPTER::doAddColumn( const wxString& aHeader, bool aTranslate )
|
|
{
|
|
wxString translatedHeader = aTranslate ? wxGetTranslation( aHeader ) : aHeader;
|
|
|
|
// The extent of the text doesn't take into account the space on either side
|
|
// in the header, so artificially pad it
|
|
wxSize headerMinWidth = KIUI::GetTextSize( translatedHeader + wxT( "MMM" ), m_widget );
|
|
|
|
if( !m_colWidths.count( aHeader ) || m_colWidths[aHeader] < headerMinWidth.x )
|
|
m_colWidths[aHeader] = headerMinWidth.x;
|
|
|
|
int index = (int) m_columns.size();
|
|
|
|
wxDataViewColumn* col = new wxDataViewColumn(
|
|
translatedHeader, new LIB_TREE_RENDERER(), index, m_colWidths[aHeader], wxALIGN_NOT,
|
|
wxDATAVIEW_CELL_INERT | wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE );
|
|
m_widget->AppendColumn( col );
|
|
|
|
col->SetMinWidth( headerMinWidth.x );
|
|
|
|
m_columns.emplace_back( col );
|
|
m_colNameMap[aHeader] = col;
|
|
m_colIdxMap[m_columns.size() - 1] = aHeader;
|
|
|
|
return col;
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::addColumnIfNecessary( const wxString& aHeader )
|
|
{
|
|
if( m_colNameMap.count( aHeader ) )
|
|
return;
|
|
|
|
// Columns will be created later
|
|
m_colNameMap[aHeader] = nullptr;
|
|
m_availableColumns.emplace_back( aHeader );
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::SetShownColumns( const std::vector<wxString>& aColumnNames )
|
|
{
|
|
bool recreate = m_shownColumns != aColumnNames;
|
|
|
|
m_shownColumns = aColumnNames;
|
|
|
|
if( recreate && m_widget )
|
|
recreateColumns();
|
|
}
|
|
|
|
|
|
LIB_ID LIB_TREE_MODEL_ADAPTER::GetAliasFor( const wxDataViewItem& aSelection ) const
|
|
{
|
|
const LIB_TREE_NODE* node = ToNode( aSelection );
|
|
return node ? node->m_LibId : LIB_ID();
|
|
}
|
|
|
|
|
|
int LIB_TREE_MODEL_ADAPTER::GetUnitFor( const wxDataViewItem& aSelection ) const
|
|
{
|
|
const LIB_TREE_NODE* node = ToNode( aSelection );
|
|
return node ? node->m_Unit : 0;
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE::TYPE LIB_TREE_MODEL_ADAPTER::GetTypeFor( const wxDataViewItem& aSelection ) const
|
|
{
|
|
const LIB_TREE_NODE* node = ToNode( aSelection );
|
|
return node ? node->m_Type : LIB_TREE_NODE::INVALID;
|
|
}
|
|
|
|
|
|
LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::GetTreeNodeFor( const wxDataViewItem& aSelection ) const
|
|
{
|
|
return ToNode( aSelection );
|
|
}
|
|
|
|
|
|
int LIB_TREE_MODEL_ADAPTER::GetItemCount() const
|
|
{
|
|
int n = 0;
|
|
|
|
for( const std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
|
|
n += lib->m_Children.size();
|
|
|
|
return n;
|
|
}
|
|
|
|
|
|
wxDataViewItem LIB_TREE_MODEL_ADAPTER::FindItem( const LIB_ID& aLibId )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& lib: m_tree.m_Children )
|
|
{
|
|
if( lib->m_Name != aLibId.GetLibNickname().wx_str() )
|
|
continue;
|
|
|
|
// if part name is not specified, return the library node
|
|
if( aLibId.GetLibItemName() == "" )
|
|
return ToItem( lib.get() );
|
|
|
|
for( std::unique_ptr<LIB_TREE_NODE>& alias: lib->m_Children )
|
|
{
|
|
if( alias->m_Name == aLibId.GetLibItemName().wx_str() )
|
|
return ToItem( alias.get() );
|
|
}
|
|
|
|
break; // could not find the part in the requested library
|
|
}
|
|
|
|
return wxDataViewItem();
|
|
}
|
|
|
|
|
|
wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetCurrentDataViewItem()
|
|
{
|
|
return FindItem( m_preselect_lib_id );
|
|
}
|
|
|
|
|
|
unsigned int LIB_TREE_MODEL_ADAPTER::GetChildren( const wxDataViewItem& aItem,
|
|
wxDataViewItemArray& aChildren ) const
|
|
{
|
|
const LIB_TREE_NODE* node = ( aItem.IsOk() ? ToNode( aItem ) : &m_tree );
|
|
unsigned int count = 0;
|
|
|
|
if( node->m_Type == LIB_TREE_NODE::TYPE::ROOT
|
|
|| node->m_Type == LIB_TREE_NODE::LIBRARY
|
|
|| ( m_show_units && node->m_Type == LIB_TREE_NODE::TYPE::ITEM ) )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE> const& child: node->m_Children )
|
|
{
|
|
if( child->m_Score > 0 )
|
|
{
|
|
aChildren.Add( ToItem( &*child ) );
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::FinishTreeInitialization()
|
|
{
|
|
wxDataViewColumn* col = nullptr;
|
|
size_t idx = 0;
|
|
int totalWidth = 0;
|
|
wxString header;
|
|
|
|
for( ; idx < m_columns.size() - 1; idx++ )
|
|
{
|
|
wxASSERT( m_colIdxMap.count( idx ) );
|
|
|
|
col = m_columns[idx];
|
|
header = m_colIdxMap[idx];
|
|
|
|
wxASSERT( m_colWidths.count( header ) );
|
|
|
|
col->SetWidth( m_colWidths[header] );
|
|
totalWidth += col->GetWidth();
|
|
}
|
|
|
|
int remainingWidth = m_widget->GetSize().x - totalWidth;
|
|
header = m_columns[idx]->GetTitle();
|
|
|
|
m_columns[idx]->SetWidth( std::max( m_colWidths[header], remainingWidth ) );
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::RefreshTree()
|
|
{
|
|
// Yes, this is an enormous hack. But it works on all platforms, it doesn't suffer
|
|
// the On^2 sorting issues that ItemChanged() does on OSX, and it doesn't lose the
|
|
// user's scroll position (which re-attaching or deleting/re-inserting columns does).
|
|
static int walk = 1;
|
|
|
|
std::vector<int> widths;
|
|
|
|
for( const wxDataViewColumn* col : m_columns )
|
|
widths.emplace_back( col->GetWidth() );
|
|
|
|
wxASSERT( widths.size() );
|
|
|
|
// Only use the widths read back if they are non-zero.
|
|
// GTK returns the displayed width of the column, which is not calculated immediately
|
|
if( widths[0] > 0 )
|
|
{
|
|
size_t i = 0;
|
|
|
|
for( const auto& [ colName, colPtr ] : m_colNameMap )
|
|
m_colWidths[ colName ] = widths[i++];
|
|
}
|
|
|
|
auto colIt = m_colWidths.begin();
|
|
|
|
colIt->second += walk;
|
|
colIt++;
|
|
|
|
if( colIt != m_colWidths.end() )
|
|
colIt->second -= walk;
|
|
|
|
for( const auto& [ colName, colPtr ] : m_colNameMap )
|
|
{
|
|
if( colPtr == m_columns[0] )
|
|
continue;
|
|
|
|
wxASSERT( m_colWidths.count( colName ) );
|
|
colPtr->SetWidth( m_colWidths[ colName ] );
|
|
}
|
|
|
|
walk = -walk;
|
|
}
|
|
|
|
|
|
bool LIB_TREE_MODEL_ADAPTER::HasContainerColumns( const wxDataViewItem& aItem ) const
|
|
{
|
|
return IsContainer( aItem );
|
|
}
|
|
|
|
|
|
bool LIB_TREE_MODEL_ADAPTER::IsContainer( const wxDataViewItem& aItem ) const
|
|
{
|
|
LIB_TREE_NODE* node = ToNode( aItem );
|
|
return node ? node->m_Children.size() : true;
|
|
}
|
|
|
|
|
|
wxDataViewItem LIB_TREE_MODEL_ADAPTER::GetParent( const wxDataViewItem& aItem ) const
|
|
{
|
|
if( m_freeze )
|
|
return ToItem( nullptr );
|
|
|
|
LIB_TREE_NODE* node = ToNode( aItem );
|
|
LIB_TREE_NODE* parent = node ? node->m_Parent : nullptr;
|
|
|
|
// wxDataViewModel has no root node, but rather top-level elements have
|
|
// an invalid (null) parent.
|
|
if( !node || !parent || parent->m_Type == LIB_TREE_NODE::TYPE::ROOT )
|
|
return ToItem( nullptr );
|
|
else
|
|
return ToItem( parent );
|
|
}
|
|
|
|
|
|
void LIB_TREE_MODEL_ADAPTER::GetValue( wxVariant& aVariant,
|
|
const wxDataViewItem& aItem,
|
|
unsigned int aCol ) const
|
|
{
|
|
if( IsFrozen() )
|
|
{
|
|
aVariant = wxEmptyString;
|
|
return;
|
|
}
|
|
|
|
LIB_TREE_NODE* node = ToNode( aItem );
|
|
wxCHECK( node, /* void */ );
|
|
wxString valueStr;
|
|
|
|
switch( aCol )
|
|
{
|
|
case NAME_COL:
|
|
if( node->m_Pinned )
|
|
valueStr = GetPinningSymbol() + UnescapeString( node->m_Name );
|
|
else
|
|
valueStr = UnescapeString( node->m_Name );
|
|
|
|
break;
|
|
|
|
default:
|
|
if( m_colIdxMap.count( aCol ) )
|
|
{
|
|
const wxString& key = m_colIdxMap.at( aCol );
|
|
|
|
if( key == wxT( "Description" ) )
|
|
valueStr = UnescapeString( node->m_Desc );
|
|
else if( node->m_Fields.count( key ) )
|
|
valueStr = UnescapeString( node->m_Fields.at( key ) );
|
|
else
|
|
valueStr = wxEmptyString;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
valueStr.Replace( wxS( "\n" ), wxS( " " ) ); // Clear line breaks
|
|
|
|
aVariant = valueStr;
|
|
}
|
|
|
|
|
|
bool LIB_TREE_MODEL_ADAPTER::GetAttr( const wxDataViewItem& aItem,
|
|
unsigned int aCol,
|
|
wxDataViewItemAttr& aAttr ) const
|
|
{
|
|
if( IsFrozen() )
|
|
return false;
|
|
|
|
LIB_TREE_NODE* node = ToNode( aItem );
|
|
wxCHECK( node, false );
|
|
|
|
if( node->m_Type == LIB_TREE_NODE::ITEM )
|
|
{
|
|
if( !node->m_IsRoot && aCol == 0 )
|
|
{
|
|
// Names of non-root aliases are italicized
|
|
aAttr.SetItalic( true );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void recursiveDescent( LIB_TREE_NODE& aNode, const std::function<int( const LIB_TREE_NODE* )>& f )
|
|
{
|
|
for( std::unique_ptr<LIB_TREE_NODE>& node: aNode.m_Children )
|
|
{
|
|
int r = f( node.get() );
|
|
|
|
if( r == 0 )
|
|
break;
|
|
else if( r == -1 )
|
|
continue;
|
|
|
|
recursiveDescent( *node, f );
|
|
}
|
|
}
|
|
|
|
|
|
const LIB_TREE_NODE* LIB_TREE_MODEL_ADAPTER::ShowResults()
|
|
{
|
|
const LIB_TREE_NODE* firstMatch = nullptr;
|
|
|
|
// Expand parents of leaf nodes with some level of matching
|
|
recursiveDescent( m_tree,
|
|
[&]( const LIB_TREE_NODE* n )
|
|
{
|
|
if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM && n->m_Score > 1 )
|
|
{
|
|
if( !firstMatch )
|
|
firstMatch = n;
|
|
else if( n->m_Score > firstMatch->m_Score )
|
|
firstMatch = n;
|
|
|
|
m_widget->ExpandAncestors( ToItem( n ) );
|
|
}
|
|
|
|
return 1; // keep going to expand ancestors of all found items
|
|
} );
|
|
|
|
// If no matches, find and show the preselect node
|
|
if( !firstMatch && m_preselect_lib_id.IsValid() )
|
|
{
|
|
recursiveDescent( m_tree,
|
|
[&]( const LIB_TREE_NODE* n )
|
|
{
|
|
// Don't match the recent and already placed libraries
|
|
if( n->m_Name.StartsWith( "-- " ) )
|
|
return -1; // Skip this node and its children
|
|
|
|
if( n->m_Type == LIB_TREE_NODE::ITEM
|
|
&& ( n->m_Children.empty() || !m_preselect_unit )
|
|
&& m_preselect_lib_id == n->m_LibId )
|
|
{
|
|
firstMatch = n;
|
|
m_widget->ExpandAncestors( ToItem( n ) );
|
|
return 0;
|
|
}
|
|
else if( n->m_Type == LIB_TREE_NODE::UNIT
|
|
&& ( m_preselect_unit && m_preselect_unit == n->m_Unit )
|
|
&& m_preselect_lib_id == n->m_Parent->m_LibId )
|
|
{
|
|
firstMatch = n;
|
|
m_widget->ExpandAncestors( ToItem( n ) );
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
} );
|
|
}
|
|
|
|
// If still no matches expand a single library if there is only one
|
|
if( !firstMatch )
|
|
{
|
|
int libraries = 0;
|
|
|
|
for( const std::unique_ptr<LIB_TREE_NODE>& child : m_tree.m_Children )
|
|
{
|
|
if( !child->m_Name.StartsWith( "-- " ) )
|
|
libraries++;
|
|
}
|
|
|
|
if( libraries != 1 )
|
|
return nullptr;
|
|
|
|
recursiveDescent( m_tree,
|
|
[&]( const LIB_TREE_NODE* n )
|
|
{
|
|
if( n->m_Type == LIB_TREE_NODE::TYPE::ITEM )
|
|
{
|
|
firstMatch = n;
|
|
m_widget->ExpandAncestors( ToItem( n ) );
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
} );
|
|
}
|
|
|
|
return firstMatch;
|
|
}
|
|
|
|
|