diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 05dd30bf82..45411d8fae 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -19,6 +19,8 @@ set( EESCHEMA_DLGS dialogs/dialog_bom.cpp dialogs/dialog_bom_base.cpp dialogs/dialog_bom_cfg_keywords.cpp + dialogs/dialog_choose_component.cpp + dialogs/dialog_choose_component_base.cpp dialogs/dialog_lib_edit_text.cpp dialogs/dialog_lib_edit_text_base.cpp dialogs/dialog_edit_component_in_lib.cpp @@ -88,6 +90,7 @@ set( EESCHEMA_SRCS files-io.cpp find.cpp getpart.cpp + component_tree_search_container.cpp hierarch.cpp hotkeys.cpp libarch.cpp diff --git a/eeschema/component_tree_search_container.cpp b/eeschema/component_tree_search_container.cpp new file mode 100644 index 0000000000..6972ff1956 --- /dev/null +++ b/eeschema/component_tree_search_container.cpp @@ -0,0 +1,356 @@ +/* -*- c++ -*- + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 Henner Zeller + * Copyright (C) 2014 KiCad Developers, see change_log.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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +// Each node gets this lowest score initially, without any matches applied. Matches +// will then increase this score depending on match quality. +// This way, an empty search string will result in all components being displayed as they +// have the minimum score. However, in that case, we avoid expanding all the nodes asd the +// result is very unspecific. +static const unsigned kLowestDefaultScore = 1; + +struct COMPONENT_TREE_SEARCH_CONTAINER::TREE_NODE +{ + TREE_NODE(TREE_NODE* aParent, CMP_LIBRARY* aOwningLib, + const wxString& aName, const wxString& aDisplayInfo, + const wxString& aSearchText, + bool aNormallyExpanded = false) + : parent( aParent ), + lib( aOwningLib ), + normally_expanded( aNormallyExpanded ), + name( aName ), + display_info( aDisplayInfo ), + match_name( aName.Lower() ), + search_text( aSearchText.Lower() ), + match_score( 0 ), previous_score( 0 ) + { + } + + TREE_NODE* parent; // NULL if library, pointer to lib when component. + CMP_LIBRARY* lib; // Owning library of this component. + bool normally_expanded; // If this is a parent node, should it be unfolded ? + wxString name; // Exact name as displayed to the user. + wxString display_info; // Additional info displayed in the tree (description..) + + wxString match_name; // Preprocessed: lowercased display name. + wxString search_text; // Other lowercase text (keywords, description..) to search in. + + unsigned match_score; // Result-Score after UpdateSearchTerm() + unsigned previous_score; // Optimization: used to see if we need any tree update. + wxTreeItemId tree_id; // Tree-ID if stored in the tree (if match_score > 0). +}; + + +// Sort tree nodes by reverse match-score (bigger is first), then alphabetically. +// Library nodes (i.e. the ones that don't have a parent) are always sorted before any +// leaf nodes. +bool COMPONENT_TREE_SEARCH_CONTAINER::scoreComparator( const TREE_NODE* a1, const TREE_NODE* a2 ) +{ + if ( a1->parent == NULL && a2->parent != NULL ) + return true; + + if ( a1->parent != NULL && a2->parent == NULL ) + return false; + + if ( a1->match_score != a2->match_score ) + return a1->match_score > a2->match_score; // biggest first. + + if (a1->parent != a2->parent) + return a1->parent->match_name.Cmp(a2->parent->match_name) < 0; + + return a1->match_name.Cmp( a2->match_name ) < 0; +} + +COMPONENT_TREE_SEARCH_CONTAINER::COMPONENT_TREE_SEARCH_CONTAINER() + : tree( NULL ) +{ +} + + +COMPONENT_TREE_SEARCH_CONTAINER::~COMPONENT_TREE_SEARCH_CONTAINER() +{ + BOOST_FOREACH( TREE_NODE* node, nodes ) + delete node; + nodes.clear(); +} + + +void COMPONENT_TREE_SEARCH_CONTAINER::SetPreselectNode( const wxString& aComponentName ) +{ + preselect_node_name = aComponentName.Lower(); +} + + +void COMPONENT_TREE_SEARCH_CONTAINER::SetTree( wxTreeCtrl* aTree ) +{ + tree = aTree; + UpdateSearchTerm( wxEmptyString ); +} + + +void COMPONENT_TREE_SEARCH_CONTAINER::AddLibrary( CMP_LIBRARY& aLib ) +{ + wxArrayString all_comp; + + aLib.GetEntryNames( all_comp ); + AddComponentList( aLib.GetName(), all_comp, &aLib, false ); +} + + +void COMPONENT_TREE_SEARCH_CONTAINER::AddComponentList( const wxString& aNodeName, + const wxArrayString& aComponentNameList, + CMP_LIBRARY* aOptionalLib, + bool aNormallyExpanded ) +{ + TREE_NODE* parent_node = new TREE_NODE( NULL, NULL, aNodeName, wxEmptyString, wxEmptyString, + aNormallyExpanded ); + + nodes.push_back( parent_node ); + + BOOST_FOREACH( const wxString& cName, aComponentNameList ) + { + LIB_COMPONENT *c; + + if (aOptionalLib) + c = aOptionalLib->FindComponent( cName ); + else + c = CMP_LIBRARY::FindLibraryComponent( cName, wxEmptyString ); + + if (c == NULL) + continue; + + wxString keywords, descriptions; + wxString display_info; + + for ( size_t i = 0; i < c->GetAliasCount(); ++i ) + { + LIB_ALIAS *a = c->GetAlias( i ); + keywords += a->GetKeyWords(); + descriptions += a->GetDescription(); + + if ( display_info.empty() && !a->GetDescription().empty() ) + { + // Preformatting. Unfortunately, the tree widget doesn't have columns + display_info.Printf( wxT(" %s[ %s ]"), + ( cName.length() <= 8 ) ? wxT("\t\t") : wxT("\t"), + GetChars( a->GetDescription() ) ); + } + } + + // If there are no keywords, we give a couple of characters whitespace penalty. We want + // a component with a search-term found in the keywords score slightly higher than another + // component without keywords, but that term in the descriptions. + wxString search_text = ( !keywords.empty() ) ? keywords : wxT(" "); + search_text += descriptions; + nodes.push_back( new TREE_NODE( parent_node, c->GetLibrary(), + cName, display_info, search_text ) ); + } +} + + +LIB_COMPONENT* COMPONENT_TREE_SEARCH_CONTAINER::GetSelectedComponent() +{ + const wxTreeItemId& select_id = tree->GetSelection(); + BOOST_FOREACH( TREE_NODE* node, nodes ) + { + if ( node->match_score > 0 && node->tree_id == select_id && node->lib ) + return node->lib->FindComponent( node->name ); + } + return NULL; +} + + +// Creates a score depending on the position of a string match. If the position +// is 0 (= prefix match), this returns the maximum score. This degrades until +// pos == max, which returns a score of 0; +// Evertyhing else beyond that is just 0. Only values >= 0 allowed for position and max. +// +// @param aPosition is the position a string has been found in a substring. +// @param aMaximum is the maximum score this function returns. +// @return position dependent score. +static int matchPosScore(int aPosition, int aMaximum) +{ + return ( aPosition < aMaximum ) ? aMaximum - aPosition : 0; +} + + +void COMPONENT_TREE_SEARCH_CONTAINER::UpdateSearchTerm( const wxString& aSearch ) +{ + if ( tree == NULL ) + return; + + // We score the list by going through it several time, essentially with a complexity + // of O(n). For the default library of 2000+ items, this typically takes less than 5ms + // on an i5. Good enough, no index needed. + + // Initial AND condition: Leaf nodes are considered to match initially. + BOOST_FOREACH( TREE_NODE* node, nodes ) + { + node->previous_score = node->match_score; + node->match_score = node->parent ? kLowestDefaultScore : 0; // start-match for leafs. + } + + // Create match scores for each node for all the terms, that come space-separated. + // Scoring adds up values for each term according to importance of the match. If a term does + // not match at all, the result is thrown out of the results (AND semantics). + // From high to low + // - Exact match for a ccmponent name gives the highest score, trumping all. + // - A positional score depending of where a term is found as substring; prefix-match: high. + // - substring-match in library name. + // - substring match in keywords and descriptions with positional score. Keywords come + // first so contribute more to the score. + // + // This is of course subject to tweaking. + wxStringTokenizer tokenizer( aSearch ); + + while ( tokenizer.HasMoreTokens() ) + { + const wxString term = tokenizer.GetNextToken().Lower(); + BOOST_FOREACH( TREE_NODE* node, nodes ) + { + if ( node->parent == NULL) + continue; // Library nodes are not scored here. + + if ( node->match_score == 0) + continue; // Leaf node without score are out of the game. + + // Keywords and description we only count if the match string is at + // least two characters long. That avoids spurious, low quality + // matches. Most abbreviations are at three characters long. + int found_pos; + + if ( term == node->match_name ) + node->match_score += 1000; // exact match. High score :) + else if ( (found_pos = node->match_name.Find( term ) ) != wxNOT_FOUND ) + { + // Substring match. The earlier in the string the better. score += 20..40 + node->match_score += matchPosScore( found_pos, 20 ) + 20; + } + else if ( node->parent->match_name.Find( term ) != wxNOT_FOUND ) + node->match_score += 19; // parent name matches. score += 19 + else if ( ( found_pos = node->search_text.Find( term ) ) != wxNOT_FOUND ) + { + // If we have a very short string (like one or two letters), we don't want + // to accumulate scores if they just happen to be in keywords or description. + // For longer terms, we add scores 1..18 for positional match (higher in the + // front, where the keywords are). score += 0..18 + node->match_score += ( ( term.length() >= 2 ) + ? matchPosScore( found_pos, 17 ) + 1 + : 0 ); + } + else + node->match_score = 0; // No match. That's it for this item. + } + } + + // Parent nodes have the maximum score seen in any of their children. + unsigned highest_score_seen = 0; + bool any_change = false; + BOOST_FOREACH( TREE_NODE* node, nodes ) + { + if ( node->parent == NULL ) + continue; + + any_change |= (node->previous_score != node->match_score); + node->parent->match_score = std::max( node->parent->match_score, node->match_score ); + highest_score_seen = std::max( highest_score_seen, node->match_score ); + } + + + // The tree update might be slow, so we want to bail out if there is no change. + if ( !any_change ) + return; + + // Now: sort all items according to match score, libraries first. + std::sort( nodes.begin(), nodes.end(), scoreComparator ); + + // Fill the tree with all items that have a match. Re-arranging, adding and removing changed + // items is pretty complex, so we just re-build the whole tree. + tree->Freeze(); + tree->DeleteAllItems(); + wxTreeItemId root = tree->AddRoot( wxEmptyString ); + const TREE_NODE* first_match = NULL; + const TREE_NODE* preselected_node = NULL; + BOOST_FOREACH( TREE_NODE* node, nodes ) + { + if ( node->match_score == 0 ) + continue; + + // If we have nodes that go beyond the default score, suppress nodes that + // have the default score. That can happen if they have an honary += 0 score due to + // some one-letter match in the keyword or description. In this case, we prefer matches + // that just have higher scores. Improves confusion (and performance as the tree has to + // display less items). + if ( highest_score_seen > kLowestDefaultScore && node->match_score == kLowestDefaultScore ) + continue; + + wxString node_text; +#if 0 + // Node text with scoring information for debugging + node_text.Printf( wxT("%s (s=%u)%s"), GetChars(node->name), + node->match_score, GetChars(node->display_info)); +#else + node_text = node->name + node->display_info; +#endif + node->tree_id = tree->AppendItem( ( node->parent == NULL ) ? root : node->parent->tree_id, + node_text); + + // If we are a child node, we might need to expand. + if ( node->parent != NULL ) + { + if ( node->match_score > kLowestDefaultScore ) + { + tree->EnsureVisible( node->tree_id ); + + if ( first_match == NULL ) + first_match = node; // The "I am feeling lucky" element. + } + + if ( preselected_node == NULL && node->match_name == preselect_node_name ) + preselected_node = node; + } + + if ( node->parent == NULL && node->normally_expanded ) + tree->Expand( node->tree_id ); + } + + if ( first_match ) // Highest score search match pre-selected. + tree->SelectItem( first_match->tree_id ); + else if ( preselected_node ) // No search, so history item preselected. + tree->SelectItem( preselected_node->tree_id ); + + tree->Thaw(); +} diff --git a/eeschema/component_tree_search_container.h b/eeschema/component_tree_search_container.h new file mode 100644 index 0000000000..b89d1b2c6b --- /dev/null +++ b/eeschema/component_tree_search_container.h @@ -0,0 +1,108 @@ +/* -*- c++ -*- + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 Henner Zeller + * Copyright (C) 2014 KiCad Developers, see change_log.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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http: *www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http: *www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +class LIB_COMPONENT; +class CMP_LIBRARY; +class wxTreeCtrl; +class wxArrayString; + +// class COMPONENT_TREE_SEARCH_CONTAINER +// A container for components that allows to search them matching their name, keywords +// and descripotions, updating a wxTreeCtrl with the results (toplevel nodes: +// libraries, leafs: components), scored by relevance. +// +// The scored result list is adpated on each update on the search-term: this allows +// to have a search-as-you-type experience. +class COMPONENT_TREE_SEARCH_CONTAINER +{ +public: + COMPONENT_TREE_SEARCH_CONTAINER(); + ~COMPONENT_TREE_SEARCH_CONTAINER(); + + /** Function AddLibrary + * Add the components of this library to be searched. + * To be called in the setup phase to fill this container. + * + * @param aLib containting all the components to be added. + */ + void AddLibrary( CMP_LIBRARY& aLib ); + + /** Function AddComponentList + * Add the given list of components, given by name, to be searched. + * To be called in the setup phase to fill this container. + * + * @param aNodeName The parent node name the components will show up as leaf. + * @param aComponentNameList List of component names. + * @param aOptionalLib Library to look up the component names (if NULL: global lookup) + * @param aNormallyExpanded Should the node in the tree be expanded by default. + */ + void AddComponentList( const wxString& aNodeName, const wxArrayString& aComponentNameList, + CMP_LIBRARY* aOptionalLib, bool aNormallyExpanded ); + + /** Function SetPreselectNode + * Set the component name to be selected in absence of any search-result. + * + * @param aComponentName the component name to be selected. + */ + void SetPreselectNode( const wxString& aComponentName ); + + /** Function SetTree + * Set the tree to be manipulated. + * Each update of the search term will update the tree, with the most + * scoring component at the top and selected. If a preselect node is set, this + * is displayed. Does not take ownership of the tree. + * + * @param aTree that is to be modified on search updates. + */ + void SetTree( wxTreeCtrl* aTree ); + + /** Function UpdateSearchTerm + * Update the search string provided by the user and narrow down the result list. + * + * This string is a space-separated list of terms, each of which + * is applied to the components list to narrow it down. Results are scored by + * relevancy (e.g. exact match scores higher than prefix-match which in turn scores + * higher than substring match). This updates the search and tree on each call. + * + * @param aSearch is the user-provided search string. + */ + void UpdateSearchTerm( const wxString& aSearch ); + + /** Function GetSelectedComponent + * + * @return the selected component or NULL if there is none. + */ + LIB_COMPONENT* GetSelectedComponent(); + +private: + struct TREE_NODE; + static bool scoreComparator( const TREE_NODE* a1, const TREE_NODE* a2 ); + + std::vector nodes; + wxString preselect_node_name; + wxTreeCtrl* tree; +}; diff --git a/eeschema/dialogs/dialog_choose_component.cpp b/eeschema/dialogs/dialog_choose_component.cpp new file mode 100644 index 0000000000..9d5ad8cd64 --- /dev/null +++ b/eeschema/dialogs/dialog_choose_component.cpp @@ -0,0 +1,275 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 Henner Zeller + * Copyright (C) 2014 KiCad Developers, see change_log.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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +#include + +#include +#include +#include + +#include +#include +#include + +// Work-around broken implementation in wxWidgets <=2.8 tree. +static wxTreeItemId fixedGetPrevVisible( const wxTreeCtrl& tree, const wxTreeItemId& item ); + +// Combine descriptions of all aliases from given component. +static wxString combineDescriptions( LIB_COMPONENT* aComponent ) +{ + std::set descriptions; + + for( size_t i = 0; i < aComponent->GetAliasCount(); ++i ) + { + LIB_ALIAS* a = aComponent->GetAlias( i ); + + if ( !a->GetDescription().empty() ) + descriptions.insert( a->GetDescription() ); + } + + wxString result; + BOOST_FOREACH( const wxString& s, descriptions ) + result += s + wxT("\n"); + return result; +} + + +// Combine keywords. Keywords come as a string, but are considered space-separated +// individual words. Return a string with a unique set of these. +static wxString combineKeywords( LIB_COMPONENT* aComponent ) +{ + std::set keywords; + + for( size_t i = 0; i < aComponent->GetAliasCount(); ++i ) + { + LIB_ALIAS* a = aComponent->GetAlias( i ); + wxStringTokenizer tokenizer( a->GetKeyWords() ); + + while ( tokenizer.HasMoreTokens() ) + keywords.insert( tokenizer.GetNextToken() ); + } + + wxString result; + BOOST_FOREACH( const wxString& s, keywords ) + result += s + wxT(" "); + return result; +} + + +DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( wxWindow* aParent, const wxString& aTitle, + COMPONENT_TREE_SEARCH_CONTAINER* aContainer ) + : DIALOG_CHOOSE_COMPONENT_BASE( aParent, wxID_ANY, aTitle ), + m_search_container( aContainer ), + m_selected_component( NULL ), + m_external_browser_requested( false ), + m_received_doubleclick_in_tree( false ) +{ + // TODO: restore last size user was choosing. + m_search_container->SetTree( m_libraryComponentTree ); + m_searchBox->SetFocus(); + m_componentDetails->SetEditable( false ); +} + + +// After this dialog is done: return the component that has been selected, or an +// empty string if there is none. +wxString DIALOG_CHOOSE_COMPONENT::GetSelectedComponentName() const +{ + if ( m_selected_component == NULL ) + return wxEmptyString; + + return m_selected_component->GetName(); +} + + +void DIALOG_CHOOSE_COMPONENT::OnSearchBoxChange( wxCommandEvent& aEvent ) +{ + m_selected_component = NULL; + m_search_container->UpdateSearchTerm( m_searchBox->GetLineText(0) ); + updateSelection(); +} + + +void DIALOG_CHOOSE_COMPONENT::OnSearchBoxEnter( wxCommandEvent& aEvent ) +{ + EndModal( wxID_OK ); // We are done. +} + + +void DIALOG_CHOOSE_COMPONENT::SelectIfValid( const wxTreeItemId& aTreeId ) +{ + if ( aTreeId.IsOk() && aTreeId != m_libraryComponentTree->GetRootItem() ) + m_libraryComponentTree->SelectItem( aTreeId ); +} + + +void DIALOG_CHOOSE_COMPONENT::OnInterceptSearchBoxKey( wxKeyEvent& aKeyStroke ) +{ + // Cursor up/down are forwarded to the tree. This is done by intercepting some navigational + // keystrokes that normally would go to the text search box (which has the focus by default). + const wxTreeItemId sel = m_libraryComponentTree->GetSelection(); + + switch ( aKeyStroke.GetKeyCode() ) + { + case WXK_UP: + SelectIfValid( fixedGetPrevVisible( *m_libraryComponentTree, sel ) ); + break; + + case WXK_DOWN: + SelectIfValid( m_libraryComponentTree->GetNextVisible( sel ) ); + break; + + default: + aKeyStroke.Skip(); // Pass on to search box. + break; + } +} + + +void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxTreeEvent& aEvent ) +{ + updateSelection(); +} + + +void DIALOG_CHOOSE_COMPONENT::OnDoubleClickTreeSelect( wxTreeEvent& aEvent ) +{ + updateSelection(); + + if ( m_selected_component == NULL ) + return; + + // Ok, got selection. We don't just end the modal dialog here, but + // wait for the MouseUp event to occur. Otherwise something (broken?) + // happens: the dialog will close and will deliver the 'MouseUp' event + // to the eeschema canvas, that will immediately place the component. + m_received_doubleclick_in_tree = true; +} + + +void DIALOG_CHOOSE_COMPONENT::OnTreeMouseUp( wxMouseEvent& aMouseEvent ) +{ + if ( m_received_doubleclick_in_tree ) + EndModal( wxID_OK ); // We are done (see OnDoubleClickTreeSelect) + else + aMouseEvent.Skip(); // Let upstream handle it. +} + + +void DIALOG_CHOOSE_COMPONENT::OnStartComponentBrowser( wxMouseEvent& aEvent ) +{ + m_external_browser_requested = true; + EndModal( wxID_OK ); // We are done. +} + + +void DIALOG_CHOOSE_COMPONENT::updateSelection() +{ + LIB_COMPONENT* selection = m_search_container->GetSelectedComponent(); + + if ( selection == m_selected_component ) + return; // no change. + + m_selected_component = selection; + + m_componentDetails->Clear(); + + if ( m_selected_component == NULL ) + return; + + wxFont font_normal = m_componentDetails->GetFont(); + wxFont font_bold = m_componentDetails->GetFont(); + font_bold.SetWeight( wxFONTWEIGHT_BOLD ); + + wxTextAttr headline_attribute; + headline_attribute.SetFont(font_bold); + wxTextAttr text_attribute; + text_attribute.SetFont(font_normal); + + const wxString description = combineDescriptions( selection ); + + if ( !description.empty() ) + { + m_componentDetails->SetDefaultStyle( headline_attribute ); + m_componentDetails->AppendText( _("Description\n") ); + m_componentDetails->SetDefaultStyle( text_attribute ); + m_componentDetails->AppendText( description ); + m_componentDetails->AppendText( wxT("\n") ); + } + + const wxString keywords = combineKeywords( selection ); + + if ( !keywords.empty() ) + { + m_componentDetails->SetDefaultStyle( headline_attribute ); + m_componentDetails->AppendText( _("Keywords\n") ); + m_componentDetails->SetDefaultStyle( text_attribute ); + m_componentDetails->AppendText( keywords ); + } + + m_componentDetails->SetInsertionPoint( 0 ); // scroll up. +} + + +// The GetPrevVisible() method is broken in at least 2.8 wxWidgets. This is mostly copied +// verbatim from the latest 3.x source in which this is fixed. +// ( wxWidgets/src/generic/treectlg.cpp ) +static wxTreeItemId fixedGetPrevVisible( const wxTreeCtrl& tree, const wxTreeItemId& item ) +{ +#if wxCHECK_VERSION( 3, 0, 0 ) + return tree.GetPrevVisible(item); +#else + // find out the starting point + wxTreeItemId prevItem = tree.GetPrevSibling(item); + + if ( !prevItem.IsOk() ) + { + prevItem = tree.GetItemParent(item); + } + + // find the first visible item after it + while ( prevItem.IsOk() && !tree.IsVisible(prevItem) ) + { + prevItem = tree.GetNext(prevItem); + + if ( !prevItem.IsOk() || prevItem == item ) + { + // there are no visible items before item + return wxTreeItemId(); + } + } + + // from there we must be able to navigate until this item + while ( prevItem.IsOk() ) + { + const wxTreeItemId nextItem = tree.GetNextVisible(prevItem); + + if ( !nextItem.IsOk() || nextItem == item ) + break; + + prevItem = nextItem; + } + + return prevItem; +#endif +} diff --git a/eeschema/dialogs/dialog_choose_component.h b/eeschema/dialogs/dialog_choose_component.h new file mode 100644 index 0000000000..3f02b74ef6 --- /dev/null +++ b/eeschema/dialogs/dialog_choose_component.h @@ -0,0 +1,69 @@ +/* -*- c++ -*- + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 Henner Zeller + * Copyright (C) 2014 KiCad Developers, see change_log.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 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 + +class COMPONENT_TREE_SEARCH_CONTAINER; +class LIB_COMPONENT; +class wxTreeItemId; + +class DIALOG_CHOOSE_COMPONENT : public DIALOG_CHOOSE_COMPONENT_BASE +{ +public: + DIALOG_CHOOSE_COMPONENT( wxWindow* aParent, const wxString& aTitle, + COMPONENT_TREE_SEARCH_CONTAINER* aSearch_container ); + + /** Function GetSelectedComponentName + * To be called after this dialog returns from ShowModal(). + * + * @return the component that has been selected, or an empty string if there is none. + */ + wxString GetSelectedComponentName() const; + + /** Function IsExternalBrowserSelected + * + * @return true, iff the browser pressed the browsing button. + */ + bool IsExternalBrowserSelected() const { return m_external_browser_requested; } + +protected: + virtual void OnSearchBoxChange( wxCommandEvent& aEvent ); + virtual void OnSearchBoxEnter( wxCommandEvent& aEvent ); + virtual void OnInterceptSearchBoxKey( wxKeyEvent& aEvent ); + + virtual void OnTreeSelect( wxTreeEvent& aEvent ); + virtual void OnDoubleClickTreeSelect( wxTreeEvent& aEvent ); + virtual void OnTreeMouseUp( wxMouseEvent& aMouseEvent ); + + virtual void OnStartComponentBrowser( wxMouseEvent& aEvent ); + +private: + void updateSelection(); + void SelectIfValid( const wxTreeItemId& aTreeId ); + + COMPONENT_TREE_SEARCH_CONTAINER* const m_search_container; + LIB_COMPONENT* m_selected_component; + bool m_external_browser_requested; + bool m_received_doubleclick_in_tree; +}; diff --git a/eeschema/dialogs/dialog_choose_component_base.cpp b/eeschema/dialogs/dialog_choose_component_base.cpp new file mode 100644 index 0000000000..a9334549ba --- /dev/null +++ b/eeschema/dialogs/dialog_choose_component_base.cpp @@ -0,0 +1,109 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Nov 6 2013) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "dialog_choose_component_base.h" + +/////////////////////////////////////////////////////////////////////////// + +DIALOG_CHOOSE_COMPONENT_BASE::DIALOG_CHOOSE_COMPONENT_BASE( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : DIALOG_SHIM( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxSize( 450,-1 ), wxDefaultSize ); + + wxBoxSizer* bSizer1; + bSizer1 = new wxBoxSizer( wxVERTICAL ); + + wxBoxSizer* bSearchSizer; + bSearchSizer = new wxBoxSizer( wxHORIZONTAL ); + + m_searchLabel = new wxStaticText( this, wxID_ANY, wxT("Search"), wxDefaultPosition, wxDefaultSize, 0 ); + m_searchLabel->Wrap( -1 ); + bSearchSizer->Add( m_searchLabel, 0, wxALL, 5 ); + + m_searchBox = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER ); + bSearchSizer->Add( m_searchBox, 1, wxALL, 5 ); + + + bSizer1->Add( bSearchSizer, 0, wxEXPAND, 5 ); + + m_libraryComponentTree = new wxTreeCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE|wxTR_HIDE_ROOT ); + m_libraryComponentTree->SetMinSize( wxSize( -1,50 ) ); + + bSizer1->Add( m_libraryComponentTree, 2, wxALL|wxEXPAND, 5 ); + + wxBoxSizer* bSizer3; + bSizer3 = new wxBoxSizer( wxHORIZONTAL ); + + m_componentView = new wxStaticText( this, wxID_ANY, wxT("TODO\n(mini. comp image)"), wxDefaultPosition, wxSize( 100,100 ), 0 ); + m_componentView->Wrap( -1 ); + m_componentView->SetMinSize( wxSize( 100,100 ) ); + + bSizer3->Add( m_componentView, 0, wxALL, 5 ); + + wxBoxSizer* bSizer6; + bSizer6 = new wxBoxSizer( wxVERTICAL ); + + m_componentDetails = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,100 ), wxTE_MULTILINE ); + bSizer6->Add( m_componentDetails, 1, wxALL|wxEXPAND, 5 ); + + m_unitChoice = new wxComboBox( this, wxID_ANY, wxT("Unit A"), wxDefaultPosition, wxDefaultSize, 0, NULL, 0 ); + m_unitChoice->Enable( false ); + m_unitChoice->Hide(); + + bSizer6->Add( m_unitChoice, 0, wxALL, 5 ); + + + bSizer3->Add( bSizer6, 2, wxEXPAND, 5 ); + + + bSizer1->Add( bSizer3, 1, wxEXPAND, 5 ); + + wxBoxSizer* bSizer5; + bSizer5 = new wxBoxSizer( wxHORIZONTAL ); + + + bSizer5->Add( 0, 0, 1, wxEXPAND, 5 ); + + m_button = new wxStdDialogButtonSizer(); + m_buttonOK = new wxButton( this, wxID_OK ); + m_button->AddButton( m_buttonOK ); + m_buttonCancel = new wxButton( this, wxID_CANCEL ); + m_button->AddButton( m_buttonCancel ); + m_button->Realize(); + + bSizer5->Add( m_button, 0, wxEXPAND, 5 ); + + + bSizer1->Add( bSizer5, 0, wxEXPAND, 5 ); + + + this->SetSizer( bSizer1 ); + this->Layout(); + + this->Centre( wxBOTH ); + + // Connect Events + m_searchBox->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnInterceptSearchBoxKey ), NULL, this ); + m_searchBox->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxChange ), NULL, this ); + m_searchBox->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxEnter ), NULL, this ); + m_libraryComponentTree->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeMouseUp ), NULL, this ); + m_libraryComponentTree->Connect( wxEVT_COMMAND_TREE_ITEM_ACTIVATED, wxTreeEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnDoubleClickTreeSelect ), NULL, this ); + m_libraryComponentTree->Connect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeSelect ), NULL, this ); + m_componentView->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnStartComponentBrowser ), NULL, this ); +} + +DIALOG_CHOOSE_COMPONENT_BASE::~DIALOG_CHOOSE_COMPONENT_BASE() +{ + // Disconnect Events + m_searchBox->Disconnect( wxEVT_KEY_DOWN, wxKeyEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnInterceptSearchBoxKey ), NULL, this ); + m_searchBox->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxChange ), NULL, this ); + m_searchBox->Disconnect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnSearchBoxEnter ), NULL, this ); + m_libraryComponentTree->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeMouseUp ), NULL, this ); + m_libraryComponentTree->Disconnect( wxEVT_COMMAND_TREE_ITEM_ACTIVATED, wxTreeEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnDoubleClickTreeSelect ), NULL, this ); + m_libraryComponentTree->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnTreeSelect ), NULL, this ); + m_componentView->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( DIALOG_CHOOSE_COMPONENT_BASE::OnStartComponentBrowser ), NULL, this ); + +} diff --git a/eeschema/dialogs/dialog_choose_component_base.fbp b/eeschema/dialogs/dialog_choose_component_base.fbp new file mode 100644 index 0000000000..73cdcb2f83 --- /dev/null +++ b/eeschema/dialogs/dialog_choose_component_base.fbp @@ -0,0 +1,710 @@ + + + + + + C++ + 1 + source_name + 0 + 0 + res + UTF-8 + connect + dialog_choose_component_base + 1000 + none + 0 + dialog_choose_component_base + + . + + 1 + 1 + 1 + 1 + UI + 0 + 0 + + 0 + wxAUI_MGR_DEFAULT + + wxBOTH + + 1 + 1 + impl_virtual + + + + 0 + wxID_ANY + + 450,-1 + DIALOG_CHOOSE_COMPONENT_BASE + + 450,500 + wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER + DIALOG_SHIM; dialog_shim.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bSizer1 + wxVERTICAL + none + + 5 + wxEXPAND + 0 + + + bSearchSizer + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + Search + + 0 + + + 0 + + 1 + m_searchLabel + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + -1,-1 + + + 0 + + 1 + m_searchBox + 1 + + + protected + 1 + + Resizable + 1 + + wxTE_PROCESS_ENTER + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + OnInterceptSearchBoxKey + + + + + + + + + + + + + + + + + + + OnSearchBoxChange + OnSearchBoxEnter + + + + + + + + + 5 + wxALL|wxEXPAND + 2 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + -1,50 + 1 + m_libraryComponentTree + 1 + + + protected + 1 + + Resizable + 1 + + wxTR_DEFAULT_STYLE|wxTR_HIDE_ROOT + + 0 + + + + + + + + + + + + + + OnTreeMouseUp + + + + + + + + + + + + + + + + + + + + OnDoubleClickTreeSelect + + + + + + + + + + OnTreeSelect + + + + + + + + 5 + wxEXPAND + 1 + + + bSizer3 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + TODO (mini. comp image) + + 0 + + + 0 + 100,100 + 1 + m_componentView + 1 + + + protected + 1 + + Resizable + 1 + 100,100 + + + 0 + + + + + -1 + + + + + + + + + + OnStartComponentBrowser + + + + + + + + + + + + + + + + + 5 + wxEXPAND + 2 + + + bSizer6 + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + + 0 + -1,100 + 1 + m_componentDetails + 1 + + + protected + 1 + + Resizable + 1 + -1,-1 + wxTE_MULTILINE + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + + + + + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 0 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_unitChoice + 1 + + + protected + 1 + + Resizable + -1 + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + Unit A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + wxALIGN_RIGHT + 0 + + + bSizer5 + wxVERTICAL + none + + 5 + wxEXPAND + 0 + + 0 + 1 + 0 + 0 + 0 + 1 + 0 + 0 + + m_button + protected + + + + + + + + + + + + + + + + diff --git a/eeschema/dialogs/dialog_choose_component_base.h b/eeschema/dialogs/dialog_choose_component_base.h new file mode 100644 index 0000000000..005aceb9cd --- /dev/null +++ b/eeschema/dialogs/dialog_choose_component_base.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Nov 6 2013) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#ifndef __DIALOG_CHOOSE_COMPONENT_BASE_H__ +#define __DIALOG_CHOOSE_COMPONENT_BASE_H__ + +#include +#include +class DIALOG_SHIM; + +#include "dialog_shim.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class DIALOG_CHOOSE_COMPONENT_BASE +/////////////////////////////////////////////////////////////////////////////// +class DIALOG_CHOOSE_COMPONENT_BASE : public DIALOG_SHIM +{ + private: + + protected: + wxStaticText* m_searchLabel; + wxTextCtrl* m_searchBox; + wxTreeCtrl* m_libraryComponentTree; + wxStaticText* m_componentView; + wxTextCtrl* m_componentDetails; + wxComboBox* m_unitChoice; + wxStdDialogButtonSizer* m_button; + wxButton* m_buttonOK; + wxButton* m_buttonCancel; + + // Virtual event handlers, overide them in your derived class + virtual void OnInterceptSearchBoxKey( wxKeyEvent& event ) { event.Skip(); } + virtual void OnSearchBoxChange( wxCommandEvent& event ) { event.Skip(); } + virtual void OnSearchBoxEnter( wxCommandEvent& event ) { event.Skip(); } + virtual void OnTreeMouseUp( wxMouseEvent& event ) { event.Skip(); } + virtual void OnDoubleClickTreeSelect( wxTreeEvent& event ) { event.Skip(); } + virtual void OnTreeSelect( wxTreeEvent& event ) { event.Skip(); } + virtual void OnStartComponentBrowser( wxMouseEvent& event ) { event.Skip(); } + + + public: + + DIALOG_CHOOSE_COMPONENT_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxEmptyString, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 450,500 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER ); + ~DIALOG_CHOOSE_COMPONENT_BASE(); + +}; + +#endif //__DIALOG_CHOOSE_COMPONENT_BASE_H__ diff --git a/eeschema/getpart.cpp b/eeschema/getpart.cpp index a423d2ea72..4554e4f4be 100644 --- a/eeschema/getpart.cpp +++ b/eeschema/getpart.cpp @@ -45,11 +45,14 @@ #include #include +#include +#include #include #include +// TODO(hzeller): would be good if we could give a pre-selected component. wxString SCH_BASE_FRAME::SelectComponentFromLibBrowser( void ) { wxSemaphore semaphore( 0, 1 ); @@ -76,118 +79,66 @@ wxString SCH_BASE_FRAME::SelectComponentFromLibBrowser( void ) return cmpname; } - wxString SCH_BASE_FRAME::SelectComponentFromLibrary( const wxString& aLibname, wxArrayString& aHistoryList, bool aUseLibBrowser, int* aUnit, int* aConvert ) { - int CmpCount = 0; - LIB_COMPONENT* libEntry = NULL; - CMP_LIBRARY* currLibrary = NULL; - wxString cmpName, keys, msg; - bool allowWildSeach = true; + int cmpCount = 0; + wxString dialogTitle; + + COMPONENT_TREE_SEARCH_CONTAINER search_container; // Container doing search-as-you-type if( !aLibname.IsEmpty() ) { - currLibrary = CMP_LIBRARY::FindLibrary( aLibname ); + CMP_LIBRARY* currLibrary = CMP_LIBRARY::FindLibrary( aLibname ); if( currLibrary != NULL ) - CmpCount = currLibrary->GetCount(); + { + cmpCount = currLibrary->GetCount(); + search_container.AddLibrary( *currLibrary ); + } } else { BOOST_FOREACH( CMP_LIBRARY& lib, CMP_LIBRARY::GetLibraryList() ) { - CmpCount += lib.GetCount(); + cmpCount += lib.GetCount(); + search_container.AddLibrary( lib ); } } - // Ask for a component name or key words - msg.Printf( _( "Component selection (%d items loaded):" ), CmpCount ); + if( !aHistoryList.empty() ) + { + // This is good for a transition for experineced users: giving them a History. Ideally, + // we actually make this part even faster to access with a popup on ALT-a or something. + search_container.AddComponentList( _("-- History --"), aHistoryList, NULL, true ); + search_container.SetPreselectNode( aHistoryList[0] ); + } - DIALOG_GET_COMPONENT dlg( this, aHistoryList, msg, aUseLibBrowser ); - - if( aHistoryList.GetCount() ) - dlg.SetComponentName( aHistoryList[0] ); + dialogTitle.Printf( _( "Choose Component (%d items loaded)" ), cmpCount ); + DIALOG_CHOOSE_COMPONENT dlg( this, dialogTitle, &search_container ); if( dlg.ShowModal() == wxID_CANCEL ) return wxEmptyString; - if( dlg.m_GetExtraFunction ) + wxString cmpName = dlg.GetSelectedComponentName(); + + if( dlg.IsExternalBrowserSelected() ) { - cmpName = SelectComponentFromLibBrowser(); + cmpName = SelectComponentFromLibBrowser(); // Would be good if we could pre-select. + if( aUnit ) *aUnit = LIB_VIEW_FRAME::GetUnit(); + if( aConvert ) *aConvert = LIB_VIEW_FRAME::GetConvert(); - if( !cmpName.IsEmpty() ) - AddHistoryComponentName( aHistoryList, cmpName ); - return cmpName; - } - else - cmpName = dlg.GetComponentName(); - - if( cmpName.IsEmpty() ) - return wxEmptyString; - - // Here, cmpName contains the component name, - // or "*" if the Select All dialog button was pressed - -#ifndef KICAD_KEEPCASE - cmpName.MakeUpper(); -#endif - - if( dlg.IsKeyword() ) - { - allowWildSeach = false; - keys = cmpName; - cmpName = DataBaseGetName( this, keys, cmpName ); - - if( cmpName.IsEmpty() ) - return wxEmptyString; - } - else if( cmpName == wxT( "*" ) ) - { - allowWildSeach = false; - - if( GetNameOfPartToLoad( this, currLibrary, cmpName ) == 0 ) - return wxEmptyString; - } - else if( cmpName.Contains( wxT( "?" ) ) || cmpName.Contains( wxT( "*" ) ) ) - { - allowWildSeach = false; - cmpName = DataBaseGetName( this, keys, cmpName ); - - if( cmpName.IsEmpty() ) - return wxEmptyString; } - libEntry = CMP_LIBRARY::FindLibraryComponent( cmpName, aLibname ); + if ( !cmpName.empty() ) + AddHistoryComponentName( aHistoryList, cmpName ); - if( !libEntry && allowWildSeach ) // Search with wildcard - { - allowWildSeach = false; - wxString wildname = wxChar( '*' ) + cmpName + wxChar( '*' ); - cmpName = wildname; - cmpName = DataBaseGetName( this, keys, cmpName ); - - if( !cmpName.IsEmpty() ) - libEntry = CMP_LIBRARY::FindLibraryComponent( cmpName, aLibname ); - - if( !libEntry ) - return wxEmptyString; - } - - if( !libEntry ) - { - msg.Printf( _( "Failed to find part <%s> in library" ), GetChars( cmpName ) ); - DisplayError( this, msg ); - return wxEmptyString; - } - - AddHistoryComponentName( aHistoryList, cmpName ); return cmpName; }