dialog_fp_lib_table.cpp 28 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * 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
 */


Dick Hollenbeck's avatar
Dick Hollenbeck committed
26 27 28
/*  TODO:

*)  Grab text from any pending ChoiceEditor when OK button pressed.
29
*)  After any change to uri, reparse the environment variables.
Dick Hollenbeck's avatar
Dick Hollenbeck committed
30 31 32 33

*/


34
#include <wx/grid.h>
35 36
#include <wx/clipbrd.h>
#include <wx/tokenzr.h>
Dick Hollenbeck's avatar
Dick Hollenbeck committed
37 38 39
#include <wx/arrstr.h>
#include <wx/regex.h>
#include <set>
40

41 42 43 44 45 46
#include <fctsys.h>
#include <dialog_fp_lib_table_base.h>
#include <fp_lib_table.h>
#include <fp_lib_table_lexer.h>
#include <invoke_pcb_dialog.h>

47 48 49 50 51 52 53 54 55 56 57 58 59

/// grid column order is established by this sequence
enum COL_ORDER
{
    COL_NICKNAME,
    COL_URI,
    COL_TYPE,
    COL_OPTIONS,
    COL_DESCR,
    COL_COUNT       // keep as last
};


Dick Hollenbeck's avatar
Dick Hollenbeck committed
60 61 62
/**
 * Class FP_TBL_MODEL
 * mixes in wxGridTableBase into FP_LIB_TABLE so that the latter can be used
Dick Hollenbeck's avatar
Dick Hollenbeck committed
63
 * as a table within wxGrid.
Dick Hollenbeck's avatar
Dick Hollenbeck committed
64
 */
65 66 67 68 69 70
class FP_TBL_MODEL : public wxGridTableBase, public FP_LIB_TABLE
{
public:

    /**
     * Constructor FP_TBL_MODEL
Dick Hollenbeck's avatar
Dick Hollenbeck committed
71 72
     * is a copy constructor that builds a wxGridTableBase (table model) by wrapping
     * an FP_LIB_TABLE.
73 74 75 76 77 78 79 80
     */
    FP_TBL_MODEL( const FP_LIB_TABLE& aTableToEdit ) :
        FP_LIB_TABLE( aTableToEdit )    // copy constructor
    {
    }

    //-----<wxGridTableBase overloads>-------------------------------------------

81 82
    int         GetNumberRows()     { return rows.size(); }
    int         GetNumberCols()     { return COL_COUNT; }
83 84 85 86 87 88 89 90 91

    wxString    GetValue( int aRow, int aCol )
    {
        if( unsigned( aRow ) < rows.size() )
        {
            const ROW&  r  = rows[aRow];

            switch( aCol )
            {
92 93 94 95 96
            case COL_NICKNAME:  return r.GetNickName();
            case COL_URI:       return r.GetFullURI();
            case COL_TYPE:      return r.GetType();
            case COL_OPTIONS:   return r.GetOptions();
            case COL_DESCR:     return r.GetDescr();
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
            default:
                ;       // fall thru to wxEmptyString
            }
        }

        return wxEmptyString;
    }

    void    SetValue( int aRow, int aCol, const wxString &aValue )
    {
        if( unsigned( aRow ) < rows.size() )
        {
            ROW&  r  = rows[aRow];

            switch( aCol )
            {
113 114 115 116 117
            case COL_NICKNAME:  r.SetNickName( aValue );    break;
            case COL_URI:       r.SetFullURI( aValue );     break;
            case COL_TYPE:      r.SetType( aValue  );       break;
            case COL_OPTIONS:   r.SetOptions( aValue );     break;
            case COL_DESCR:     r.SetDescr( aValue );       break;
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
            }
        }
    }

    bool IsEmptyCell( int aRow, int aCol )
    {
        if( unsigned( aRow ) < rows.size() )
            return false;
        return true;
    }

    bool InsertRows( size_t aPos = 0, size_t aNumRows = 1 )
    {
        if( aPos < rows.size() )
        {
            rows.insert( rows.begin() + aPos, aNumRows, ROW() );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
134 135 136 137 138 139 140 141 142 143 144 145

            // use the (wxGridStringTable) source Luke.
            if( GetView() )
            {
                wxGridTableMessage msg( this,
                                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
                                        aPos,
                                        aNumRows );

                GetView()->ProcessTableMessage( msg );
            }

146 147 148 149 150 151 152
            return true;
        }
        return false;
    }

    bool AppendRows( size_t aNumRows = 1 )
    {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
153 154
        // do not modify aNumRows, original value needed for wxGridTableMessage below
        for( int i = aNumRows; i; --i )
155
            rows.push_back( ROW() );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
156 157 158 159 160 161 162 163 164 165

        if( GetView() )
        {
            wxGridTableMessage msg( this,
                                    wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
                                    aNumRows );

            GetView()->ProcessTableMessage( msg );
        }

166 167 168 169 170 171 172 173 174
        return true;
    }

    bool DeleteRows( size_t aPos, size_t aNumRows )
    {
        if( aPos + aNumRows <= rows.size() )
        {
            ROWS_ITER start = rows.begin() + aPos;
            rows.erase( start, start + aNumRows );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
175 176 177 178 179 180 181 182 183 184 185

            if( GetView() )
            {
                wxGridTableMessage msg( this,
                                        wxGRIDTABLE_NOTIFY_ROWS_DELETED,
                                        aPos,
                                        aNumRows );

                GetView()->ProcessTableMessage( msg );
            }

186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
            return true;
        }
        return false;
    }

    void Clear()
    {
        rows.clear();
        nickIndex.clear();
    }

    wxString GetColLabelValue( int aCol )
    {
        switch( aCol )
        {
201 202
        case COL_NICKNAME:  return _( "Nickname" );
        case COL_URI:       return _( "Library Path" );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
203 204 205

        // keep this text fairly long so column is sized wide enough
        case COL_TYPE:      return _( "Plugin Type" );
206 207 208
        case COL_OPTIONS:   return _( "Options" );
        case COL_DESCR:     return _( "Description" );
        default:            return wxEmptyString;
209 210 211 212 213 214 215
        }
    }

    //-----</wxGridTableBase overloads>------------------------------------------
};


Dick Hollenbeck's avatar
Dick Hollenbeck committed
216
// It works for table data on clipboard for an Excell spreadsheet,
217 218 219 220
// why not us too for now.
#define COL_SEP     wxT( '\t' )
#define ROW_SEP     wxT( '\n' )

221

222 223 224 225 226 227
inline bool isCtl( int aChar, const wxKeyEvent& e )
{
    return e.GetKeyCode() == aChar && e.ControlDown() && !e.AltDown() && !e.ShiftDown() && !e.MetaDown();
}


228 229 230 231 232 233 234
/**
 * Class DIALOG_FP_LIB_TABLE
 * shows and edits the PCB library tables.  Two tables are expected, one global
 * and one project specific.
 */
class DIALOG_FP_LIB_TABLE : public DIALOG_FP_LIB_TABLE_BASE
{
Dick Hollenbeck's avatar
Dick Hollenbeck committed
235 236
    typedef FP_LIB_TABLE::ROW   ROW;

237 238
    enum
    {
239 240
        MYID_FIRST = -1,
        MYID_CUT,
241 242 243
        MYID_COPY,
        MYID_PASTE,
        MYID_SELECT,
244
        MYID_LAST,
245 246 247
    };

    // row & col "selection" acquisition
Dick Hollenbeck's avatar
Dick Hollenbeck committed
248 249 250 251 252 253
    // selected area by cell coordinate and count
    int selRowStart;
    int selColStart;
    int selRowCount;
    int selColCount;

254 255 256 257 258 259 260 261 262 263
    int getCursorRow() const
    {
        return m_cur_grid->GetGridCursorRow();
    }

    int getCursorCol() const
    {
        return m_cur_grid->GetGridCursorCol();
    }

264
    /// Gets the selected area into a sensible rectangle of sel{Row,Col}{Start,Count} above.
Dick Hollenbeck's avatar
Dick Hollenbeck committed
265 266 267 268 269
    void getSelectedArea()
    {
        wxGridCellCoordsArray topLeft  = m_cur_grid->GetSelectionBlockTopLeft();
        wxGridCellCoordsArray botRight = m_cur_grid->GetSelectionBlockBottomRight();

270 271 272 273 274
        wxArrayInt  cols = m_cur_grid->GetSelectedCols();
        wxArrayInt  rows = m_cur_grid->GetSelectedRows();

        D(printf("topLeft.Count():%zd botRight:Count():%zd\n", topLeft.Count(), botRight.Count() );)

Dick Hollenbeck's avatar
Dick Hollenbeck committed
275 276 277 278 279 280 281 282
        if( topLeft.Count() && botRight.Count() )
        {
            selRowStart = topLeft[0].GetRow();
            selColStart = topLeft[0].GetCol();

            selRowCount = botRight[0].GetRow() - selRowStart + 1;
            selColCount = botRight[0].GetCol() - selColStart + 1;
        }
283 284 285 286 287 288 289 290 291 292 293 294 295 296
        else if( cols.Count() )
        {
            selColStart = cols[0];
            selColCount = cols.Count();
            selRowStart = 0;
            selRowCount = m_cur_grid->GetNumberRows();
        }
        else if( rows.Count() )
        {
            selColStart = 0;
            selColCount = m_cur_grid->GetNumberCols();
            selRowStart = rows[0];
            selRowCount = rows.Count();
        }
Dick Hollenbeck's avatar
Dick Hollenbeck committed
297 298 299 300 301 302 303 304
        else
        {
            selRowStart = -1;
            selColStart = -1;
            selRowCount = 0;
            selColCount = 0;
        }

305
        D(printf("selRowStart:%d selColStart:%d selRowCount:%d selColCount:%d\n", selRowStart, selColStart, selRowCount, selColCount );)
306 307 308 309 310 311
    }

    void rightClickCellPopupMenu()
    {
        wxMenu      menu;

312 313 314 315
        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" ) );
316 317 318

        getSelectedArea();

319
        // if nothing is selected, disable cut and copy.
320 321
        if( !selRowCount && !selColCount )
        {
322 323
            menu.Enable( MYID_CUT,  false );
            menu.Enable( MYID_COPY, false );
324 325
        }

326 327 328 329 330 331 332 333 334 335 336 337
        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.
338
            menu.Enable( MYID_PASTE, false );
339 340
        }

341 342 343 344 345
        PopupMenu( &menu );

        // passOnFocus();
    }

346 347
    void cutcopy( bool doCut );
    void paste();
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369

    // the user clicked on a popup menu choice:
    void onPopupSelection( wxCommandEvent& event )
    {
        int     menuId = event.GetId();

        // assume getSelectedArea() was called by rightClickPopupMenu() and there's
        // no way to have gotten here without that having been called.

        switch( menuId )
        {
        case MYID_CUT:
        case MYID_COPY:
            cutcopy( menuId == MYID_CUT );
            break;

        case MYID_PASTE:
            paste();
            break;

        case MYID_SELECT:
            m_cur_grid->SelectAll();
370
            break;
371 372 373

        default:
            ;
374
        }
Dick Hollenbeck's avatar
Dick Hollenbeck committed
375
    }
Dick Hollenbeck's avatar
Dick Hollenbeck committed
376

377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
    /**
     * Function verifyTables
     * trims important fields, removes blank row entries, and checks for duplicates.
     * @return bool - true if tables are OK, else false.
     */
    bool verifyTables()
    {
        for( int t=0; t<2; ++t )
        {
            FP_TBL_MODEL& model = t==0 ? m_global_model : m_project_model;

            for( int r = 0; r < model.GetNumberRows(); )
            {
                wxString nick = model.GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
                wxString uri  = model.GetValue( r, COL_URI ).Trim( false ).Trim();

393
                if( !nick || !uri )
394
                {
395
                    // Delete the "empty" row, where empty means missing nick or uri.
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
                    // This also updates the UI which could be slow, but there should only be a few
                    // rows to delete, unless the user fell asleep on the Add Row
                    // button.
                    model.DeleteRows( r, 1 );
                }
                else if( nick.find(':') != size_t(-1) )
                {
                    wxString msg = wxString::Format(
                        _( "Illegal character '%s' found in Nickname: '%s' in row %d" ),
                        wxT( ":" ), GetChars( nick ), r );

                    // show the tabbed panel holding the grid we have flunked:
                    if( &model != (FP_TBL_MODEL*) m_cur_grid->GetTable() )
                    {
                        m_auinotebook->SetSelection( &model == &m_global_model ? 0 : 1 );
                    }

413
                    // go to the problematic row
414
                    m_cur_grid->SetGridCursor( r, 0 );
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
                    m_cur_grid->SelectBlock( r, 0, r, 0 );
                    m_cur_grid->MakeCellVisible( r, 0 );

                    wxMessageDialog errdlg( this, msg, _( "No Colon in Nicknames" ) );
                    errdlg.ShowModal();
                    return false;
                }
                else
                {
                    // set the trimmed values back into the table so they get saved to disk.
                    model.SetValue( r, COL_NICKNAME, nick );
                    model.SetValue( r, COL_URI, uri );
                    ++r;        // this row was OK.
                }
            }
        }

        // check for duplicate nickNames, separately in each table.
        for( int t=0; t<2; ++t )
        {
            FP_TBL_MODEL& model = t==0 ? m_global_model : m_project_model;

            for( int r1 = 0; r1 < model.GetNumberRows() - 1;  ++r1 )
            {
439 440
                wxString    nick1 = model.GetValue( r1, COL_NICKNAME );

441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
                for( int r2=r1+1; r2 < model.GetNumberRows();  ++r2 )
                {
                    wxString    nick2 = model.GetValue( r2, COL_NICKNAME );

                    if( nick1 == nick2 )
                    {
                        wxString msg = wxString::Format(
                            _( "Duplicate Nickname: '%s' in rows %d and %d" ),
                            GetChars( nick1 ), r1+1, r2+1
                            );

                        // show the tabbed panel holding the grid we have flunked:
                        if( &model != (FP_TBL_MODEL*) m_cur_grid->GetTable() )
                        {
                            m_auinotebook->SetSelection( &model == &m_global_model ? 0 : 1 );
                        }

458
                        // go to the lower of the two rows, it is technically the duplicate:
459
                        m_cur_grid->SetGridCursor( r2, 0 );
460 461 462 463 464 465 466 467 468 469 470 471 472 473
                        m_cur_grid->SelectBlock( r2, 0, r2, 0 );
                        m_cur_grid->MakeCellVisible( r2, 0 );

                        wxMessageDialog errdlg( this, msg, _( "Please Delete or Modify One" ) );
                        errdlg.ShowModal();
                        return false;
                    }
                }
            }
        }

        return true;
    }

474 475
    //-----<event handlers>----------------------------------

476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
    void onKeyDown( wxKeyEvent& ev )
    {
        if( isCtl( 'A', ev ) )
        {
            m_cur_grid->SelectAll();
        }
        else if( isCtl( 'C', ev ) )
        {
            getSelectedArea();
            cutcopy( false );
        }
        else if( isCtl( 'V', ev ) )
        {
            getSelectedArea();
            paste();
        }
        else if( isCtl( 'X', ev ) )
        {
            getSelectedArea();
            cutcopy( true );
        }
        else
            ev.Skip();
    }

501 502 503
    void pageChangedHandler( wxAuiNotebookEvent& event )
    {
        int pageNdx = m_auinotebook->GetSelection();
504
        m_cur_grid = ( pageNdx == 0 ) ? m_global_grid : m_project_grid;
505 506 507 508
    }

    void appendRowHandler( wxMouseEvent& event )
    {
509 510 511 512 513
        if( m_cur_grid->AppendRows( 1 ) )
        {
            int last_row = m_cur_grid->GetNumberRows() - 1;

            m_cur_grid->MakeCellVisible( last_row, 0 );
514
            m_cur_grid->SetGridCursor( last_row, 0 );
515
        }
516 517 518 519
    }

    void deleteRowHandler( wxMouseEvent& event )
    {
520 521 522
        int rowCount = m_cur_grid->GetNumberRows();
        int curRow   = getCursorRow();

Dick Hollenbeck's avatar
Dick Hollenbeck committed
523
        m_cur_grid->DeleteRows( curRow );
524 525 526

        if( curRow && curRow == rowCount - 1 )
            m_cur_grid->SetGridCursor( curRow-1, getCursorCol() );
527 528 529 530
    }

    void moveUpHandler( wxMouseEvent& event )
    {
531
        int curRow = getCursorRow();
Dick Hollenbeck's avatar
Dick Hollenbeck committed
532 533
        if( curRow >= 1 )
        {
534
            int curCol = getCursorCol();
Dick Hollenbeck's avatar
Dick Hollenbeck committed
535

536
            FP_TBL_MODEL* tbl = (FP_TBL_MODEL*) m_cur_grid->GetTable();
Dick Hollenbeck's avatar
Dick Hollenbeck committed
537

538
            ROW move_me = tbl->rows[curRow];
Dick Hollenbeck's avatar
Dick Hollenbeck committed
539

540 541 542
            tbl->rows.erase( tbl->rows.begin() + curRow );
            --curRow;
            tbl->rows.insert( tbl->rows.begin() + curRow, move_me );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
543 544 545

            if( tbl->GetView() )
            {
546
                // fire a msg to cause redrawing
Dick Hollenbeck's avatar
Dick Hollenbeck committed
547 548 549 550 551 552 553
                wxGridTableMessage msg( tbl,
                                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
                                        curRow,
                                        0 );

                tbl->GetView()->ProcessTableMessage( msg );
            }
554 555

            m_cur_grid->SetGridCursor( curRow, curCol );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
556
        }
557 558 559 560
    }

    void moveDownHandler( wxMouseEvent& event )
    {
561 562
        FP_TBL_MODEL* tbl = (FP_TBL_MODEL*) m_cur_grid->GetTable();

563
        int curRow = getCursorRow();
564 565
        if( unsigned( curRow + 1 ) < tbl->rows.size() )
        {
566
            int curCol  = getCursorCol();
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586

            ROW move_me = tbl->rows[curRow];

            tbl->rows.erase( tbl->rows.begin() + curRow );
             ++curRow;
            tbl->rows.insert( tbl->rows.begin() + curRow, move_me );

            if( tbl->GetView() )
            {
                // fire a msg to cause redrawing
                wxGridTableMessage msg( tbl,
                                        wxGRIDTABLE_NOTIFY_ROWS_INSERTED,
                                        curRow - 1,
                                        0 );

                tbl->GetView()->ProcessTableMessage( msg );
            }

            m_cur_grid->SetGridCursor( curRow, curCol );
        }
587 588 589
        D(printf("%s\n", __func__);)
    }

590 591
    void optionsEditor( wxMouseEvent& event )
    {
592 593 594 595 596 597 598 599 600 601 602 603
        FP_TBL_MODEL*   tbl = (FP_TBL_MODEL*) m_cur_grid->GetTable();

        int     curRow = getCursorRow();
        ROW&    row    = tbl->rows[curRow];

        wxString        result;
        const wxString& options = row.GetOptions();

        InvokePluginOptionsEditor( this, row.GetNickName(), options, &result );

        if( options != result )
            row.SetOptions( result );
604 605
    }

606 607
    void onCancelButtonClick( wxCommandEvent& event )
    {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
608
        EndModal( 0 );
609 610
    }

611 612 613 614 615
    void onCancelButtonClick( wxCloseEvent& event )
    {
        EndModal( 0 );
    }

616 617
    void onOKButtonClick( wxCommandEvent& event )
    {
Dick Hollenbeck's avatar
Dick Hollenbeck committed
618
        int dialogRet = 0;
619

620
        if( verifyTables() )
Dick Hollenbeck's avatar
Dick Hollenbeck committed
621
        {
622 623 624
            if( m_global_model != *m_global )
            {
                dialogRet |= 1;
625

626 627 628
                *m_global  = m_global_model;
                m_global->reindex();
            }
629

630 631 632
            if( m_project_model != *m_project )
            {
                dialogRet |= 2;
Dick Hollenbeck's avatar
Dick Hollenbeck committed
633

634 635 636
                *m_project = m_project_model;
                m_project->reindex();
            }
Dick Hollenbeck's avatar
Dick Hollenbeck committed
637

638 639
            EndModal( dialogRet );
        }
Dick Hollenbeck's avatar
Dick Hollenbeck committed
640
    }
641

642 643 644 645 646
    void onGridCmdSelectCell( wxGridEvent& event )
    {
        event.Skip();
    }

647 648 649 650 651 652 653 654 655 656 657 658 659 660
    void onGridCellLeftClick( wxGridEvent& event )
    {
        event.Skip();
    }

    void onGridCellLeftDClick( wxGridEvent& event )
    {
        event.Skip();
    }

    void onGridCellRightClick( wxGridEvent& event )
    {
        rightClickCellPopupMenu();
    }
661

Dick Hollenbeck's avatar
Dick Hollenbeck committed
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
    /// Populate the readonly environment variable table with names and values
    /// by examining all the full_uri columns.
    void populateEnvironReadOnlyTable()
    {
        wxRegEx re( wxT( ".*?\\$\\{(.+?)\\}.*?" ), wxRE_ADVANCED );
        wxASSERT( re.IsValid() );   // wxRE_ADVANCED is required.

        std::set< wxString >        unique;
        typedef std::set<wxString>::const_iterator      SET_CITER;

        m_path_subs_grid->DeleteRows( 0, m_path_subs_grid->GetNumberRows() );

        int gblRowCount = m_global_model.GetNumberRows();
        int prjRowCount = m_project_model.GetNumberRows();
        int row;

        for( row = 0;  row < gblRowCount;  ++row )
        {
680
            wxString uri = m_global_model.GetValue( row, COL_URI );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
681 682 683 684 685 686 687 688 689 690 691 692

            while( re.Matches( uri ) )
            {
                wxString envvar = re.GetMatch( uri, 1 );

                // ignore duplicates
                unique.insert( envvar );

                // delete the last match and search again
                uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
            }
        }
693

Dick Hollenbeck's avatar
Dick Hollenbeck committed
694 695
        for( row = 0;  row < prjRowCount;  ++row )
        {
696
            wxString uri = m_project_model.GetValue( row, COL_URI );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726

            while( re.Matches( uri ) )
            {
                wxString envvar = re.GetMatch( uri, 1 );

                // ignore duplicates
                unique.insert( envvar );

                // delete the last match and search again
                uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
            }
        }

        m_path_subs_grid->AppendRows( unique.size() );

        row = 0;
        for( SET_CITER it = unique.begin();  it != unique.end();  ++it, ++row )
        {
            wxString    evName = *it;
            wxString    evValue;

            m_path_subs_grid->SetCellValue( row, 0, evName );

            if( wxGetEnv( evName, &evValue ) )
                m_path_subs_grid->SetCellValue( row, 1, evValue );
        }

        m_path_subs_grid->AutoSizeColumns();
    }

727
    //-----</event handlers>---------------------------------
728 729 730 731 732

    // caller's tables are modified only on OK button.
    FP_LIB_TABLE*       m_global;
    FP_LIB_TABLE*       m_project;

733 734 735
    // local copies which are edited, but aborted if Cancel button.
    FP_TBL_MODEL        m_global_model;
    FP_TBL_MODEL        m_project_model;
736 737 738

    wxGrid*             m_cur_grid;     ///< changed based on tab choice

739
public:
740
    DIALOG_FP_LIB_TABLE( wxTopLevelWindow* aParent, FP_LIB_TABLE* aGlobal, FP_LIB_TABLE* aProject ) :
741 742 743
        DIALOG_FP_LIB_TABLE_BASE( aParent ),
        m_global( aGlobal ),
        m_project( aProject ),
744
        m_global_model( *aGlobal ),
745
        m_project_model( *aProject )
746
    {
747 748
        m_global_grid->SetTable( (wxGridTableBase*) &m_global_model );
        m_project_grid->SetTable( (wxGridTableBase*) &m_project_model );
749 750 751 752

        m_global_grid->AutoSizeColumns( false );
        m_project_grid->AutoSizeColumns( false );

Dick Hollenbeck's avatar
Dick Hollenbeck committed
753
        wxArrayString choices;
754

Dick Hollenbeck's avatar
Dick Hollenbeck committed
755
        choices.Add( IO_MGR::ShowType( IO_MGR::KICAD ) );
756
        choices.Add( IO_MGR::ShowType( IO_MGR::GITHUB ) );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
757 758
        choices.Add( IO_MGR::ShowType( IO_MGR::LEGACY ) );
        choices.Add( IO_MGR::ShowType( IO_MGR::EAGLE ) );
759
        choices.Add( IO_MGR::ShowType( IO_MGR::GEDA_PCB ) );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
760

761 762 763 764
        /* PCAD_PLUGIN does not support Footprint*() functions
        choices.Add( IO_MGR::ShowType( IO_MGR::GITHUB ) );
        */

Dick Hollenbeck's avatar
Dick Hollenbeck committed
765 766 767 768
        wxGridCellAttr* attr;

        attr = new wxGridCellAttr;
        attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
769
        m_project_grid->SetColAttr( COL_TYPE, attr );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
770 771 772

        attr = new wxGridCellAttr;
        attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
773
        m_global_grid->SetColAttr( COL_TYPE, attr );
Dick Hollenbeck's avatar
Dick Hollenbeck committed
774 775 776 777

        m_global_grid->AutoSizeColumns();
        m_project_grid->AutoSizeColumns();

778
        Connect( MYID_FIRST, MYID_LAST, wxEVT_COMMAND_MENU_SELECTED,
779 780
            wxCommandEventHandler( DIALOG_FP_LIB_TABLE::onPopupSelection ), NULL, this );

Dick Hollenbeck's avatar
Dick Hollenbeck committed
781 782 783 784 785
        populateEnvironReadOnlyTable();

        /* This scrunches the dialog hideously
        Fit();
        */
Dick Hollenbeck's avatar
Dick Hollenbeck committed
786 787 788 789

        // fire pageChangedHandler() so m_cur_grid gets set
        wxAuiNotebookEvent uneventful;
        pageChangedHandler( uneventful );
790 791 792

        // for ALT+A handling, we want the initial focus to be on the first selected grid.
        m_cur_grid->SetFocus();
793
    }
794 795 796

    ~DIALOG_FP_LIB_TABLE()
    {
797
        Disconnect( MYID_FIRST, MYID_LAST, wxEVT_COMMAND_MENU_SELECTED,
Dick Hollenbeck's avatar
Dick Hollenbeck committed
798 799 800 801 802 803 804
            wxCommandEventHandler( DIALOG_FP_LIB_TABLE::onPopupSelection ), NULL, this );

        // ~wxGrid() examines its table, and the tables will have been destroyed before
        // the wxGrids are, so remove the tables from the wxGrids' awareness.
        // Otherwise there is a segfault.
        m_global_grid->SetTable( NULL );
        m_project_grid->SetTable( NULL );
805
    }
806 807 808
};


809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
void DIALOG_FP_LIB_TABLE::cutcopy( bool doCut )
{
    // this format is compatible with most spreadsheets
    if( wxTheClipboard->Open() )
    {
        wxGridTableBase*    tbl = m_cur_grid->GetTable();
        wxString            txt;

        for( int row = selRowStart;  row < selRowStart + selRowCount;  ++row )
        {
            for( int col = selColStart;  col < selColStart + selColCount; ++col )
            {
                txt += tbl->GetValue( row, col );

                if( col < selColStart + selColCount - 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();

        m_cur_grid->AutoSizeColumns();
        m_cur_grid->ForceRefresh();
    }
}


void DIALOG_FP_LIB_TABLE::paste()
{
    // assume format came from a spreadsheet or us.
    if( wxTheClipboard->Open() )
    {
        if( wxTheClipboard->IsSupported( wxDF_TEXT ) )
        {
            wxTextDataObject    data;
            FP_TBL_MODEL*       tbl = (FP_TBL_MODEL*) m_cur_grid->GetTable();

            wxTheClipboard->GetData( data );

            wxString    cb_text = data.GetText();
            size_t      ndx = cb_text.find( wxT( "(fp_lib_table" ) );

            if( ndx != std::string::npos )
            {
                // paste the ROWs of s-expression (fp_lib_table), starting
                // at column 0 regardless of current cursor column.

                STRING_LINE_READER  slr( TO_UTF8( cb_text ), wxT( "Clipboard" ) );
                FP_LIB_TABLE_LEXER  lexer( &slr );
                FP_LIB_TABLE        tmp_tbl;
                bool                parsed = true;

                try
                {
                    tmp_tbl.Parse( &lexer );
                }
                catch( PARSE_ERROR& pe )
                {
                    // @todo tell what line and offset
                    parsed = false;
                }

                if( parsed )
                {
                    const int cur_row = getCursorRow();

                    // if clipboard rows would extend past end of current table size...
                    if( int( tmp_tbl.rows.size() ) > tbl->GetNumberRows() - cur_row )
                    {
                        int newRowsNeeded = tmp_tbl.rows.size() - ( tbl->GetNumberRows() - cur_row );
                        tbl->AppendRows( newRowsNeeded );
                    }

                    for( int i = 0;  i < (int) tmp_tbl.rows.size();  ++i )
                    {
                        tbl->rows[cur_row+i] = tmp_tbl.rows[i];
                    }
                }
                m_cur_grid->AutoSizeColumns();
            }
            else
            {
                const int           cur_row = getCursorRow();
                const int           cur_col = getCursorCol();
                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_cur_grid->AutoSizeColumns();
            }
        }

        wxTheClipboard->Close();
        m_cur_grid->ForceRefresh();
    }
}


930
int InvokePcbLibTableEditor( wxTopLevelWindow* aParent, FP_LIB_TABLE* aGlobal, FP_LIB_TABLE* aProject )
931
{
Dick Hollenbeck's avatar
Dick Hollenbeck committed
932
    DIALOG_FP_LIB_TABLE dlg( aParent, aGlobal, aProject );
933

Dick Hollenbeck's avatar
Dick Hollenbeck committed
934
    int dialogRet = dlg.ShowModal();    // returns value passed to EndModal() above
935

Dick Hollenbeck's avatar
Dick Hollenbeck committed
936
    return dialogRet;
937
}