kicad/common/grid_tricks.cpp

363 lines
9.9 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
* Copyright (C) 2012 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 <fctsys.h>
#include <grid_tricks.h>
#include <wx/tokenzr.h>
#include <wx/arrstr.h>
#include <wx/clipbrd.h>
#include <algorithm>
// It works for table data on clipboard for an Excell spreadsheet,
// why not us too for now.
#define COL_SEP wxT( '\t' )
#define ROW_SEP wxT( '\n' )
enum
{
MYID_FIRST = -1,
MYID_CUT,
MYID_COPY,
MYID_PASTE,
MYID_SELECT,
MYID_LAST,
};
GRID_TRICKS::GRID_TRICKS( wxGrid* aGrid ):
m_grid( aGrid )
{
m_sel_row_start = 0;
m_sel_col_start = 0;
m_sel_row_count = 0;
m_sel_col_count = 0;
aGrid->Connect( wxEVT_GRID_CELL_LEFT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridCellLeftClick ), NULL, this );
aGrid->Connect( wxEVT_GRID_CELL_LEFT_DCLICK, wxGridEventHandler( GRID_TRICKS::onGridCellLeftClick ), NULL, this );
aGrid->Connect( wxEVT_GRID_CELL_RIGHT_CLICK, wxGridEventHandler( GRID_TRICKS::onGridCellRightClick ), NULL, this );
aGrid->Connect( MYID_FIRST, MYID_LAST, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler( GRID_TRICKS::onPopupSelection ), NULL, this );
aGrid->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( GRID_TRICKS::onKeyDown ), NULL, this );
aGrid->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( GRID_TRICKS::onRightDown ), NULL, this );
}
bool GRID_TRICKS::toggleCell( int aRow, int aCol )
{
auto renderer = m_grid->GetCellRenderer( aRow, aCol );
bool isCheckbox = ( dynamic_cast<wxGridCellBoolRenderer*>( renderer ) != nullptr );
renderer->DecRef();
if( isCheckbox )
{
wxGridTableBase* model = m_grid->GetTable();
if( model->CanGetValueAs( aRow, aCol, wxGRID_VALUE_BOOL )
&& model->CanSetValueAs( aRow, aCol, wxGRID_VALUE_BOOL ))
{
model->SetValueAsBool( aRow, aCol, !model->GetValueAsBool( aRow, aCol ));
}
else // fall back to string processing
{
if( model->GetValue( aRow, aCol ) == wxT( "1" ) )
model->SetValue( aRow, aCol, wxT( "0" ) );
else
model->SetValue( aRow, aCol, wxT( "1" ) );
}
// Mac needs this for the keyboard events; Linux appears to always need it.
m_grid->ForceRefresh();
// Let any clients know
wxGridEvent event( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, aRow, aCol );
event.SetString( model->GetValue( aRow, aCol ) );
m_grid->GetEventHandler()->ProcessEvent( event );
return true;
}
return false;
}
void GRID_TRICKS::onGridCellLeftClick( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
int col = aEvent.GetCol();
// Don't make users click twice to toggle a checkbox
if( !aEvent.GetModifiers() && toggleCell( row, col ) )
{
m_grid->ClearSelection();
m_grid->SetGridCursor( row, col );
// eat event
}
else
{
aEvent.Skip();
}
}
void GRID_TRICKS::getSelectedArea()
{
wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
wxGridCellCoordsArray botRight = m_grid->GetSelectionBlockBottomRight();
wxArrayInt cols = m_grid->GetSelectedCols();
wxArrayInt rows = m_grid->GetSelectedRows();
DBG(printf("topLeft.Count():%d botRight:Count():%d\n", int( topLeft.Count() ), int( botRight.Count() ) );)
if( topLeft.Count() && botRight.Count() )
{
m_sel_row_start = topLeft[0].GetRow();
m_sel_col_start = topLeft[0].GetCol();
m_sel_row_count = botRight[0].GetRow() - m_sel_row_start + 1;
m_sel_col_count = botRight[0].GetCol() - m_sel_col_start + 1;
}
else if( cols.Count() )
{
m_sel_col_start = cols[0];
m_sel_col_count = cols.Count();
m_sel_row_start = 0;
m_sel_row_count = m_grid->GetNumberRows();
}
else if( rows.Count() )
{
m_sel_col_start = 0;
m_sel_col_count = m_grid->GetNumberCols();
m_sel_row_start = rows[0];
m_sel_row_count = rows.Count();
}
else
{
m_sel_row_start = -1;
m_sel_col_start = -1;
m_sel_row_count = 0;
m_sel_col_count = 0;
}
//DBG(printf("m_sel_row_start:%d m_sel_col_start:%d m_sel_row_count:%d m_sel_col_count:%d\n", m_sel_row_start, m_sel_col_start, m_sel_row_count, m_sel_col_count );)
}
void GRID_TRICKS::showPopupMenu()
{
wxMenu menu;
menu.Append( MYID_CUT, _( "Cut\tCTRL+X" ), _( "Clear selected cells pasting original contents to clipboard" ) );
menu.Append( MYID_COPY, _( "Copy\tCTRL+C" ), _( "Copy selected cells to clipboard" ) );
menu.Append( MYID_PASTE, _( "Paste\tCTRL+V" ), _( "Paste clipboard cells to matrix at current cell" ) );
menu.Append( MYID_SELECT, _( "Select All\tCTRL+A" ), _( "Select all cells" ) );
getSelectedArea();
// if nothing is selected, disable cut and copy.
if( !m_sel_row_count && !m_sel_col_count )
{
menu.Enable( MYID_CUT, false );
menu.Enable( MYID_COPY, false );
}
bool have_cb_text = false;
if( wxTheClipboard->Open() )
{
if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
have_cb_text = true;
wxTheClipboard->Close();
}
if( !have_cb_text )
{
// if nothing on clipboard, disable paste.
menu.Enable( MYID_PASTE, false );
}
m_grid->PopupMenu( &menu );
}
void GRID_TRICKS::onPopupSelection( wxCommandEvent& event )
{
int menu_id = event.GetId();
// assume getSelectedArea() was called by rightClickPopupMenu() and there's
// no way to have gotten here without that having been called.
switch( menu_id )
{
case MYID_CUT:
case MYID_COPY:
cutcopy( menu_id == MYID_CUT );
break;
case MYID_PASTE:
paste_clipboard();
break;
case MYID_SELECT:
m_grid->SelectAll();
break;
default:
;
}
}
void GRID_TRICKS::onKeyDown( wxKeyEvent& ev )
{
if( isCtl( 'A', ev ) )
{
m_grid->SelectAll();
return;
}
else if( isCtl( 'C', ev ) )
{
getSelectedArea();
cutcopy( false );
return;
}
else if( isCtl( 'V', ev ) )
{
getSelectedArea();
paste_clipboard();
return;
}
else if( isCtl( 'X', ev ) )
{
getSelectedArea();
cutcopy( true );
return;
}
else if( ev.GetKeyCode() == ' ' )
{
int row = getCursorRow();
int col = getCursorCol();
if( m_grid->IsVisible( row, col ) && toggleCell( row, col ) )
return;
}
ev.Skip( true );
}
void GRID_TRICKS::paste_clipboard()
{
if( wxTheClipboard->Open() )
{
if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
{
wxTextDataObject data;
wxTheClipboard->GetData( data );
wxString cb_text = data.GetText();
paste_text( cb_text );
}
wxTheClipboard->Close();
m_grid->ForceRefresh();
}
}
void GRID_TRICKS::paste_text( const wxString& cb_text )
{
wxGridTableBase* tbl = m_grid->GetTable();
const int cur_row = std::max( getCursorRow(), 0 ); // no -1
const int cur_col = std::max( getCursorCol(), 0 );
wxStringTokenizer rows( cb_text, ROW_SEP, wxTOKEN_RET_EMPTY );
// if clipboard rows would extend past end of current table size...
if( int( rows.CountTokens() ) > tbl->GetNumberRows() - cur_row )
{
int newRowsNeeded = rows.CountTokens() - ( tbl->GetNumberRows() - cur_row );
tbl->AppendRows( newRowsNeeded );
}
for( int row = cur_row; rows.HasMoreTokens(); ++row )
{
wxString rowTxt = rows.GetNextToken();
wxStringTokenizer cols( rowTxt, COL_SEP, wxTOKEN_RET_EMPTY );
for( int col = cur_col; cols.HasMoreTokens(); ++col )
{
wxString cellTxt = cols.GetNextToken();
tbl->SetValue( row, col, cellTxt );
}
}
m_grid->AutoSizeColumns( false );
}
void GRID_TRICKS::cutcopy( bool doCut )
{
if( wxTheClipboard->Open() )
{
wxGridTableBase* tbl = m_grid->GetTable();
wxString txt;
// fill txt with a format that is compatible with most spreadsheets
for( int row = m_sel_row_start; row < m_sel_row_start + m_sel_row_count; ++row )
{
for( int col = m_sel_col_start; col < m_sel_col_start + m_sel_col_count; ++col )
{
txt += tbl->GetValue( row, col );
if( col < m_sel_col_start + m_sel_col_count - 1 ) // that was not last column
txt += COL_SEP;
if( doCut )
tbl->SetValue( row, col, wxEmptyString );
}
txt += ROW_SEP;
}
wxTheClipboard->SetData( new wxTextDataObject( txt ) );
wxTheClipboard->Close();
if( doCut )
{
m_grid->AutoSizeColumns( false );
m_grid->ForceRefresh();
}
}
}