/* * KiRouter - a push-and-(sometimes-)shove PCB router * * Copyright (C) 2013 CERN * Author: Tomasz Wlostowski * * 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 3 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, see . */ #include #include #include #include #include "class_draw_panel_gal.h" #include "class_board.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "router_tool.h" #include "pns_segment.h" #include "pns_router.h" #include "trace.h" using namespace KIGFX; using boost::optional; static TOOL_ACTION ACT_NewTrack( "pcbnew.InteractiveRouter.NewTrack", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_NEW_TRACK ), _( "New Track" ), _( "Starts laying a new track." ), add_tracks_xpm ); static TOOL_ACTION ACT_EndTrack( "pcbnew.InteractiveRouter.EndTrack", AS_CONTEXT, WXK_END, _( "End Track" ), _( "Stops laying the current track." ), checked_ok_xpm ); static TOOL_ACTION ACT_AutoEndRoute( "pcbnew.InteractiveRouter.AutoEndRoute", AS_CONTEXT, 'F', _( "Auto-end Track" ), _( "Automagically finishes currently routed track." ) ); static TOOL_ACTION ACT_Drag( "pcbnew.InteractiveRouter.Drag", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_DRAG_TRACK_KEEP_SLOPE ), _( "Drag Track/Via" ), _( "Drags a track or a via." ), drag_track_segment_xpm ); static TOOL_ACTION ACT_PlaceThroughVia( "pcbnew.InteractiveRouter.PlaceVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_THROUGH_VIA ), _( "Place Through Via" ), _( "Adds a through-hole via at the end of currently routed track." ), via_xpm ); static TOOL_ACTION ACT_PlaceBlindVia( "pcbnew.InteractiveRouter.PlaceBlindVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_BLIND_BURIED_VIA ), _( "Place Blind/Buried Via" ), _( "Adds a blind or buried via at the end of currently routed track."), via_buried_xpm ); static TOOL_ACTION ACT_PlaceMicroVia( "pcbnew.InteractiveRouter.PlaceMicroVia", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_ADD_MICROVIA ), _( "Place Microvia" ), _( "Adds a microvia at the end of currently routed track." ), via_microvia_xpm ); static TOOL_ACTION ACT_CustomTrackWidth( "pcbnew.InteractiveRouter.CustomTrackViaSize", AS_CONTEXT, 'Q', _( "Custom Track/Via Size" ), _( "Shows a dialog for changing the track width and via size." ), width_track_xpm ); static TOOL_ACTION ACT_SwitchPosture( "pcbnew.InteractiveRouter.SwitchPosture", AS_CONTEXT, TOOL_ACTION::LegacyHotKey( HK_SWITCH_TRACK_POSTURE ), _( "Switch Track Posture" ), _( "Switches posture of the currenly routed track." ), change_entry_orient_xpm ); static TOOL_ACTION ACT_SetDpDimensions( "pcbnew.InteractiveRouter.SetDpDimensions", AS_CONTEXT, 'P', _( "Differential Pair Dimensions..." ), _( "Sets the width and gap of the currently routed differential pair." ), ps_diff_pair_tune_length_xpm ); ROUTER_TOOL::ROUTER_TOOL() : PNS_TOOL_BASE( "pcbnew.InteractiveRouter" ) { } class CONTEXT_TRACK_WIDTH_MENU: public CONTEXT_TRACK_VIA_SIZE_MENU { public: CONTEXT_TRACK_WIDTH_MENU() : CONTEXT_TRACK_VIA_SIZE_MENU( true, true ), m_board( NULL ) { SetMenuHandler( boost::bind( &CONTEXT_TRACK_WIDTH_MENU::EventHandler, this, _1 ) ); } void SetBoard( BOARD* aBoard ) { m_board = aBoard; Append( ID_POPUP_PCB_SELECT_CUSTOM_WIDTH, _( "Custom size" ), wxEmptyString, wxITEM_CHECK ); Append( ID_POPUP_PCB_SELECT_AUTO_WIDTH, _( "Use the starting track width" ), _( "Route using the width of the starting track." ), wxITEM_CHECK ); Append( ID_POPUP_PCB_SELECT_USE_NETCLASS_VALUES, _( "Use net class values" ), _( "Use track and via sizes from the net class" ), wxITEM_CHECK ); AppendSeparator(); // Append the list of tracks & via sizes AppendSizes( aBoard ); } OPT_TOOL_EVENT EventHandler( const wxMenuEvent& aEvent ) { #if ID_POPUP_PCB_SELECT_VIASIZE1 < ID_POPUP_PCB_SELECT_WIDTH1 #error You have changed event ids order, it breaks code. Check the source code for more details. // Recognising type of event (track width/via size) is based on comparison if the event id is // within a specific range. If ranges of event ids changes, then the following is not valid anymore. #endif BOARD_DESIGN_SETTINGS &bds = m_board->GetDesignSettings(); int id = aEvent.GetId(); // Initial settings, to be modified below bds.m_UseConnectedTrackWidth = false; bds.UseCustomTrackViaSize( false ); if( id == ID_POPUP_PCB_SELECT_CUSTOM_WIDTH ) { bds.UseCustomTrackViaSize( true ); } else if( id == ID_POPUP_PCB_SELECT_AUTO_WIDTH ) { bds.m_UseConnectedTrackWidth = true; } else if( id == ID_POPUP_PCB_SELECT_USE_NETCLASS_VALUES ) { bds.SetViaSizeIndex( 0 ); bds.SetTrackWidthIndex( 0 ); } else if( id >= ID_POPUP_PCB_SELECT_VIASIZE1 ) // via size has changed { assert( id < ID_POPUP_PCB_SELECT_WIDTH_END_RANGE ); bds.SetViaSizeIndex( id - ID_POPUP_PCB_SELECT_VIASIZE1 ); } else // track width has changed { assert( id >= ID_POPUP_PCB_SELECT_WIDTH1 ); assert( id < ID_POPUP_PCB_SELECT_VIASIZE ); bds.SetTrackWidthIndex( id - ID_POPUP_PCB_SELECT_WIDTH1 ); } return OPT_TOOL_EVENT( COMMON_ACTIONS::trackViaSizeChanged.MakeEvent() ); } private: BOARD* m_board; }; class ROUTER_TOOL_MENU: public CONTEXT_MENU { public: ROUTER_TOOL_MENU( BOARD* aBoard, PNS_ROUTER_MODE aMode ) { SetTitle( _( "Interactive Router" ) ); Add( ACT_NewTrack ); Add( ACT_EndTrack ); // Add( ACT_AutoEndRoute ); // fixme: not implemented yet. Sorry. Add( ACT_Drag ); Add( ACT_PlaceThroughVia ); Add( ACT_PlaceBlindVia ); Add( ACT_PlaceMicroVia ); Add( ACT_SwitchPosture ); AppendSeparator(); m_widthMenu.SetBoard( aBoard ); Add( &m_widthMenu, _( "Select Track/Via Width" ) ); Add( ACT_CustomTrackWidth ); if( aMode == PNS_MODE_ROUTE_DIFF_PAIR ) Add( ACT_SetDpDimensions ); AppendSeparator(); Add( PNS_TOOL_BASE::ACT_RouterOptions ); } private: CONTEXT_TRACK_WIDTH_MENU m_widthMenu; }; ROUTER_TOOL::~ROUTER_TOOL() { m_savedSettings.Save( GetSettings() ); } bool ROUTER_TOOL::Init() { m_savedSettings.Load( GetSettings() ); return true; } void ROUTER_TOOL::Reset( RESET_REASON aReason ) { PNS_TOOL_BASE::Reset( aReason ); Go( &ROUTER_TOOL::RouteSingleTrace, COMMON_ACTIONS::routerActivateSingle.MakeEvent() ); Go( &ROUTER_TOOL::RouteDiffPair, COMMON_ACTIONS::routerActivateDiffPair.MakeEvent() ); Go( &ROUTER_TOOL::DpDimensionsDialog, COMMON_ACTIONS::routerActivateDpDimensionsDialog.MakeEvent() ); Go( &ROUTER_TOOL::SettingsDialog, COMMON_ACTIONS::routerActivateSettingsDialog.MakeEvent() ); Go( &ROUTER_TOOL::InlineDrag, COMMON_ACTIONS::routerInlineDrag.MakeEvent() ); } int ROUTER_TOOL::getDefaultWidth( int aNetCode ) { int w, d1, d2; getNetclassDimensions( aNetCode, w, d1, d2 ); return w; } void ROUTER_TOOL::getNetclassDimensions( int aNetCode, int& aWidth, int& aViaDiameter, int& aViaDrill ) { BOARD_DESIGN_SETTINGS &bds = m_board->GetDesignSettings(); NETCLASSPTR netClass; NETINFO_ITEM* ni = m_board->FindNet( aNetCode ); if( ni ) { wxString netClassName = ni->GetClassName(); netClass = bds.m_NetClasses.Find( netClassName ); } if( !netClass ) netClass = bds.GetDefault(); aWidth = netClass->GetTrackWidth(); aViaDiameter = netClass->GetViaDiameter(); aViaDrill = netClass->GetViaDrill(); } void ROUTER_TOOL::handleCommonEvents( const TOOL_EVENT& aEvent ) { #ifdef DEBUG if( aEvent.IsKeyPressed() ) { switch( aEvent.KeyCode() ) { case '0': TRACEn( 2, "saving drag/route log...\n" ); m_router->DumpLog(); break; } } else #endif if( aEvent.IsAction( &ACT_RouterOptions ) ) { DIALOG_PNS_SETTINGS settingsDlg( m_frame, m_router->Settings() ); if( settingsDlg.ShowModal() ) { // FIXME: do we need an explicit update? } } else if( aEvent.IsAction( &ACT_SetDpDimensions ) ) { PNS_SIZES_SETTINGS sizes = m_router->Sizes(); DIALOG_PNS_DIFF_PAIR_DIMENSIONS settingsDlg( m_frame, sizes ); if( settingsDlg.ShowModal() ) { m_router->UpdateSizes( sizes ); } } else if( aEvent.IsAction( &ACT_CustomTrackWidth ) ) { BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); DIALOG_TRACK_VIA_SIZE sizeDlg( m_frame, bds ); if( sizeDlg.ShowModal() ) { bds.UseCustomTrackViaSize( true ); m_toolMgr->RunAction( COMMON_ACTIONS::trackViaSizeChanged ); } } else if( aEvent.IsAction( &COMMON_ACTIONS::trackViaSizeChanged ) ) { PNS_SIZES_SETTINGS sizes( m_router->Sizes() ); sizes.ImportCurrent( m_board->GetDesignSettings() ); m_router->UpdateSizes( sizes ); } } int ROUTER_TOOL::getStartLayer( const PNS_ITEM* aItem ) { int tl = getView()->GetTopLayer(); if( m_startItem ) { const PNS_LAYERSET& ls = m_startItem->Layers(); if( ls.Overlaps( tl ) ) return tl; else return ls.Start(); } return tl; } void ROUTER_TOOL::switchLayerOnViaPlacement() { int al = m_frame->GetActiveLayer(); int cl = m_router->GetCurrentLayer(); if( cl != al ) { m_router->SwitchLayer( al ); } optional newLayer = m_router->Sizes().PairedLayer( cl ); if( !newLayer ) newLayer = m_router->Sizes().GetLayerTop(); m_router->SwitchLayer( *newLayer ); m_frame->SetActiveLayer( ToLAYER_ID( *newLayer ) ); } bool ROUTER_TOOL::onViaCommand( VIATYPE_T aType ) { BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings(); const int layerCount = bds.GetCopperLayerCount(); int currentLayer = m_router->GetCurrentLayer(); LAYER_ID pairTop = m_frame->GetScreen()->m_Route_Layer_TOP; LAYER_ID pairBottom = m_frame->GetScreen()->m_Route_Layer_BOTTOM; PNS_SIZES_SETTINGS sizes = m_router->Sizes(); // fixme: P&S supports more than one fixed layer pair. Update the dialog? sizes.ClearLayerPairs(); if( !m_router->IsPlacingVia() ) { // Cannot place microvias or blind vias if not allowed (obvious) if( ( aType == VIA_BLIND_BURIED ) && ( !bds.m_BlindBuriedViaAllowed ) ) { DisplayError( m_frame, _( "Blind/buried vias have to be enabled in the design settings." ) ); return false; } if( ( aType == VIA_MICROVIA ) && ( !bds.m_MicroViasAllowed ) ) { DisplayError( m_frame, _( "Microvias have to be enabled in the design settings." ) ); return false; } // Can only place through vias on 2-layer boards if( ( aType != VIA_THROUGH ) && ( layerCount <= 2 ) ) { DisplayError( m_frame, _( "Only through vias are allowed on 2 layer boards." ) ); return false; } // Can only place microvias if we're on an outer layer, or directly adjacent to one if( ( aType == VIA_MICROVIA ) && ( currentLayer > In1_Cu ) && ( currentLayer < layerCount - 2 ) ) { DisplayError( m_frame, _( "Microvias can be placed only between the outer layers " \ "(F.Cu/B.Cu) and the ones directly adjacent to them." ) ); return false; } } // Convert blind/buried via to a through hole one, if it goes through all layers if( aType == VIA_BLIND_BURIED && ( ( currentLayer == B_Cu ) || ( currentLayer == F_Cu ) ) && ( ( pairTop == B_Cu && pairBottom == F_Cu ) || ( pairTop == F_Cu && pairBottom == B_Cu ) ) ) { aType = VIA_THROUGH; } switch( aType ) { case VIA_THROUGH: sizes.SetViaDiameter( bds.GetCurrentViaSize() ); sizes.SetViaDrill( bds.GetCurrentViaDrill() ); sizes.AddLayerPair( pairTop, pairBottom ); break; case VIA_MICROVIA: sizes.SetViaDiameter( bds.GetCurrentMicroViaSize() ); sizes.SetViaDrill( bds.GetCurrentMicroViaDrill() ); if( currentLayer == F_Cu || currentLayer == In1_Cu ) sizes.AddLayerPair( F_Cu, In1_Cu ); else if( currentLayer == B_Cu || currentLayer == layerCount - 2 ) sizes.AddLayerPair( B_Cu, layerCount - 2 ); else wxASSERT( false ); break; case VIA_BLIND_BURIED: sizes.SetViaDiameter( bds.GetCurrentViaSize() ); sizes.SetViaDrill( bds.GetCurrentViaDrill() ); if( currentLayer == pairTop || currentLayer == pairBottom ) sizes.AddLayerPair( pairTop, pairBottom ); else sizes.AddLayerPair( pairTop, currentLayer ); break; default: wxASSERT( false ); break; } sizes.SetViaType( aType ); m_router->UpdateSizes( sizes ); m_router->ToggleViaPlacement(); m_router->Move( m_endSnapPoint, m_endItem ); // refresh return false; } bool ROUTER_TOOL::prepareInteractive() { int routingLayer = getStartLayer( m_startItem ); m_frame->SetActiveLayer( ToLAYER_ID( routingLayer ) ); // fixme: switch on invisible layer // for some reason I don't understand, GetNetclass() may return null sometimes... if( m_startItem && m_startItem->Net() >= 0 && m_startItem->Parent() && m_startItem->Parent()->GetNetClass() ) { highlightNet( true, m_startItem->Net() ); // Update track width and via size shown in main toolbar comboboxes m_frame->SetCurrentNetClass( m_startItem->Parent()->GetNetClass()->GetName() ); } else m_frame->SetCurrentNetClass( NETCLASS::Default ); m_ctls->ForceCursorPosition( false ); m_ctls->SetAutoPan( true ); PNS_SIZES_SETTINGS sizes( m_router->Sizes() ); sizes.Init( m_board, m_startItem ); sizes.AddLayerPair( m_frame->GetScreen()->m_Route_Layer_TOP, m_frame->GetScreen()->m_Route_Layer_BOTTOM ); m_router->UpdateSizes( sizes ); if( !m_router->StartRouting( m_startSnapPoint, m_startItem, routingLayer ) ) { DisplayError( m_frame, m_router->FailureReason() ); highlightNet( false ); return false; } m_endItem = NULL; m_endSnapPoint = m_startSnapPoint; return true; } bool ROUTER_TOOL::finishInteractive() { m_router->StopRouting(); // Save the recent changes in the undo buffer m_frame->SaveCopyInUndoList( m_router->GetUndoBuffer(), UR_UNSPECIFIED ); m_router->ClearUndoBuffer(); m_frame->OnModify(); m_ctls->SetAutoPan( false ); m_ctls->ForceCursorPosition( false ); highlightNet( false ); return true; } void ROUTER_TOOL::performRouting() { if( !prepareInteractive() ) return; while( OPT_TOOL_EVENT evt = Wait() ) { if( evt->IsCancel() || evt->IsActivate() ) break; else if( evt->IsMotion() ) { updateEndItem( *evt ); m_router->SetOrthoMode( evt->Modifier( MD_CTRL ) ); m_router->Move( m_endSnapPoint, m_endItem ); } else if( evt->IsClick( BUT_LEFT ) ) { updateEndItem( *evt ); bool needLayerSwitch = m_router->IsPlacingVia(); if( m_router->FixRoute( m_endSnapPoint, m_endItem ) ) break; if( needLayerSwitch ) switchLayerOnViaPlacement(); // Synchronize the indicated layer m_frame->SetActiveLayer( ToLAYER_ID( m_router->GetCurrentLayer() ) ); m_router->Move( m_endSnapPoint, m_endItem ); m_startItem = NULL; } else if( evt->IsAction( &ACT_PlaceThroughVia ) ) { onViaCommand( VIA_THROUGH ); } else if( evt->IsAction( &ACT_PlaceBlindVia ) ) { onViaCommand( VIA_BLIND_BURIED ); } else if( evt->IsAction( &ACT_PlaceMicroVia ) ) { onViaCommand( VIA_MICROVIA ); } else if( evt->IsAction( &ACT_SwitchPosture ) ) { m_router->FlipPosture(); m_router->Move( m_endSnapPoint, m_endItem ); // refresh } else if( evt->IsAction( &COMMON_ACTIONS::layerChanged ) ) { updateEndItem( *evt ); m_router->SwitchLayer( m_frame->GetActiveLayer() ); m_router->Move( m_endSnapPoint, m_endItem ); // refresh } else if( evt->IsAction( &ACT_EndTrack ) ) { if( m_router->FixRoute( m_endSnapPoint, m_endItem ) ) break; } handleCommonEvents( *evt ); } finishInteractive(); } int ROUTER_TOOL::DpDimensionsDialog( const TOOL_EVENT& aEvent ) { Activate(); PNS_SIZES_SETTINGS sizes = m_router->Sizes(); DIALOG_PNS_DIFF_PAIR_DIMENSIONS settingsDlg( m_frame, sizes ); if( settingsDlg.ShowModal() ) { m_router->UpdateSizes( sizes ); m_savedSizes = sizes; } return 0; } int ROUTER_TOOL::SettingsDialog( const TOOL_EVENT& aEvent ) { Activate(); DIALOG_PNS_SETTINGS settingsDlg( m_frame, m_router->Settings() ); if( settingsDlg.ShowModal() ) { m_savedSettings = m_router->Settings(); } return 0; } int ROUTER_TOOL::RouteSingleTrace( const TOOL_EVENT& aEvent ) { m_frame->SetToolID( ID_TRACK_BUTT, wxCURSOR_PENCIL, _( "Route Track" ) ); return mainLoop( PNS_MODE_ROUTE_SINGLE ); } int ROUTER_TOOL::RouteDiffPair( const TOOL_EVENT& aEvent ) { m_frame->SetToolID( ID_TRACK_BUTT, wxCURSOR_PENCIL, _( "Router Differential Pair" ) ); return mainLoop( PNS_MODE_ROUTE_DIFF_PAIR ); } int ROUTER_TOOL::mainLoop( PNS_ROUTER_MODE aMode ) { PCB_EDIT_FRAME* frame = getEditFrame(); BOARD* board = getModel(); // Deselect all items m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true ); Activate(); m_router->SetMode( aMode ); m_ctls->SetSnapping( true ); m_ctls->ShowCursor( true ); frame->UndoRedoBlock( true ); m_startSnapPoint = getViewControls()->GetCursorPosition(); std::auto_ptr ctxMenu( new ROUTER_TOOL_MENU( board, aMode ) ); SetContextMenu( ctxMenu.get() ); // Main loop: keep receiving events while( OPT_TOOL_EVENT evt = Wait() ) { if( m_needsSync ) { m_router->SyncWorld(); m_router->SetView( getView() ); m_needsSync = false; } if( evt->IsCancel() || evt->IsActivate() ) break; // Finish else if( evt->IsMotion() ) updateStartItem( *evt ); else if( evt->IsClick( BUT_LEFT ) || evt->IsAction( &ACT_NewTrack ) ) { updateStartItem( *evt ); if( evt->Modifier( MD_CTRL ) ) performDragging(); else performRouting(); } else if( evt->IsAction( &ACT_Drag ) ) { updateStartItem( *evt ); performDragging(); } else if( evt->IsAction( &ACT_PlaceThroughVia ) ) { m_toolMgr->RunAction( COMMON_ACTIONS::layerToggle, true ); } handleCommonEvents( *evt ); } frame->SetToolID( ID_NO_TOOL_SELECTED, wxCURSOR_DEFAULT, wxEmptyString ); frame->UndoRedoBlock( false ); // Store routing settings till the next invocation m_savedSettings = m_router->Settings(); m_savedSizes = m_router->Sizes(); return 0; } void ROUTER_TOOL::performDragging() { PCB_EDIT_FRAME* frame = getEditFrame(); VIEW_CONTROLS* ctls = getViewControls(); bool dragStarted = m_router->StartDragging( m_startSnapPoint, m_startItem ); if( !dragStarted ) return; if( m_startItem && m_startItem->Net() >= 0 ) highlightNet( true, m_startItem->Net() ); ctls->ForceCursorPosition( false ); ctls->SetAutoPan( true ); while( OPT_TOOL_EVENT evt = Wait() ) { if( evt->IsCancel() || evt->IsActivate() ) break; else if( evt->IsMotion() ) { updateEndItem( *evt ); m_router->Move( m_endSnapPoint, m_endItem ); } else if( evt->IsClick( BUT_LEFT ) ) { if( m_router->FixRoute( m_endSnapPoint, m_endItem ) ) break; } handleCommonEvents( *evt ); } if( m_router->RoutingInProgress() ) m_router->StopRouting(); // Save the recent changes in the undo buffer frame->SaveCopyInUndoList( m_router->GetUndoBuffer(), UR_UNSPECIFIED ); m_router->ClearUndoBuffer(); frame->OnModify(); m_startItem = NULL; ctls->SetAutoPan( false ); ctls->ForceCursorPosition( false ); highlightNet( false ); } int ROUTER_TOOL::InlineDrag( const TOOL_EVENT& aEvent ) { const BOARD_CONNECTED_ITEM *item = aEvent.Parameter(); PCB_EDIT_FRAME* frame = getEditFrame(); VIEW_CONTROLS* ctls = getViewControls(); m_toolMgr->RunAction( COMMON_ACTIONS::selectionClear, true ); Activate(); m_router->SyncWorld(); m_router->SetView( getView() ); m_startItem = m_router->GetWorld()->FindItemByParent( item ); VECTOR2I p0 = ctls->GetCursorPosition(); bool dragStarted = m_router->StartDragging( p0, m_startItem ); if( !dragStarted ) return 0; ctls->ForceCursorPosition( false ); ctls->SetAutoPan( true ); frame->UndoRedoBlock( true ); bool saveUndoBuffer = true; while( OPT_TOOL_EVENT evt = Wait() ) { p0 = ctls->GetCursorPosition(); if( evt->IsCancel() ) { saveUndoBuffer = false; break; } else if( evt->IsMotion() || evt->IsDrag( BUT_LEFT ) ) { m_router->Move( p0, NULL ); } else if( evt->IsMouseUp( BUT_LEFT ) || evt->IsClick( BUT_LEFT ) ) { saveUndoBuffer = m_router->FixRoute( p0, NULL ); break; } } if( m_router->RoutingInProgress() ) m_router->StopRouting(); if( saveUndoBuffer ) { frame->SaveCopyInUndoList( m_router->GetUndoBuffer(), UR_UNSPECIFIED ); m_router->ClearUndoBuffer(); frame->OnModify(); } ctls->SetAutoPan( false ); ctls->ShowCursor( false ); frame->UndoRedoBlock( false ); return 0; }