File: sheet.cpp

package info (click to toggle)
kicad 5.0.2%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 234,592 kB
  • sloc: cpp: 505,330; ansic: 57,038; python: 4,886; sh: 879; awk: 294; makefile: 253; xml: 103; perl: 5
file content (540 lines) | stat: -rw-r--r-- 18,910 bytes parent folder | download
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
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
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2015 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright (C) 2004-2018 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
 */

/**
 * @file sheet.cpp
 */

#include <fctsys.h>
#include <class_drawpanel.h>
#include <confirm.h>
#include <base_units.h>
#include <kiface_i.h>
#include <project.h>
#include <wildcards_and_files_ext.h>

#include <sch_edit_frame.h>
#include <sch_legacy_plugin.h>
#include <sch_sheet.h>
#include <sch_sheet_path.h>

#include <dialogs/dialog_sch_sheet_props.h>


bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, bool* aClearAnnotationNewItems )
{
    if( aSheet == NULL || aHierarchy == NULL )
        return false;

    SCH_SHEET_LIST hierarchy( g_RootSheet );       // This is the schematic sheet hierarchy.

    // Get the new texts
    DIALOG_SCH_SHEET_PROPS dlg( this );

    wxString units = GetUnitsLabel( g_UserUnit );
    dlg.SetFileName( aSheet->GetFileName() );
    dlg.SetFileNameTextSize( StringFromValue( g_UserUnit, aSheet->GetFileNameSize() ) );
    dlg.SetFileNameTextSizeUnits( units );
    dlg.SetSheetName( aSheet->GetName() );
    dlg.SetSheetNameTextSize( StringFromValue( g_UserUnit, aSheet->GetSheetNameSize() ) );
    dlg.SetSheetNameTextSizeUnits( units );
    dlg.SetSheetTimeStamp( wxString::Format( wxT( "%8.8lX" ),
                           (unsigned long) aSheet->GetTimeStamp() ) );

    /* This ugly hack fixes a bug in wxWidgets 2.8.7 and likely earlier
     * versions for the flex grid sizer in wxGTK that prevents the last
     * column from being sized correctly.  It doesn't cause any problems
     * on win32 so it doesn't need to wrapped in ugly #ifdef __WXGTK__
     * #endif.
     * Still present in wxWidgets 3.0.2
     */
    dlg.Layout();
    dlg.Fit();
    dlg.SetMinSize( dlg.GetSize() );
    dlg.GetSizer()->Fit( &dlg );

    if( dlg.ShowModal() == wxID_CANCEL )
        return false;

    wxFileName fileName = dlg.GetFileName();
    fileName.SetExt( SchematicFileExtension );

    if( !fileName.IsOk() )
    {
        DisplayError( this, _( "File name is not valid!" ) );
        return false;
    }

    // Duplicate sheet names are not valid.
    const SCH_SHEET* sheet = hierarchy.FindSheetByName( dlg.GetSheetName() );

    if( sheet && (sheet != aSheet) )
    {
        DisplayError( this, wxString::Format( _( "A sheet named \"%s\" already exists." ),
                                              dlg.GetSheetName() ) );
        return false;
    }

    wxString msg;
    bool loadFromFile = false;
    bool clearAnnotation = false;
    SCH_SCREEN* useScreen = NULL;

    // Relative file names are relative to the path of the current sheet.  This allows for
    // nesting of schematic files in subfolders.
    if( !fileName.IsAbsolute() )
    {
        const SCH_SCREEN* currentScreen = aHierarchy->LastScreen();

        wxCHECK_MSG( currentScreen, false, "Invalid sheet path object." );

        wxFileName currentSheetFileName = currentScreen->GetFileName();

        wxCHECK_MSG( fileName.Normalize( wxPATH_NORM_ALL, currentSheetFileName.GetPath() ), false,
                     "Cannot normalize new sheet schematic file path." );
    }

    wxString newFilename = fileName.GetFullPath();

    // Search for a schematic file having the same filename
    // already in use in the hierarchy or on disk, in order to reuse it.
    if( !g_RootSheet->SearchHierarchy( newFilename, &useScreen ) )
    {
        loadFromFile = wxFileExists( newFilename );
        wxLogDebug( "Sheet requested file \"%s\", %s", newFilename,
                ( loadFromFile ) ? "found" : "not found" );
    }

    // Inside Eeschema, filenames are stored using unix notation
    newFilename.Replace( wxT( "\\" ), wxT( "/" ) );

    SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) );

    if( aSheet->GetScreen() == NULL )              // New sheet.
    {
        if( useScreen || loadFromFile )            // Load from existing file.
        {
            clearAnnotation = true;

            wxString existsMsg;
            wxString linkMsg;
            existsMsg.Printf( _( "\"%s\" already exists." ), fileName.GetFullName() );
            linkMsg.Printf( _( "Link \"%s\" to this file?" ), dlg.GetSheetName() );
            msg.Printf( wxT( "%s\n\n%s" ), existsMsg, linkMsg );

            if( !IsOK( this, msg ) )
                return false;

        }
        else                                                   // New file.
        {
            aSheet->SetScreen( new SCH_SCREEN( &Kiway() ) );
            aSheet->GetScreen()->SetModify();
            aSheet->GetScreen()->SetMaxUndoItems( m_UndoRedoCountMax );
            aSheet->GetScreen()->SetFileName( newFilename );
        }
    }
    else                                                       // Existing sheet.
    {
        bool isUndoable = true;
        bool renameFile = false;
        wxString replaceMsg;
        wxString newMsg;
        wxString noUndoMsg;

        // Changing the filename of a sheet can modify the full hierarchy structure
        // and can be not always undoable.
        // So prepare messages for user notifications:
        replaceMsg.Printf( _( "Change \"%s\" link from \"%s\" to \"%s\"?" ),
                dlg.GetSheetName(), aSheet->GetFileName(), fileName.GetFullName() );
        newMsg.Printf( _( "Create new file \"%s\" with contents of \"%s\"?" ),
                fileName.GetFullName(), aSheet->GetFileName() );
        noUndoMsg = _( "This action cannot be undone." );

        // We are always using here a case insensitive comparison
        // to avoid issues under Windows, although under Unix
        // filenames are case sensitive.
        // But many users create schematic under both Unix and Windows
        // **
        // N.B. 1: aSheet->GetFileName() will return a relative path
        //         aSheet->GetScreen()->GetFileName() returns a full path
        //
        // N.B. 2: newFilename uses the unix notation for separator.
        //         so we must use it also to compare the old filename to the new filename
        wxString oldFilename = aSheet->GetScreen()->GetFileName();
        oldFilename.Replace( wxT( "\\" ), wxT( "/" ) );

        if( newFilename.CmpNoCase( oldFilename ) != 0 )
        {
            // Sheet file name changes cannot be undone.
            isUndoable = false;

            if( useScreen || loadFromFile )                    // Load from existing file.
            {
                clearAnnotation = true;

                msg.Printf( wxT( "%s\n\n%s" ), replaceMsg, noUndoMsg );

                if( !IsOK( this, msg ) )
                    return false;

                if( loadFromFile )
                    aSheet->SetScreen( NULL );
            }
            else                                               // Save to new file name.
            {
                if( aSheet->GetScreenCount() > 1 )
                {
                    msg.Printf( wxT( "%s\n\n%s" ), newMsg, noUndoMsg );

                    if( !IsOK( this, msg ) )
                        return false;
                }

                renameFile = true;
            }
        }

        m_canvas->SetIgnoreMouseEvents( true );

        if( isUndoable )
            SaveCopyInUndoList( aSheet, UR_CHANGED );

        if( renameFile )
        {
            // If the the associated screen is shared by more than one sheet, do not
            // change the filename of the corresponding screen here.
            // (a new screen will be created later)
            // if it is not shared, update the filename
            if( aSheet->GetScreenCount() <= 1 )
                aSheet->GetScreen()->SetFileName( newFilename );

            try
            {
                pi->Save( newFilename, aSheet->GetScreen(), &Kiway() );
            }
            catch( const IO_ERROR& ioe )
            {
                msg.Printf( _( "Error occurred saving schematic file \"%s\"." ), newFilename );
                DisplayErrorMessage( this, msg, ioe.What() );

                msg.Printf( _( "Failed to save schematic \"%s\"" ), newFilename );
                AppendMsgPanel( wxEmptyString, msg, CYAN );

                return false;
            }

            // If the the associated screen is shared by more than one sheet, remove the
            // screen and reload the file to a new screen.  Failure to do this will trash
            // the screen reference counting in complex hierarchies.
            if( aSheet->GetScreenCount() > 1 )
            {
                aSheet->SetScreen( NULL );
                loadFromFile = true;
            }
        }
    }

    wxFileName userFileName = dlg.GetFileName();
    userFileName.SetExt( SchematicFileExtension );
    aSheet->SetFileName( userFileName.GetFullPath( wxPATH_UNIX ) );

    if( useScreen )
    {
        aSheet->SetScreen( useScreen );
    }
    else if( loadFromFile )
    {
        try
        {
            aSheet = pi->Load( newFilename, &Kiway(), aSheet );

            if( !pi->GetError().IsEmpty() )
            {
                DisplayErrorMessage( this,
                                     _( "The entire schematic could not be load.  Errors "
                                        "occurred attempting to load hierarchical sheet "
                                        "schematics." ),
                                     pi->GetError() );
            }
        }
        catch( const IO_ERROR& ioe )
        {
            msg.Printf( _( "Error occurred loading schematic file \"%s\"." ), newFilename );
            DisplayErrorMessage( this, msg, ioe.What() );

            msg.Printf( _( "Failed to load schematic \"%s\"" ), newFilename );
            AppendMsgPanel( wxEmptyString, msg, CYAN );

            return false;
        }
    }

    aSheet->SetFileNameSize( ValueFromString( g_UserUnit, dlg.GetFileNameTextSize() ) );
    aSheet->SetName( dlg.GetSheetName() );
    aSheet->SetSheetNameSize( ValueFromString( g_UserUnit, dlg.GetSheetNameTextSize() ) );

    if( aSheet->GetName().IsEmpty() )
        aSheet->SetName( wxString::Format( wxT( "Sheet%8.8lX" ),
                                           (long unsigned) aSheet->GetTimeStamp() ) );

    // Make sure the sheet changes do not cause any recursion.
    SCH_SHEET_LIST sheetHierarchy( aSheet );

    // Make sure files have fully qualified path and file name.
    wxFileName destFn = aHierarchy->Last()->GetFileName();

    if( destFn.IsRelative() )
        destFn.MakeAbsolute( Prj().GetProjectPath() );

    if( hierarchy.TestForRecursion( sheetHierarchy, destFn.GetFullPath( wxPATH_UNIX ) ) )
    {
        msg.Printf( _( "The sheet changes cannot be made because the destination sheet already "
                       "has the sheet \"%s\" or one of it's subsheets as a parent somewhere in "
                       "the schematic hierarchy." ),
                    newFilename );
        DisplayError( this, msg );
        return false;
    }

    // Check to make sure the symbols have been remapped to the symbol library table.
    SCH_SCREENS newScreens( aSheet );

    if( newScreens.HasNoFullyDefinedLibIds() )
    {
        msg.Printf(_( "The schematic \"%s\" has not been remapped to the symbol library table. "
                      "Most if not all of the symbol library links will be broken.  Do you "
                      "want to continue?" ), fileName.GetFullName() );

        if( !IsOK( this, msg ) )
            return false;
    }

    if( aClearAnnotationNewItems )
        *aClearAnnotationNewItems = clearAnnotation;

    m_canvas->MoveCursorToCrossHair();
    m_canvas->SetIgnoreMouseEvents( false );
    OnModify();

    return true;
}


/* Move selected sheet with the cursor.
 * Callback function used by m_mouseCaptureCallback.
 * Note also now this function is called only when resizing the sheet
 * But the (very small code) relative to sheet move is still present here
 */
static void resizeSheetWithMouseCursor( EDA_DRAW_PANEL* aPanel, wxDC* aDC, const wxPoint& aPosition,
                                        bool aErase )
{
    BASE_SCREEN*   screen = aPanel->GetScreen();
    SCH_SHEET*     sheet = dynamic_cast<SCH_SHEET*>( screen->GetCurItem() );

    if( sheet == nullptr )  // Be sure we are using the right object
        return;

    if( aErase )
        sheet->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode );

    wxPoint pos = sheet->GetPosition();

    int width  = aPanel->GetParent()->GetCrossHairPosition().x - pos.x;
    int height = aPanel->GetParent()->GetCrossHairPosition().y - pos.y;

    // If the sheet doesn't have any pins, clamp the minimum size to the default values.
    width = ( width < MIN_SHEET_WIDTH ) ? MIN_SHEET_WIDTH : width;
    height = ( height < MIN_SHEET_HEIGHT ) ? MIN_SHEET_HEIGHT : height;

    if( sheet->HasPins() )
    {
        int gridSizeX = KiROUND( screen->GetGridSize().x );
        int gridSizeY = KiROUND( screen->GetGridSize().y );

        // If the sheet has pins, use the pin positions to clamp the minimum width and height.
        height = ( height < sheet->GetMinHeight() + gridSizeY ) ?
                 sheet->GetMinHeight() + gridSizeY : height;
        width = ( width < sheet->GetMinWidth() + gridSizeX ) ?
                sheet->GetMinWidth() + gridSizeX : width;
    }

    wxPoint grid = aPanel->GetParent()->GetNearestGridPosition(
                    wxPoint( pos.x + width, pos.y + height ) );
    sheet->Resize( wxSize( grid.x - pos.x, grid.y - pos.y ) );

    sheet->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode );
}


//  Complete sheet move.
static void ExitSheet( EDA_DRAW_PANEL* aPanel, wxDC* aDC )
{
    SCH_SCREEN* screen = (SCH_SCREEN*) aPanel->GetScreen();
    SCH_ITEM*   item = screen->GetCurItem();

    SCH_EDIT_FRAME* parent = (SCH_EDIT_FRAME*) aPanel->GetParent();

    if( (item == NULL) || (item->Type() != SCH_SHEET_T) || (parent == NULL) )
        return;

    parent->SetRepeatItem( NULL );

    item->Draw( aPanel, aDC, wxPoint( 0, 0 ), g_XorMode );

    if( item->IsNew() )
    {
        delete item;
    }
    else if( item->IsMoving() || item->IsResized() )
    {
        screen->Remove( item );
        delete item;

        item = parent->GetUndoItem();

        wxCHECK_RET( item != NULL, wxT( "Cannot restore undefined last sheet item." ) );

        screen->Append( item );
        // the owner of item is no more parent, this is the draw list of screen:
        parent->SetUndoItem( NULL );

        item->Draw( aPanel, aDC, wxPoint( 0, 0 ), GR_DEFAULT_DRAWMODE );
        item->ClearFlags();
    }
    else
    {
        item->ClearFlags();
    }

    screen->SetCurItem( NULL );
}


// Create hierarchy sheet.
SCH_SHEET* SCH_EDIT_FRAME::CreateSheet( wxDC* aDC )
{
    SetRepeatItem( NULL );

    SCH_SHEET* sheet = new SCH_SHEET( GetCrossHairPosition() );

    sheet->SetFlags( IS_NEW | IS_RESIZED );
    sheet->SetTimeStamp( GetNewTimeStamp() );
    sheet->SetParent( GetScreen() );
    sheet->SetScreen( NULL );

    // need to check if this is being added to the GetDrawItems().
    // also need to update the hierarchy, if we are adding
    // a sheet to a screen that already has multiple instances (!)
    GetScreen()->SetCurItem( sheet );
    m_canvas->SetMouseCapture( resizeSheetWithMouseCursor, ExitSheet );
    m_canvas->CallMouseCapture( aDC, wxDefaultPosition, false );
    m_canvas->CrossHairOff( aDC );

    SetCrossHairPosition( sheet->GetResizePosition() );

    m_canvas->MoveCursorToCrossHair();
    m_canvas->CrossHairOn( aDC );

    return sheet;
}


void SCH_EDIT_FRAME::ReSizeSheet( SCH_SHEET* aSheet, wxDC* aDC )
{
    if( aSheet == NULL || aSheet->IsNew() )
        return;

    wxCHECK_RET( aSheet->Type() == SCH_SHEET_T,
                 wxString::Format( wxT( "Cannot perform sheet resize on %s object." ),
                                   GetChars( aSheet->GetClass() ) ) );

    m_canvas->CrossHairOff( aDC );
    SetCrossHairPosition( aSheet->GetResizePosition() );
    m_canvas->MoveCursorToCrossHair();
    m_canvas->CrossHairOn( aDC );

    SetUndoItem( aSheet );
    aSheet->SetFlags( IS_RESIZED );

    m_canvas->SetMouseCapture( resizeSheetWithMouseCursor, ExitSheet );
    m_canvas->CallMouseCapture( aDC, wxDefaultPosition, true );

    if( aSheet->IsNew() )    // not already in edit, save a copy for undo/redo
        SetUndoItem( aSheet );
}


void SCH_EDIT_FRAME::RotateHierarchicalSheet( SCH_SHEET* aSheet, bool aRotCCW )
{
    if( aSheet == NULL )
        return;

    // Save old sheet in undo list if not already in edit, or moving.
    if( aSheet->GetFlags() == 0 )
        SaveCopyInUndoList( aSheet, UR_CHANGED );

    // Rotate the sheet on itself. Sheets do not have a anchor point.
    // Rotation is made around it center
    wxPoint rotPoint = aSheet->GetBoundingBox().Centre();

    // Keep this rotation point on the grid, otherwise all items of this sheet
    // will be moved off grid
    rotPoint = GetNearestGridPosition( rotPoint );

    // rotate CCW, or CW. to rotate CW, rotate 3 times
    aSheet->Rotate( rotPoint );

    if( !aRotCCW )
    {
        aSheet->Rotate( rotPoint );
        aSheet->Rotate( rotPoint );
    }

    GetCanvas()->Refresh();
    OnModify();
}


void SCH_EDIT_FRAME::MirrorSheet( SCH_SHEET* aSheet, bool aFromXaxis )
{
    if( aSheet == NULL )
        return;

    // Save old sheet in undo list if not already in edit, or moving.
    if( aSheet->GetFlags() == 0 )
        SaveCopyInUndoList( aSheet, UR_CHANGED );

    // Mirror the sheet on itself. Sheets do not have a anchor point.
    // Mirroring is made around it center
    wxPoint mirrorPoint = aSheet->GetBoundingBox().Centre();

    if( aFromXaxis )    // mirror relative to Horizontal axis
        aSheet->MirrorX( mirrorPoint.y );
    else                // Mirror relative to vertical axis
        aSheet->MirrorY( mirrorPoint.x );

    GetCanvas()->Refresh();
    OnModify();
}