diff --git a/common/grid_tricks.cpp b/common/grid_tricks.cpp index 10235bbfbc..39314cb0c2 100644 --- a/common/grid_tricks.cpp +++ b/common/grid_tricks.cpp @@ -23,6 +23,8 @@ */ #include +#include +#include #include #include #include @@ -58,6 +60,10 @@ GRID_TRICKS::GRID_TRICKS( WX_GRID* aGrid ): aGrid->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( GRID_TRICKS::onKeyDown ), nullptr, this ); aGrid->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( GRID_TRICKS::onUpdateUI ), nullptr, this ); + + // The handlers that control the tooltips must be on the actual grid window, not the grid + aGrid->GetGridWindow()->Connect( wxEVT_MOTION, + wxMouseEventHandler( GRID_TRICKS::onGridMotion ), nullptr, this ); } @@ -165,6 +171,36 @@ void GRID_TRICKS::onGridCellLeftDClick( wxGridEvent& aEvent ) } +void GRID_TRICKS::onGridMotion( wxMouseEvent& aEvent ) +{ + // Always skip the event + aEvent.Skip(); + + wxPoint pt = aEvent.GetPosition(); + wxPoint pos = m_grid->CalcScrolledPosition( wxPoint( pt.x, pt.y ) ); + + int col = m_grid->XToCol( pos.x ); + + // Skip the event if the tooltip shouldn't be shown + if( !m_tooltipEnabled[col] || ( col == wxNOT_FOUND ) ) + { + m_grid->GetGridWindow()->SetToolTip( "" ); + return; + } + + int row = m_grid->YToRow( pos.y ); + + if( row == wxNOT_FOUND ) + { + m_grid->GetGridWindow()->SetToolTip( "" ); + return; + } + + // Set the tooltip to the string contained in the cell + m_grid->GetGridWindow()->SetToolTip( m_grid->GetCellValue( row, col ) ); +} + + bool GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent ) { // Double-click processing must be handled by specific sub-classes diff --git a/common/widgets/grid_icon_text_helpers.cpp b/common/widgets/grid_icon_text_helpers.cpp index 7bc0c2b629..8dfd1d2dff 100644 --- a/common/widgets/grid_icon_text_helpers.cpp +++ b/common/widgets/grid_icon_text_helpers.cpp @@ -23,6 +23,8 @@ #include +#include +#include #include #include @@ -121,6 +123,61 @@ wxGridCellRenderer* GRID_CELL_ICON_RENDERER::Clone() const } +//---- Grid helpers: custom wxGridCellRenderer that renders just an icon ---------------- +// +// Note: this renderer is supposed to be used with read only cells + +GRID_CELL_STATUS_ICON_RENDERER::GRID_CELL_STATUS_ICON_RENDERER( int aStatus ) : + m_status( aStatus ) +{ + if( m_status != 0 ) + { + m_bitmap = wxArtProvider::GetBitmap( wxArtProvider::GetMessageBoxIconId( m_status ), + wxART_BUTTON ); + } + else + { + // Dummy bitmap for size + m_bitmap = wxArtProvider::GetBitmap( wxArtProvider::GetMessageBoxIconId( wxICON_INFORMATION ), + wxART_BUTTON ); + } +} + + +void GRID_CELL_STATUS_ICON_RENDERER::Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, + const wxRect& aRect, int aRow, int aCol, + bool isSelected ) +{ + wxRect rect = aRect; + rect.Inflate( -1 ); + + // erase background + wxGridCellRenderer::Draw( aGrid, aAttr, aDC, aRect, aRow, aCol, isSelected ); + + // Draw icon + if( ( m_status != 0 ) && m_bitmap.IsOk() ) + { + aDC.DrawBitmap( m_bitmap, + rect.GetLeft() + ( rect.GetWidth() - m_bitmap.GetWidth() ) / 2, + rect.GetTop() + ( rect.GetHeight() - m_bitmap.GetHeight() ) / 2, + true ); + } +} + + +wxSize GRID_CELL_STATUS_ICON_RENDERER::GetBestSize( wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, + int row, int col ) +{ + return wxSize( m_bitmap.GetWidth() + 6, m_bitmap.GetHeight() + 4 ); +} + + +wxGridCellRenderer* GRID_CELL_STATUS_ICON_RENDERER::Clone() const +{ + return new GRID_CELL_STATUS_ICON_RENDERER( m_status ); +} + + GRID_CELL_ICON_TEXT_POPUP::GRID_CELL_ICON_TEXT_POPUP( const std::vector& icons, const wxArrayString& names ) : m_icons( icons ), diff --git a/include/grid_tricks.h b/include/grid_tricks.h index df15a3c5d3..658ade0cb0 100644 --- a/include/grid_tricks.h +++ b/include/grid_tricks.h @@ -26,11 +26,14 @@ #define _GRID_TRICKS_H_ +#include #include #include #include #include +#define GRIDTRICKS_MAX_COL 20 + enum { GRIDTRICKS_FIRST_ID = 901, @@ -40,9 +43,9 @@ enum GRIDTRICKS_ID_PASTE, GRIDTRICKS_ID_SELECT, - GRIDTRICKS_FIRST_SHOWHIDE = 979, // reserve 20 IDs for show/hide-column-n + GRIDTRICKS_FIRST_SHOWHIDE = 979, // reserve IDs for show/hide-column-n - GRIDTRICKS_LAST_ID = 999 + GRIDTRICKS_LAST_ID = GRIDTRICKS_FIRST_SHOWHIDE + GRIDTRICKS_MAX_COL }; @@ -54,6 +57,30 @@ class GRID_TRICKS : public wxEvtHandler public: explicit GRID_TRICKS( WX_GRID* aGrid ); + /** + * Enable the tooltip for a column. + * + * The tooltip is read from the string contained in the cell data. + * + * @param aCol is the column to use + * @param aEnable is true to enable the tooltip (default) + */ + void SetTooltipEnable( int aCol, bool aEnable = true ) + { + m_tooltipEnabled[aCol] = aEnable; + } + + /** + * Query if the tooltip for a column is enabled + * + * @param aCol is the column to query + * @return if the tooltip is enabled for the column + */ + bool GetTooltipEnabled( int aCol ) + { + return m_tooltipEnabled[aCol]; + } + protected: /// Puts the selected area into a sensible rectangle of m_sel_{row,col}_{start,count} above. void getSelectedArea(); @@ -66,6 +93,7 @@ protected: void onPopupSelection( wxCommandEvent& event ); void onKeyDown( wxKeyEvent& ev ); void onUpdateUI( wxUpdateUIEvent& event ); + void onGridMotion( wxMouseEvent& event ); virtual bool handleDoubleClick( wxGridEvent& aEvent ); virtual void showPopupMenu( wxMenu& menu ); @@ -86,6 +114,8 @@ protected: int m_sel_col_start; int m_sel_row_count; int m_sel_col_count; + + std::bitset m_tooltipEnabled; }; #endif // _GRID_TRICKS_H_ diff --git a/include/widgets/grid_icon_text_helpers.h b/include/widgets/grid_icon_text_helpers.h index b01985bbdf..120df0f141 100644 --- a/include/widgets/grid_icon_text_helpers.h +++ b/include/widgets/grid_icon_text_helpers.h @@ -24,6 +24,7 @@ #ifndef GRID_ICON_TEXT_HELPERS_H #define GRID_ICON_TEXT_HELPERS_H +#include #include #include #include @@ -67,6 +68,27 @@ private: const wxBitmap& m_icon; }; +//---- Grid helpers: custom wxGridCellRenderer that renders just an icon from wxArtprovider - +// +// Note: use with read only cells + +class GRID_CELL_STATUS_ICON_RENDERER : public wxGridCellRenderer +{ +public: + GRID_CELL_STATUS_ICON_RENDERER( int aStatus ); + + void Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, + const wxRect& aRect, int aRow, int aCol, bool isSelected ) override; + wxSize GetBestSize( wxGrid & grid, wxGridCellAttr & attr, wxDC & dc, int row, int col ) override; + wxGridCellRenderer* Clone() const override; + +private: + int m_status; + wxBitmap m_bitmap; +}; + + + //---- Grid helpers: custom wxGridCellEditor ------------------------------------------ // // Note: this implementation is an adaptation of wxGridCellChoiceEditor diff --git a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp index 3b947b1d65..4d91037f2e 100644 --- a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp +++ b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,12 @@ // Remember the last open page during session. int DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::m_page = 0; +enum MODELS_TABLE_COLUMNS +{ + COL_PROBLEM = 0, + COL_FILENAME = 1, + COL_SHOWN = 2 +}; DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR( FOOTPRINT_EDIT_FRAME* aParent, @@ -87,7 +94,11 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR( m_itemsGrid->SetTable( m_texts ); m_itemsGrid->PushEventHandler( new GRID_TRICKS( m_itemsGrid ) ); - m_modelsGrid->PushEventHandler( new GRID_TRICKS( m_modelsGrid ) ); + + GRID_TRICKS* trick = new GRID_TRICKS( m_modelsGrid ); + trick->SetTooltipEnable( COL_PROBLEM ); + + m_modelsGrid->PushEventHandler( trick ); // Show/hide columns according to the user's preference m_itemsGrid->ShowHideColumns( m_frame->GetSettings()->m_FootprintTextShownColumns ); @@ -99,17 +110,23 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR( wxGetEnv( KICAD6_3DMODEL_DIR, &cfg->m_lastFootprint3dDir ); } + // Icon showing warning/error information wxGridCellAttr* attr = new wxGridCellAttr; + attr->SetReadOnly(); + m_modelsGrid->SetColAttr( COL_PROBLEM, attr ); + + // Filename + attr = new wxGridCellAttr; attr->SetEditor( new GRID_CELL_PATH_EDITOR( this, m_modelsGrid, &cfg->m_lastFootprint3dDir, "*.*", true, Prj().GetProjectPath() ) ); - m_modelsGrid->SetColAttr( 0, attr ); + m_modelsGrid->SetColAttr( COL_FILENAME, attr ); // Show checkbox attr = new wxGridCellAttr; attr->SetRenderer( new wxGridCellBoolRenderer() ); attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER ); - m_modelsGrid->SetColAttr( 1, attr ); + m_modelsGrid->SetColAttr( COL_SHOWN, attr ); m_modelsGrid->SetWindowStyleFlag( m_modelsGrid->GetWindowStyle() & ~wxHSCROLL ); aParent->Prj().Get3DCacheManager()->GetResolver()->SetProgramBase( &Pgm() ); @@ -313,8 +330,11 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataToWindow() m_modelsGrid->AppendRows( 1 ); int row = m_modelsGrid->GetNumberRows() - 1; - m_modelsGrid->SetCellValue( row, 0, origPath ); - m_modelsGrid->SetCellValue( row, 1, model.m_Show ? wxT( "1" ) : wxT( "0" ) ); + m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath ); + m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) ); + + // Must be after the filename is set + updateValidateStatus( row ); } select3DModel( 0 ); // will clamp idx within bounds @@ -344,7 +364,7 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataToWindow() } m_itemsGrid->SetRowLabelSize( m_itemsGrid->GetVisibleWidth( -1, true, true, true ) ); - m_modelsGrid->SetColSize( 1, m_modelsGrid->GetVisibleWidth( 1, true, false, false ) ); + m_modelsGrid->SetColSize( COL_SHOWN, m_modelsGrid->GetVisibleWidth( COL_SHOWN, true, false, false ) ); Layout(); adjustGridColumns( m_itemsGrid->GetRect().GetWidth() ); @@ -363,7 +383,7 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::select3DModel( int aModelIdx ) if( m_modelsGrid->GetNumberRows() ) { m_modelsGrid->SelectRow( aModelIdx ); - m_modelsGrid->SetGridCursor( aModelIdx, 0 ); + m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME ); } m_previewPane->SetSelectedModel( aModelIdx ); @@ -381,11 +401,11 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::On3DModelSelected( wxGridEvent& aEve void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::On3DModelCellChanged( wxGridEvent& aEvent ) { - if( aEvent.GetCol() == 0 ) + if( aEvent.GetCol() == COL_FILENAME ) { bool hasAlias = false; FILENAME_RESOLVER* res = Prj().Get3DCacheManager()->GetResolver(); - wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), 0 ); + wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME ); filename.Replace( "\n", "" ); filename.Replace( "\r", "" ); @@ -411,11 +431,13 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::On3DModelCellChanged( wxGridEvent& a #endif m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename; - m_modelsGrid->SetCellValue( aEvent.GetRow(), 0, filename ); + m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename ); + + updateValidateStatus( aEvent.GetRow() ); } - else if( aEvent.GetCol() == 1 ) + else if( aEvent.GetCol() == COL_SHOWN ) { - wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), 1 ); + wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN ); m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) ); } @@ -501,8 +523,10 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnAdd3DModel( wxCommandEvent& ) int idx = m_modelsGrid->GetNumberRows(); m_modelsGrid->AppendRows( 1 ); - m_modelsGrid->SetCellValue( idx, 0, filename ); - m_modelsGrid->SetCellValue( idx, 1, wxT( "1" ) ); + m_modelsGrid->SetCellValue( idx, COL_FILENAME, filename ); + m_modelsGrid->SetCellValue( idx, COL_SHOWN, wxT( "1" ) ); + + updateValidateStatus( idx ); select3DModel( idx ); m_previewPane->UpdateDummyFootprint(); @@ -521,13 +545,14 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnAdd3DRow( wxCommandEvent& ) int row = m_modelsGrid->GetNumberRows(); m_modelsGrid->AppendRows( 1 ); - m_modelsGrid->SetCellValue( row, 1, wxT( "1" ) ); + m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) ); + m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" ); select3DModel( row ); m_modelsGrid->SetFocus(); - m_modelsGrid->MakeCellVisible( row, 0 ); - m_modelsGrid->SetGridCursor( row, 0 ); + m_modelsGrid->MakeCellVisible( row, COL_FILENAME ); + m_modelsGrid->SetGridCursor( row, COL_FILENAME ); m_modelsGrid->EnableCellEditControl( true ); m_modelsGrid->ShowCellEditControl(); @@ -552,6 +577,59 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::checkFootprintName( const wxString& } +void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::updateValidateStatus( int aRow ) +{ + int icon = 0; + wxString errStr; + + switch( validateModelExists( m_modelsGrid->GetCellValue( aRow, COL_FILENAME) ) ) + { + case MODEL_VALIDATE_ERRORS::NO_ERROR: + icon = 0; + errStr = ""; + break; + + case MODEL_VALIDATE_ERRORS::RESOLVE_FAIL: + icon = wxICON_ERROR; + errStr = _( "File not found" ); + break; + + case MODEL_VALIDATE_ERRORS::OPEN_FAIL: + icon = wxICON_ERROR; + errStr = _( "Unable to open file" ); + break; + + default: + icon = wxICON_ERROR; + errStr = _( "Unknown error" ); + break; + } + + m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr ); + m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM, + new GRID_CELL_STATUS_ICON_RENDERER( icon ) ); +} + + +MODEL_VALIDATE_ERRORS DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::validateModelExists( const wxString& aFilename ) +{ + FILENAME_RESOLVER* resolv = Prj().Get3DFilenameResolver(); + + if( !resolv ) + return MODEL_VALIDATE_ERRORS::RESOLVE_FAIL; + + wxString fullPath = resolv->ResolvePath( aFilename ); + + if( fullPath.IsEmpty() ) + return MODEL_VALIDATE_ERRORS::RESOLVE_FAIL; + + if( wxFileName::IsFileReadable( fullPath ) ) + return MODEL_VALIDATE_ERRORS::NO_ERROR; + else + return MODEL_VALIDATE_ERRORS::OPEN_FAIL; +} + + bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::Validate() { if( !m_itemsGrid->CommitPendingChanges() ) @@ -829,10 +907,15 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::adjustGridColumns( int aWidth ) itemsWidth -= m_itemsGrid->GetColSize( i ); if( itemsWidth > 0 ) + { m_itemsGrid->SetColSize( 0, std::max( itemsWidth, m_itemsGrid->GetVisibleWidth( 0, true, false, false ) ) ); + } - m_modelsGrid->SetColSize( 0, modelsWidth - m_modelsGrid->GetColSize( 1 ) - 5 ); + int width = modelsWidth - m_modelsGrid->GetColSize( COL_SHOWN ) + - m_modelsGrid->GetColSize( COL_PROBLEM ) - 5; + + m_modelsGrid->SetColSize( COL_FILENAME, width ); } diff --git a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.h b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.h index da36064a0b..a8076255e3 100644 --- a/pcbnew/dialogs/dialog_footprint_properties_fp_editor.h +++ b/pcbnew/dialogs/dialog_footprint_properties_fp_editor.h @@ -36,6 +36,13 @@ class PANEL_PREVIEW_3D_MODEL; class FOOTPRINT_EDIT_FRAME; +enum class MODEL_VALIDATE_ERRORS +{ + NO_ERROR, + RESOLVE_FAIL, + OPEN_FAIL +}; + class DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR : public DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE { public: @@ -62,6 +69,10 @@ private: void OnDeleteField( wxCommandEvent& event ) override; void OnUpdateUI( wxUpdateUIEvent& event ) override; + void updateValidateStatus( int aRow ); + + MODEL_VALIDATE_ERRORS validateModelExists( const wxString& aFilename ); + bool checkFootprintName( const wxString& aFootprintName ); void select3DModel( int aModelIdx ); diff --git a/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.cpp b/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.cpp index a6440fa778..ada23c1bac 100644 --- a/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.cpp +++ b/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.cpp @@ -341,20 +341,22 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITO m_modelsGrid = new WX_GRID( sbSizer3->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE ); // Grid - m_modelsGrid->CreateGrid( 3, 2 ); + m_modelsGrid->CreateGrid( 3, 3 ); m_modelsGrid->EnableEditing( true ); m_modelsGrid->EnableGridLines( false ); m_modelsGrid->EnableDragGridSize( false ); m_modelsGrid->SetMargins( 0, 0 ); // Columns - m_modelsGrid->SetColSize( 0, 650 ); - m_modelsGrid->SetColSize( 1, 65 ); + m_modelsGrid->SetColSize( 0, 20 ); + m_modelsGrid->SetColSize( 1, 650 ); + m_modelsGrid->SetColSize( 2, 65 ); m_modelsGrid->EnableDragColMove( false ); m_modelsGrid->EnableDragColSize( false ); m_modelsGrid->SetColLabelSize( 22 ); - m_modelsGrid->SetColLabelValue( 0, _("3D Model(s)") ); - m_modelsGrid->SetColLabelValue( 1, _("Show") ); + m_modelsGrid->SetColLabelValue( 0, wxEmptyString ); + m_modelsGrid->SetColLabelValue( 1, _("3D Model(s)") ); + m_modelsGrid->SetColLabelValue( 2, _("Show") ); m_modelsGrid->SetColLabelAlignment( wxALIGN_LEFT, wxALIGN_CENTER ); // Rows diff --git a/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.fbp b/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.fbp index cd46e03ffd..df7eabd4b7 100644 --- a/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.fbp +++ b/pcbnew/dialogs/dialog_footprint_properties_fp_editor_base.fbp @@ -2888,10 +2888,10 @@ 1 wxALIGN_LEFT 22 - "3D Model(s)" "Show" + "" "3D Model(s)" "Show" wxALIGN_CENTER - 2 - 650,65 + 3 + 20,650,65 1 0