diff options
Diffstat (limited to 'eeschema/dialogs/dialog_choose_component.cpp')
-rw-r--r-- | eeschema/dialogs/dialog_choose_component.cpp | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/eeschema/dialogs/dialog_choose_component.cpp b/eeschema/dialogs/dialog_choose_component.cpp new file mode 100644 index 0000000..3183c1d --- /dev/null +++ b/eeschema/dialogs/dialog_choose_component.cpp @@ -0,0 +1,409 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2014 Henner Zeller <h.zeller@acm.org> + * 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 <dialog_choose_component.h> + +#include <set> +#include <wx/tokenzr.h> + +#include <class_library.h> +#include <component_tree_search_container.h> +#include <sch_base_frame.h> + +// Tree navigation helpers. +static wxTreeItemId GetPrevItem( const wxTreeCtrl& tree, const wxTreeItemId& item ); +static wxTreeItemId GetNextItem( const wxTreeCtrl& tree, const wxTreeItemId& item ); + +DIALOG_CHOOSE_COMPONENT::DIALOG_CHOOSE_COMPONENT( SCH_BASE_FRAME* aParent, const wxString& aTitle, + COMPONENT_TREE_SEARCH_CONTAINER* const aContainer, + int aDeMorganConvert ) + : DIALOG_CHOOSE_COMPONENT_BASE( aParent, wxID_ANY, aTitle ), m_search_container( aContainer ) +{ + m_parent = aParent; + m_deMorganConvert = aDeMorganConvert >= 0 ? aDeMorganConvert : 0; + m_external_browser_requested = false; + m_received_doubleclick_in_tree = false; + m_search_container->SetTree( m_libraryComponentTree ); + m_searchBox->SetFocus(); + m_componentDetails->SetEditable( false ); + m_componentView->SetLayoutDirection( wxLayout_LeftToRight ); + + m_libraryComponentTree->ScrollTo( m_libraryComponentTree->GetFocusedItem() ); + + // The tree showing libs and component uses a fixed font, + // because we want controle the position of some info when drawing the + // tree. Using tabs does not work very well (does not work on Windows) + wxFont font = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ); + m_libraryComponentTree->SetFont( wxFont( font.GetPointSize(), + wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL ) ); + + // We have to call SetSizeHints to fix the minimal size of the dialog + // and its widgets. + // this line also fixes an issue on Linux Ubuntu using Unity (dialog not shown). + GetSizer()->SetSizeHints( this ); + + Centre(); +} + + +DIALOG_CHOOSE_COMPONENT::~DIALOG_CHOOSE_COMPONENT() +{ + m_search_container->SetTree( NULL ); +} + + +LIB_ALIAS* DIALOG_CHOOSE_COMPONENT::GetSelectedAlias( int* aUnit ) const +{ + return m_search_container->GetSelectedAlias( aUnit ); +} + + +void DIALOG_CHOOSE_COMPONENT::OnSearchBoxChange( wxCommandEvent& aEvent ) +{ + m_search_container->UpdateSearchTerm( m_searchBox->GetLineText( 0 ) ); + updateSelection(); + + // On Windows, but not on Linux, the focus is given to + // the m_libraryComponentTree, after modificatuons. + // We want the focus for m_searchBox. + // + // We cannot call SetFocus on Linux because it changes the current text selection + // and the text edit cursor position. +#ifdef __WINDOWS__ + m_searchBox->SetFocus(); +#endif +} + + +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 and partiallyi cursor are use to do tree navigation operations. + // This is done by intercepting some navigational keystrokes that normally would go to + // the text search box (which has the focus by default). That way, we are mostly keyboard + // operable. + // (If the tree has the focus, it can handle that by itself). + const wxTreeItemId sel = m_libraryComponentTree->GetSelection(); + + switch( aKeyStroke.GetKeyCode() ) + { + case WXK_UP: + selectIfValid( GetPrevItem( *m_libraryComponentTree, sel ) ); + break; + + case WXK_DOWN: + selectIfValid( GetNextItem( *m_libraryComponentTree, sel ) ); + break; + + // The following keys we can only hijack if they are not needed by the textbox itself. + + case WXK_LEFT: + if( m_searchBox->GetInsertionPoint() == 0 ) + m_libraryComponentTree->Collapse( sel ); + else + aKeyStroke.Skip(); // Use for original purpose: move cursor. + break; + + case WXK_RIGHT: + if( m_searchBox->GetInsertionPoint() >= (long) m_searchBox->GetLineText( 0 ).length() ) + m_libraryComponentTree->Expand( sel ); + else + aKeyStroke.Skip(); // Use for original purpose: move cursor. + break; + + default: + aKeyStroke.Skip(); // Any other key: pass on to search box directly. + break; + } +} + + +void DIALOG_CHOOSE_COMPONENT::OnTreeSelect( wxTreeEvent& aEvent ) +{ + updateSelection(); +} + + +// Test strategy for OnDoubleClickTreeActivation()/OnTreeMouseUp() work around wxWidgets bug: +// - search for an item. +// - use the mouse to double-click on an item in the tree. +// -> The dialog should close, and the component should _not_ be immediately placed +void DIALOG_CHOOSE_COMPONENT::OnDoubleClickTreeActivation( wxTreeEvent& aEvent ) +{ + if( !updateSelection() ) + 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. +} + +// Test strategy to see if OnInterceptTreeEnter() works: +// - search for an item. +// - click into the tree once to set focus on tree; navigate. Press 'Enter' +// -> The dialog should close and the component be available to place. +void DIALOG_CHOOSE_COMPONENT::OnInterceptTreeEnter( wxKeyEvent& aEvent ) +{ + // We have to do some special handling for double-click on a tree-item because + // of some superfluous event delivery bug in wxWidgets (see OnDoubleClickTreeActivation()). + // In tree-activation, we assume we got a double-click and need to take special precaution + // that the mouse-up event is not delivered to the window one level up by going through + // a state-sequence OnDoubleClickTreeActivation() -> OnTreeMouseUp(). + + // Pressing 'Enter' within a tree will also call OnDoubleClickTreeActivation(), + // but since this is not due to the double-click and we have no way of knowing that it is + // not, we need to intercept the 'Enter' key before that to know that it is time to exit. + if( aEvent.GetKeyCode() == WXK_RETURN ) + EndModal( wxID_OK ); // Dialog is done. + else + aEvent.Skip(); // Let tree handle that key for navigation. +} + + +void DIALOG_CHOOSE_COMPONENT::OnStartComponentBrowser( wxMouseEvent& aEvent ) +{ + m_external_browser_requested = true; + EndModal( wxID_OK ); // We are done. +} + + +bool DIALOG_CHOOSE_COMPONENT::updateSelection() +{ + int unit = 0; + LIB_ALIAS* selection = m_search_container->GetSelectedAlias( &unit ); + + m_componentView->Refresh(); + + m_componentDetails->Clear(); + + if( selection == NULL ) + return false; + + m_componentDetails->Freeze(); + 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 name = selection->GetName(); + + if ( !name.empty() ) + { + m_componentDetails->SetDefaultStyle( headline_attribute ); + m_componentDetails->AppendText( name ); + } + + const wxString description = selection->GetDescription(); + + if( !description.empty() ) + { + if ( !m_componentDetails->IsEmpty() ) + m_componentDetails->AppendText( wxT( "\n\n" ) ); + + m_componentDetails->SetDefaultStyle( headline_attribute ); + m_componentDetails->AppendText( _( "Description\n" ) ); + m_componentDetails->SetDefaultStyle( text_attribute ); + m_componentDetails->AppendText( description ); + } + + const wxString keywords = selection->GetKeyWords(); + + if( !keywords.empty() ) + { + if ( !m_componentDetails->IsEmpty() ) + m_componentDetails->AppendText( wxT( "\n\n" ) ); + + m_componentDetails->SetDefaultStyle( headline_attribute ); + m_componentDetails->AppendText( _( "Keywords\n" ) ); + m_componentDetails->SetDefaultStyle( text_attribute ); + m_componentDetails->AppendText( keywords ); + } + + if ( !selection->IsRoot() ) + { + LIB_PART* root_part = selection->GetPart(); + const wxString root_component_name( root_part ? root_part->GetName() : _( "Unknown" ) ); + + if ( !m_componentDetails->IsEmpty() ) + m_componentDetails->AppendText( wxT( "\n\n" ) ); + + m_componentDetails->SetDefaultStyle( headline_attribute ); + m_componentDetails->AppendText( _( "Alias of " ) ); + m_componentDetails->SetDefaultStyle( text_attribute ); + m_componentDetails->AppendText( root_component_name ); + } + + m_componentDetails->SetInsertionPoint( 0 ); // scroll up. + m_componentDetails->Thaw(); + + return true; +} + + +void DIALOG_CHOOSE_COMPONENT::OnHandlePreviewRepaint( wxPaintEvent& aRepaintEvent ) +{ + int unit = 0; + LIB_ALIAS* selection = m_search_container->GetSelectedAlias( &unit ); + LIB_PART* part = selection ? selection->GetPart() : NULL; + + // Don't draw anything (not even the background) if we don't have + // a part to show + if( !part ) + return; + + if( selection->IsRoot() ) + { + // just show the part directly + renderPreview( part, unit ); + } + else + { + // switch out the name temporarily for the alias name + wxString tmp( part->GetName() ); + part->SetName( selection->GetName() ); + + renderPreview( part, unit ); + + part->SetName( tmp ); + } +} + + +// Render the preview in our m_componentView. If this gets more complicated, we should +// probably have a derived class from wxPanel; but this keeps things local. +void DIALOG_CHOOSE_COMPONENT::renderPreview( LIB_PART* aComponent, int aUnit ) +{ + wxPaintDC dc( m_componentView ); + + const wxSize dc_size = dc.GetSize(); + + // Avoid rendering when either dimension is zero + if( dc_size.x == 0 || dc_size.y == 0 ) + return; + + GRResetPenAndBrush( &dc ); + + EDA_COLOR_T bgcolor = m_parent->GetDrawBgColor(); + + dc.SetBackground( bgcolor == BLACK ? *wxBLACK_BRUSH : *wxWHITE_BRUSH ); + dc.Clear(); + + if( aComponent == NULL ) + return; + + if( aUnit <= 0 ) + aUnit = 1; + + dc.SetDeviceOrigin( dc_size.x / 2, dc_size.y / 2 ); + + // Find joint bounding box for everything we are about to draw. + EDA_RECT bBox = aComponent->GetBoundingBox( aUnit, m_deMorganConvert ); + const double xscale = (double) dc_size.x / bBox.GetWidth(); + const double yscale = (double) dc_size.y / bBox.GetHeight(); + const double scale = std::min( xscale, yscale ) * 0.85; + + dc.SetUserScale( scale, scale ); + + wxPoint offset = -bBox.Centre(); + + + aComponent->Draw( NULL, &dc, offset, aUnit, m_deMorganConvert, GR_COPY, + UNSPECIFIED_COLOR, DefaultTransform, true, true, false ); +} + + +static wxTreeItemId GetPrevItem( const wxTreeCtrl& tree, const wxTreeItemId& item ) +{ + wxTreeItemId prevItem = tree.GetPrevSibling( item ); + + if( !prevItem.IsOk() ) + { + prevItem = tree.GetItemParent( item ); + } + else if( tree.IsExpanded( prevItem ) ) + { + prevItem = tree.GetLastChild( prevItem ); + } + + return prevItem; +} + + +static wxTreeItemId GetNextItem( const wxTreeCtrl& tree, const wxTreeItemId& item ) +{ + wxTreeItemId nextItem; + + if( !item.IsOk() ) + return nextItem; // item is not valid: return a not valid wxTreeItemId + + if( tree.IsExpanded( item ) ) + { + wxTreeItemIdValue dummy; + nextItem = tree.GetFirstChild( item, dummy ); + } + else + { + wxTreeItemId root_cell= tree.GetRootItem(); + + // Walk up levels until we find one that has a next sibling. + for ( wxTreeItemId walk = item; walk.IsOk(); walk = tree.GetItemParent( walk ) ) + { + if( walk == root_cell ) // the root cell (not displayed) is reached + break; // Exit (calling GetNextSibling( root_cell ) crashes. + + nextItem = tree.GetNextSibling( walk ); + + if( nextItem.IsOk() ) + break; + } + } + + return nextItem; +} |