mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Compare commits
243 Commits
cc30552cc5
...
0f5a23a5d8
Author | SHA1 | Date | |
---|---|---|---|
|
0f5a23a5d8 | ||
|
18b56539a6 | ||
|
9b006c4f3b | ||
|
8035a66152 | ||
|
45166bf5c3 | ||
|
6ab6283e2e | ||
|
fc7d91214d | ||
|
dcbadb5857 | ||
|
3b97804cb6 | ||
|
e72def55a9 | ||
|
fcf40deae2 | ||
|
ef602be91f | ||
|
bd5cb76fcd | ||
|
de26550b5a | ||
|
7deff606be | ||
|
6e2b20ed0e | ||
|
2f1a91279f | ||
|
6e316d9faa | ||
|
3c5fb9d90d | ||
|
11cc86e586 | ||
|
5e2fd084b9 | ||
|
a1f816e8be | ||
|
abf3438ed6 | ||
|
cb4c8e6647 | ||
|
d356073798 | ||
|
b38d9d7f81 | ||
|
297ca1bb7b | ||
|
f9e25c2e06 | ||
|
b0a6dc4acf | ||
|
59dcdb4a4f | ||
|
39889f8446 | ||
|
3f8abe3744 | ||
|
0acc659676 | ||
|
78c93ee0fb | ||
|
9622cf8ff1 | ||
|
3cca1e87d8 | ||
|
aac15f4596 | ||
|
ceed9ca5f8 | ||
|
6c4edd178e | ||
|
cfa87cfc2c | ||
|
b3b75270ce | ||
|
f1db857804 | ||
|
cdd43fe5fa | ||
|
836b3267bb | ||
|
66930ea703 | ||
|
0e60722c4b | ||
|
2b3f21b9ff | ||
|
dd8a059925 | ||
|
63fe36847e | ||
|
99a16e4306 | ||
|
5ca8fdb7cd | ||
|
88ee07e910 | ||
|
71907f0888 | ||
|
6397b6d3c6 | ||
|
d1356140fa | ||
|
30d158a60f | ||
|
c674e1749e | ||
|
c783218f93 | ||
|
07885145b3 | ||
|
500da3c15d | ||
|
fbd2e36b79 | ||
|
61fd5e7413 | ||
|
bf8d2d7bf6 | ||
|
2a93e1bfb1 | ||
|
9f097d5ae8 | ||
|
80996e70f8 | ||
|
ec8131c7bf | ||
|
2ad7c345ec | ||
|
a2e0d07c87 | ||
|
4e1dd875ae | ||
|
c737e0b360 | ||
|
c29aa7643c | ||
|
58b2ccb3d5 | ||
|
a05f8b9e36 | ||
|
9f2b84ba52 | ||
|
b345e34423 | ||
|
081b414ad5 | ||
|
58caa5c496 | ||
|
f0f5472e4d | ||
|
0b174b5227 | ||
|
765838ecf7 | ||
|
2276ae05e3 | ||
|
6838d55051 | ||
|
95abb3be8b | ||
|
41425ac32d | ||
|
0b47169387 | ||
|
7b98a8d697 | ||
|
4184e60e24 | ||
|
36eb0e4a11 | ||
|
49c7f62548 | ||
|
35f0213011 | ||
|
cb87f83dfc | ||
|
ead7de69ca | ||
|
dedc10a163 | ||
|
330361d0cb | ||
|
01d32211ba | ||
|
09c40a0e0f | ||
|
fdbf740ee2 | ||
|
93b0004175 | ||
|
0a162ded84 | ||
|
01f6776226 | ||
|
adbc80aade | ||
|
0b102fc085 | ||
|
22288e02a2 | ||
|
58f4ca7ed6 | ||
|
8ae2ad3586 | ||
|
0f97f51ba1 | ||
|
0c35068e8d | ||
|
f66cbaf43a | ||
|
ac1b44715b | ||
|
b98621c8c8 | ||
|
28141ed350 | ||
|
c33da162d6 | ||
|
288c93198a | ||
|
46c8a6ee95 | ||
|
bbecc4eaa8 | ||
|
fef84a2c6a | ||
|
963ec84587 | ||
|
4a87dbe472 | ||
|
7d67f82217 | ||
|
b7fee045e3 | ||
|
0e5a87546b | ||
|
5489daf279 | ||
|
922699f388 | ||
|
702f774a1f | ||
|
264029c8e9 | ||
|
c889a7fa16 | ||
|
fb558eee3c | ||
|
6f352ccd96 | ||
|
c163f0a24c | ||
|
fd7da138fd | ||
|
3bdf44d4a4 | ||
|
168975b0a8 | ||
|
03b3b642b0 | ||
|
f9f5bb9ed1 | ||
|
4254673be5 | ||
|
0ce952e705 | ||
|
9ffb0c8b96 | ||
|
44cc5b8e93 | ||
|
a857ea77d9 | ||
|
bae5d43c45 | ||
|
2cbd441042 | ||
|
9a7088c03c | ||
|
7a09960b46 | ||
|
05d04e665a | ||
|
9f1aa612ec | ||
|
9de2548064 | ||
|
825585847b | ||
|
6c8f25b418 | ||
|
a899da7cb7 | ||
|
a49e3b9cc5 | ||
|
9bc1cf006f | ||
|
dc7e0665c3 | ||
|
344fab5741 | ||
|
9342aea7fa | ||
|
11c5e03890 | ||
|
3deb06bf82 | ||
|
5c865cab36 | ||
|
f7fd832c67 | ||
|
62e220a8db | ||
|
622ddd3a37 | ||
|
9f06d1f66b | ||
|
f84ba3004d | ||
|
e85a8056f5 | ||
|
173e02eff7 | ||
|
876c905e2b | ||
|
5914b5aaa8 | ||
|
2346be9768 | ||
|
d4b08f0a8a | ||
|
f18c4a05fd | ||
|
ad80113f47 | ||
|
8f0b3b59f0 | ||
|
bf82217217 | ||
|
d9741fd46a | ||
|
826f15a103 | ||
|
edf8f45351 | ||
|
202619cd43 | ||
|
8709f73b98 | ||
|
e76b2089bb | ||
|
2e0088593e | ||
|
b34746e06b | ||
|
9b14434fc3 | ||
|
1f1f8699e0 | ||
|
0ede830f54 | ||
|
de8c4d4b01 | ||
|
e01dfd3158 | ||
|
cff261ce2e | ||
|
6a171e11fb | ||
|
1e272ca21b | ||
|
60a5c8b742 | ||
|
9cba910d53 | ||
|
35ffb28335 | ||
|
98b63389c1 | ||
|
ce140d8dfc | ||
|
5e2ec53165 | ||
|
d5e342b64d | ||
|
eedfb7a573 | ||
|
c95c15dd4e | ||
|
4a4725c906 | ||
|
667dccb163 | ||
|
4fb76404bc | ||
|
267ee811a7 | ||
|
e016dc52fd | ||
|
c4d9338097 | ||
|
5d967289ee | ||
|
262f1fdabb | ||
|
c9476caebf | ||
|
a8d6292d61 | ||
|
4953e80122 | ||
|
bd04d508e0 | ||
|
82c61efcbd | ||
|
4024a15a17 | ||
|
0c8e7a7137 | ||
|
1f44417ad0 | ||
|
5b31b11731 | ||
|
5fb55ccb30 | ||
|
2eb7ba2fb9 | ||
|
2c4d792358 | ||
|
77a2330a33 | ||
|
7b4f4540b4 | ||
|
1885f4bdee | ||
|
eba8d0748c | ||
|
f5904cf2ac | ||
|
bbc762a546 | ||
|
c6c8e19e39 | ||
|
1309b436da | ||
|
c47b7459aa | ||
|
a2e98fb53c | ||
|
2321285c16 | ||
|
2cdeb0cd7c | ||
|
4af585c531 | ||
|
8c6f057f6c | ||
|
9d7db3d135 | ||
|
b22f259ccb | ||
|
b86cddbbe9 | ||
|
053c46aa72 | ||
|
4610a49323 | ||
|
68f9b74ae9 | ||
|
0a8ff763e1 | ||
|
7155e97dbf | ||
|
be2ef5686c | ||
|
e160be74d3 | ||
|
516a9d8008 |
@ -8,6 +8,8 @@ win64_build:
|
||||
interruptible: false
|
||||
image: registry.gitlab.com/kicad/kicad-ci/windows-build-image/ltsc2022-msvc:latest
|
||||
variables:
|
||||
VCPKG_BINARY_SOURCES: 'nuget,gitlab,readwrite'
|
||||
VCPKG_DISABLE_COMPILER_TRACKING: '1'
|
||||
# Switch the compressor to fastzip and reduce the compression level
|
||||
FF_USE_FASTZIP: "true"
|
||||
CACHE_COMPRESSION_LEVEL: "fast"
|
||||
@ -23,6 +25,7 @@ win64_build:
|
||||
script:
|
||||
- C:\builder\build.ps1 -Env -Arch x64
|
||||
- $vcpkgCache=Join-Path -Path (Get-Location) -ChildPath ".vcpkgCache";$env:VCPKG_DEFAULT_BINARY_CACHE=$vcpkgCache;New-Item -ItemType Directory -Force -Path $vcpkgCache
|
||||
- nuget.exe sources add -Name gitlab -Source "https://gitlab.com/api/v4/projects/27426693/packages/nuget/index.json" -UserName gitlab-ci-token -Password $env:CI_JOB_TOKEN
|
||||
- mkdir -p build/windows -Force
|
||||
- cd build/windows
|
||||
- cmake `
|
||||
@ -37,8 +40,6 @@ win64_build:
|
||||
../../
|
||||
- cmake --build . 2>&1 | tee compilation_log.txt
|
||||
- cd ../../
|
||||
after_script:
|
||||
- Get-Content -Path C:\builder\vcpkg\buildtrees\wxpython-33\python3-tool-post-install-err.log
|
||||
artifacts:
|
||||
# Only save the artifacts that are needed for running the tests in the next stage
|
||||
# and the compilation log. The entire build directory is too large to save as an
|
||||
|
@ -20,6 +20,7 @@
|
||||
variables:
|
||||
BOOST_TEST_LOGGER: 'JUNIT,warning,test_results.${TEST}.xml:HRF,message'
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
BOOST_TEST_CATCH_SYSTEM_ERRORS: 'no'
|
||||
script:
|
||||
- cd build/linux/qa
|
||||
- ctest -R qa_${TEST}
|
||||
|
@ -368,11 +368,12 @@ private:
|
||||
void createTrackWithMargin( const PCB_TRACK* aTrack, CONTAINER_2D_BASE* aDstContainer,
|
||||
PCB_LAYER_ID aLayer, int aMargin = 0 );
|
||||
|
||||
// Generate the pad shape on board layers. The pad hole is not generated by createPadWithMargin
|
||||
void createPadWithMargin( const PAD *aPad, CONTAINER_2D_BASE* aDstContainer,
|
||||
PCB_LAYER_ID aLayer, const VECTOR2I& aMargin ) const;
|
||||
|
||||
void createPadWithHole( const PAD* aPad, CONTAINER_2D_BASE* aDstContainer,
|
||||
int aInflateValue );
|
||||
// Generate the hole shape of aPad, stored in aDstContainer
|
||||
void createPadHoleShape( const PAD* aPad, CONTAINER_2D_BASE* aDstContainer, int aInflateValue );
|
||||
|
||||
void addPads( const FOOTPRINT* aFootprint, CONTAINER_2D_BASE* aDstContainer,
|
||||
PCB_LAYER_ID aLayerId );
|
||||
|
@ -474,12 +474,12 @@ void BOARD_ADAPTER::createPadWithMargin( const PAD* aPad, CONTAINER_2D_BASE* aCo
|
||||
}
|
||||
|
||||
|
||||
void BOARD_ADAPTER::createPadWithHole( const PAD* aPad, CONTAINER_2D_BASE* aDstContainer,
|
||||
void BOARD_ADAPTER::createPadHoleShape( const PAD* aPad, CONTAINER_2D_BASE* aDstContainer,
|
||||
int aInflateValue )
|
||||
{
|
||||
if( !aPad->HasHole() )
|
||||
{
|
||||
wxLogTrace( m_logTrace, wxT( "BOARD_ADAPTER::createPadWithHole - found an invalid pad" ) );
|
||||
wxLogTrace( m_logTrace, wxT( "BOARD_ADAPTER::createPadHole pad has no hole" ) );
|
||||
return;
|
||||
}
|
||||
|
||||
@ -667,6 +667,9 @@ void BOARD_ADAPTER::addShape( const PCB_SHAPE* aShape, CONTAINER_2D_BASE* aConta
|
||||
TO_SFVEC2F( r_outline.CPoint( ii+1 ) ),
|
||||
linewidth3DU, *aOwner );
|
||||
}
|
||||
|
||||
addROUND_SEGMENT_2D( aContainer, TO_SFVEC2F( r_outline.CLastPoint() ),
|
||||
TO_SFVEC2F( r_outline.CPoint( 0 ) ), linewidth3DU, *aOwner );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -507,27 +507,32 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
|
||||
{
|
||||
for( PAD* pad : footprint->Pads() )
|
||||
{
|
||||
const VECTOR2I padHole = pad->GetDrillSize();
|
||||
|
||||
if( !padHole.x ) // Not drilled pad like SMD pad
|
||||
// Note: holes of NPTH are already built by GetBoardPolygonOutlines
|
||||
if( !pad->HasHole() )
|
||||
continue;
|
||||
|
||||
// The hole in the body is inflated by copper thickness, if not plated, no copper
|
||||
int inflate = 0;
|
||||
|
||||
if( pad->GetAttribute() != PAD_ATTRIB::NPTH )
|
||||
inflate = KiROUND( GetHolePlatingThickness() / 2.0 );
|
||||
|
||||
m_holeCount++;
|
||||
double holeDiameter = ( pad->GetDrillSize().x + pad->GetDrillSize().y ) / 2.0;
|
||||
m_averageHoleDiameter += static_cast<float>( holeDiameter * m_biuTo3Dunits );
|
||||
|
||||
createPadWithHole( pad, &m_TH_ODs, inflate );
|
||||
if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
|
||||
{
|
||||
// Ensure the silk drawings are clipped to the NPTH hole, like other pad/via holes
|
||||
// even if the clip to board body is not activated (remember NPTH holes are part of
|
||||
// the board body)
|
||||
createPadHoleShape( pad, &m_TH_ODs, 0 );
|
||||
continue;
|
||||
}
|
||||
|
||||
// The hole in the body is inflated by copper thickness
|
||||
int inflate = KiROUND( GetHolePlatingThickness() / 2.0 );
|
||||
|
||||
createPadHoleShape( pad, &m_TH_ODs, inflate );
|
||||
|
||||
if( cfg.clip_silk_on_via_annuli )
|
||||
createPadWithHole( pad, &m_viaAnnuli, inflate );
|
||||
createPadHoleShape( pad, &m_viaAnnuli, inflate );
|
||||
|
||||
createPadWithHole( pad, &m_TH_IDs, 0 );
|
||||
createPadHoleShape( pad, &m_TH_IDs, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,9 +544,7 @@ void BOARD_ADAPTER::createLayers( REPORTER* aStatusReporter )
|
||||
{
|
||||
for( PAD* pad : footprint->Pads() )
|
||||
{
|
||||
const VECTOR2I padHole = pad->GetDrillSize();
|
||||
|
||||
if( !padHole.x ) // Not drilled pad like SMD pad
|
||||
if( !pad->HasHole() )
|
||||
continue;
|
||||
|
||||
// The hole in the body is inflated by copper thickness.
|
||||
|
@ -35,14 +35,22 @@
|
||||
#include <advanced_config.h>
|
||||
#include <build_version.h>
|
||||
#include <board.h>
|
||||
#include <pad.h>
|
||||
#include <pcb_field.h>
|
||||
#include <reporter.h>
|
||||
#include <gal/opengl/gl_context_mgr.h>
|
||||
#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
|
||||
#include <bitmaps.h>
|
||||
#include <kiway_holder.h>
|
||||
#include <kiway.h>
|
||||
#include <macros.h>
|
||||
#include <pgm_base.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <tool/tool_dispatcher.h>
|
||||
#include <string_utils.h>
|
||||
#include <mail_type.h>
|
||||
#include <kiway_express.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <widgets/wx_busy_indicator.h>
|
||||
|
||||
@ -95,25 +103,9 @@ EDA_3D_CANVAS::EDA_3D_CANVAS( wxWindow* aParent, const wxGLAttributes& aGLAttrib
|
||||
HIDPI_GL_3D_CANVAS( EDA_DRAW_PANEL_GAL::GetVcSettings(), aCamera, aParent, aGLAttribs,
|
||||
EDA_3D_CANVAS_ID, wxDefaultPosition,
|
||||
wxDefaultSize, wxFULL_REPAINT_ON_RESIZE ),
|
||||
m_eventDispatcher( nullptr ),
|
||||
m_parentStatusBar( nullptr ),
|
||||
m_parentInfoBar( nullptr ),
|
||||
m_glRC( nullptr ),
|
||||
m_is_opengl_initialized( false ),
|
||||
m_is_opengl_version_supported( true ),
|
||||
m_editing_timeout_timer( this, wxID_HIGHEST + 1 ),
|
||||
m_redraw_trigger_timer( this, wxID_HIGHEST + 2 ),
|
||||
m_render_pivot( false ),
|
||||
m_camera_moving_speed( 1.0f ),
|
||||
m_strtime_camera_movement( 0 ),
|
||||
m_animation_enabled( true ),
|
||||
m_moving_speed_multiplier( 3 ),
|
||||
m_boardAdapter( aBoardAdapter ),
|
||||
m_3d_render( nullptr ),
|
||||
m_opengl_supports_raytracing( true ),
|
||||
m_render_raytracing_was_requested( false ),
|
||||
m_accelerator3DShapes( nullptr ),
|
||||
m_currentRollOverItem( nullptr )
|
||||
m_boardAdapter( aBoardAdapter )
|
||||
{
|
||||
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::EDA_3D_CANVAS" ) );
|
||||
|
||||
@ -482,7 +474,9 @@ void EDA_3D_CANVAS::DoRePaint()
|
||||
if( m_camera_is_moving )
|
||||
{
|
||||
const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
|
||||
curtime_delta_s = ( curtime_delta / 1e6 ) * m_camera_moving_speed;
|
||||
// Convert microseconds to seconds as float and apply speed multiplier
|
||||
curtime_delta_s = static_cast<float>( static_cast<double>( curtime_delta ) / 1e6 )
|
||||
* m_camera_moving_speed;
|
||||
m_camera.Interpolate( curtime_delta_s );
|
||||
|
||||
if( curtime_delta_s > 1.0f )
|
||||
@ -751,7 +745,8 @@ void EDA_3D_CANVAS::RenderToFrameBuffer( unsigned char* buffer, int width, int h
|
||||
if( m_camera_is_moving )
|
||||
{
|
||||
const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
|
||||
curtime_delta_s = ( curtime_delta / 1e6 ) * m_camera_moving_speed;
|
||||
curtime_delta_s = static_cast<float>( static_cast<double>( curtime_delta ) / 1e6 )
|
||||
* m_camera_moving_speed;
|
||||
m_camera.Interpolate( curtime_delta_s );
|
||||
|
||||
if( curtime_delta_s > 1.0f )
|
||||
@ -887,7 +882,7 @@ void EDA_3D_CANVAS::OnZoomGesture( wxZoomGestureEvent& aEvent )
|
||||
m_camera.Pan( aEvent.GetPosition() );
|
||||
m_camera.SetCurMousePosition( aEvent.GetPosition() );
|
||||
|
||||
m_camera.Zoom( aEvent.GetZoomFactor() / m_gestureLastZoomFactor );
|
||||
m_camera.Zoom( static_cast<float>( aEvent.GetZoomFactor() / m_gestureLastZoomFactor ) );
|
||||
|
||||
m_gestureLastZoomFactor = aEvent.GetZoomFactor();
|
||||
|
||||
@ -930,7 +925,7 @@ void EDA_3D_CANVAS::OnRotateGesture( wxRotateGestureEvent& aEvent )
|
||||
if( m_camera_is_moving )
|
||||
return;
|
||||
|
||||
m_camera.RotateScreen( m_gestureLastAngle - aEvent.GetRotationAngle() );
|
||||
m_camera.RotateScreen( static_cast<float>( m_gestureLastAngle - aEvent.GetRotationAngle() ) );
|
||||
m_gestureLastAngle = aEvent.GetRotationAngle();
|
||||
|
||||
DisplayStatus();
|
||||
@ -1068,7 +1063,43 @@ void EDA_3D_CANVAS::OnLeftDown( wxMouseEvent& event )
|
||||
|
||||
BOARD_ITEM* intersectedBoardItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
|
||||
|
||||
// !TODO: send a selection item to pcbnew, eg: via kiway?
|
||||
if( intersectedBoardItem )
|
||||
{
|
||||
FOOTPRINT* footprint = nullptr;
|
||||
|
||||
switch( intersectedBoardItem->Type() )
|
||||
{
|
||||
case PCB_FOOTPRINT_T:
|
||||
footprint = static_cast<FOOTPRINT*>( intersectedBoardItem );
|
||||
break;
|
||||
|
||||
case PCB_PAD_T:
|
||||
footprint = static_cast<PAD*>( intersectedBoardItem )->GetParentFootprint();
|
||||
break;
|
||||
|
||||
case PCB_FIELD_T:
|
||||
footprint = static_cast<PCB_FIELD*>( intersectedBoardItem )->GetParentFootprint();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if( footprint )
|
||||
{
|
||||
std::string command =
|
||||
fmt::format( "$SELECT: 0,F{}",
|
||||
EscapeString( footprint->GetReference(), CTX_IPC ).ToStdString() );
|
||||
|
||||
EDA_3D_VIEWER_FRAME* frame = static_cast<EDA_3D_VIEWER_FRAME*>( GetParent() );
|
||||
|
||||
if( frame )
|
||||
{
|
||||
frame->Kiway().ExpressMail( FRAME_PCB_EDITOR, MAIL_SELECTION, command, frame );
|
||||
frame->Kiway().ExpressMail( FRAME_SCH, MAIL_SELECTION, command, frame );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1088,14 +1119,14 @@ void EDA_3D_CANVAS::OnLeftUp( wxMouseEvent& event )
|
||||
int logicalW = logicalSize.GetWidth();
|
||||
int logicalH = logicalSize.GetHeight();
|
||||
|
||||
int gizmo_x, gizmo_y, gizmo_width, gizmo_height;
|
||||
int gizmo_x = 0, gizmo_y = 0, gizmo_width = 0, gizmo_height = 0;
|
||||
std::tie( gizmo_x, gizmo_y, gizmo_width, gizmo_height ) = m_3d_render_opengl->getGizmoViewport();
|
||||
|
||||
float scaleX = static_cast<float>( gizmo_width ) / logicalW;
|
||||
float scaleY = static_cast<float>( gizmo_height ) / logicalH;
|
||||
float scaleX = static_cast<float>( static_cast<double>( gizmo_width ) / static_cast<double>( logicalW ) );
|
||||
float scaleY = static_cast<float>( static_cast<double>( gizmo_height ) / static_cast<double>( logicalH ) );
|
||||
|
||||
int scaledMouseX = static_cast<int>( event.GetX() * scaleX );
|
||||
int scaledMouseY = static_cast<int>( ( logicalH - event.GetY() ) * scaleY );
|
||||
int scaledMouseX = static_cast<int>( static_cast<float>( event.GetX() ) * scaleX );
|
||||
int scaledMouseY = static_cast<int>( static_cast<float>( logicalH - event.GetY() ) * scaleY );
|
||||
|
||||
m_3d_render_opengl->handleGizmoMouseInput( scaledMouseX, scaledMouseY );
|
||||
Refresh();
|
||||
@ -1229,7 +1260,7 @@ void EDA_3D_CANVAS::request_start_moving_camera( float aMovingSpeed, bool aRende
|
||||
|
||||
// Map speed multiplier option to actual multiplier value
|
||||
// [1,2,3,4,5] -> [0.25, 0.5, 1, 2, 4]
|
||||
aMovingSpeed *= ( 1 << m_moving_speed_multiplier ) / 8.0f;
|
||||
aMovingSpeed *= static_cast<float>( ( 1 << m_moving_speed_multiplier ) ) / 8.0f;
|
||||
|
||||
m_render_pivot = aRenderPivot;
|
||||
m_camera_moving_speed = aMovingSpeed;
|
||||
@ -1249,7 +1280,7 @@ void EDA_3D_CANVAS::move_pivot_based_on_cur_mouse_position()
|
||||
{
|
||||
RAY mouseRay = getRayAtCurrentMousePosition();
|
||||
|
||||
float hit_t;
|
||||
float hit_t = 0.0f;
|
||||
|
||||
// Test it with the board bounding box
|
||||
if( m_boardAdapter.GetBBox().Intersect( mouseRay, &hit_t ) )
|
||||
|
@ -42,7 +42,7 @@ class RENDER_3D_RAYTRACE_GL;
|
||||
class RENDER_3D_OPENGL;
|
||||
|
||||
|
||||
#define EDA_3D_CANVAS_ID wxID_HIGHEST + 1321
|
||||
#define EDA_3D_CANVAS_ID (wxID_HIGHEST + 1321)
|
||||
|
||||
/**
|
||||
* Implement a canvas based on a wxGLCanvas
|
||||
@ -61,7 +61,7 @@ public:
|
||||
EDA_3D_CANVAS( wxWindow* aParent, const wxGLAttributes& aGLAttribs, BOARD_ADAPTER& aSettings,
|
||||
CAMERA& aCamera, S3D_CACHE* a3DCachePointer );
|
||||
|
||||
~EDA_3D_CANVAS();
|
||||
~EDA_3D_CANVAS() override;
|
||||
|
||||
/**
|
||||
* Set a dispatcher that processes events and forwards them to tools.
|
||||
@ -302,36 +302,36 @@ private:
|
||||
RAY getRayAtCurrentMousePosition();
|
||||
|
||||
private:
|
||||
TOOL_DISPATCHER* m_eventDispatcher;
|
||||
wxStatusBar* m_parentStatusBar; // Parent statusbar to report progress
|
||||
WX_INFOBAR* m_parentInfoBar;
|
||||
TOOL_DISPATCHER* m_eventDispatcher = nullptr;
|
||||
wxStatusBar* m_parentStatusBar = nullptr; // Parent statusbar to report progress
|
||||
WX_INFOBAR* m_parentInfoBar = nullptr;
|
||||
|
||||
wxGLContext* m_glRC; // Current OpenGL context
|
||||
bool m_is_opengl_initialized;
|
||||
bool m_is_opengl_version_supported;
|
||||
wxGLContext* m_glRC = nullptr; // Current OpenGL context
|
||||
bool m_is_opengl_initialized = false;
|
||||
bool m_is_opengl_version_supported = true;
|
||||
|
||||
wxTimer m_editing_timeout_timer; // Expires after some time signaling that
|
||||
// the mouse / keyboard movements are over
|
||||
wxTimer m_redraw_trigger_timer; // Used to schedule a redraw event
|
||||
std::atomic_flag m_is_currently_painting; // Avoid drawing twice at the same time
|
||||
std::atomic_flag m_is_currently_painting = ATOMIC_FLAG_INIT; // Avoid drawing twice at the same time
|
||||
|
||||
bool m_render_pivot; // Render the pivot while camera moving
|
||||
float m_camera_moving_speed; // 1.0f will be 1:1
|
||||
int64_t m_strtime_camera_movement; // Ticktime of camera movement start
|
||||
bool m_animation_enabled; // Camera animation enabled
|
||||
int m_moving_speed_multiplier; // Camera animation speed multiplier option
|
||||
bool m_render_pivot = false; // Render the pivot while camera moving
|
||||
float m_camera_moving_speed = 1.0f; // 1.0f will be 1:1
|
||||
int64_t m_strtime_camera_movement = 0; // Ticktime of camera movement start
|
||||
bool m_animation_enabled = true; // Camera animation enabled
|
||||
int m_moving_speed_multiplier = 3; // Camera animation speed multiplier option
|
||||
|
||||
BOARD_ADAPTER& m_boardAdapter; // Pre-computed 3D info and settings
|
||||
RENDER_3D_BASE* m_3d_render;
|
||||
RENDER_3D_BASE* m_3d_render = nullptr;
|
||||
RENDER_3D_RAYTRACE_GL* m_3d_render_raytracing;
|
||||
RENDER_3D_OPENGL* m_3d_render_opengl;
|
||||
|
||||
bool m_opengl_supports_raytracing;
|
||||
bool m_render_raytracing_was_requested;
|
||||
bool m_opengl_supports_raytracing = true;
|
||||
bool m_render_raytracing_was_requested = false;
|
||||
|
||||
ACCELERATOR_3D* m_accelerator3DShapes; // used for mouse over searching
|
||||
ACCELERATOR_3D* m_accelerator3DShapes = nullptr; // used for mouse over searching
|
||||
|
||||
BOARD_ITEM* m_currentRollOverItem;
|
||||
BOARD_ITEM* m_currentRollOverItem = nullptr;
|
||||
|
||||
bool m_render3dmousePivot = false; // Render the 3dmouse pivot
|
||||
SFVEC3F m_3dmousePivotPos; // The position of the 3dmouse pivot
|
||||
|
@ -819,10 +819,7 @@ void RENDER_3D_OPENGL::generateViasAndPads()
|
||||
{
|
||||
if( pad->GetAttribute() != PAD_ATTRIB::NPTH )
|
||||
{
|
||||
const VECTOR2I drillsize = pad->GetDrillSize();
|
||||
const bool hasHole = drillsize.x && drillsize.y;
|
||||
|
||||
if( !hasHole )
|
||||
if( !pad->HasHole() )
|
||||
continue;
|
||||
|
||||
pad->TransformHoleToPolygon( tht_outer_holes_poly, platingThickness,
|
||||
|
@ -277,7 +277,7 @@ void RENDER_3D_RAYTRACE_BASE::renderTracing( uint8_t* ptrPBO, REPORTER* aStatusR
|
||||
BS::multi_future<void> futures;
|
||||
|
||||
for( size_t i = 0; i < tp.get_thread_count(); ++i )
|
||||
futures.push_back( tp.submit( processBlocks ) );
|
||||
futures.push_back( tp.submit_task( processBlocks ) );
|
||||
|
||||
futures.wait();
|
||||
|
||||
@ -486,9 +486,12 @@ void RENDER_3D_RAYTRACE_BASE::renderBlockTracing( uint8_t* ptrPBO, signed int iB
|
||||
// Initialize ray packets
|
||||
const SFVEC2UI& blockPos = m_blockPositions[iBlock];
|
||||
const SFVEC2I blockPosI = SFVEC2I( blockPos.x + m_xoffset, blockPos.y + m_yoffset );
|
||||
const SFVEC2F randDisp = ( m_camera.GetProjection() == PROJECTION_TYPE::ORTHO ) ?
|
||||
SFVEC2F( 0.0f, 0.0f ) :
|
||||
SFVEC2F( DISP_FACTOR, DISP_FACTOR );
|
||||
|
||||
RAYPACKET blockPacket( m_camera, (SFVEC2F) blockPosI + SFVEC2F( DISP_FACTOR, DISP_FACTOR ),
|
||||
SFVEC2F( DISP_FACTOR, DISP_FACTOR ) /* Displacement random factor */ );
|
||||
RAYPACKET blockPacket( m_camera, (SFVEC2F) blockPosI + randDisp,
|
||||
randDisp /* Displacement random factor */ );
|
||||
|
||||
|
||||
HITINFO_PACKET hitPacket_X0Y0[RAYPACKET_RAYS_PER_PACKET];
|
||||
@ -566,7 +569,7 @@ void RENDER_3D_RAYTRACE_BASE::renderBlockTracing( uint8_t* ptrPBO, signed int iB
|
||||
HITINFO_PACKET_init( hitPacket_AA_X1Y1 );
|
||||
|
||||
RAYPACKET blockPacket_AA_X1Y1( m_camera, (SFVEC2F) blockPosI + SFVEC2F( 0.5f, 0.5f ),
|
||||
SFVEC2F( DISP_FACTOR, DISP_FACTOR ) );
|
||||
randDisp );
|
||||
|
||||
if( !m_accelerator->Intersect( blockPacket_AA_X1Y1, hitPacket_AA_X1Y1 ) )
|
||||
{
|
||||
@ -603,16 +606,16 @@ void RENDER_3D_RAYTRACE_BASE::renderBlockTracing( uint8_t* ptrPBO, signed int iB
|
||||
RAY blockRayPck_AA_X1Y1_half[RAYPACKET_RAYS_PER_PACKET];
|
||||
|
||||
RAYPACKET_InitRays_with2DDisplacement(
|
||||
m_camera, (SFVEC2F) blockPosI + SFVEC2F( 0.5f - DISP_FACTOR, DISP_FACTOR ),
|
||||
SFVEC2F( DISP_FACTOR, DISP_FACTOR ), blockRayPck_AA_X1Y0 );
|
||||
m_camera, (SFVEC2F) blockPosI + SFVEC2F( 0.5f - randDisp.x, randDisp.y ),
|
||||
randDisp, blockRayPck_AA_X1Y0 );
|
||||
|
||||
RAYPACKET_InitRays_with2DDisplacement(
|
||||
m_camera, (SFVEC2F) blockPosI + SFVEC2F( DISP_FACTOR, 0.5f - DISP_FACTOR ),
|
||||
SFVEC2F( DISP_FACTOR, DISP_FACTOR ), blockRayPck_AA_X0Y1 );
|
||||
m_camera, (SFVEC2F) blockPosI + SFVEC2F( randDisp.x, 0.5f - randDisp.y ),
|
||||
randDisp, blockRayPck_AA_X0Y1 );
|
||||
|
||||
RAYPACKET_InitRays_with2DDisplacement(
|
||||
m_camera, (SFVEC2F) blockPosI + SFVEC2F( 0.25f - DISP_FACTOR, 0.25f - DISP_FACTOR ),
|
||||
SFVEC2F( DISP_FACTOR, DISP_FACTOR ), blockRayPck_AA_X1Y1_half );
|
||||
m_camera, (SFVEC2F) blockPosI + SFVEC2F( 0.25f - randDisp.x, 0.25f - randDisp.y ),
|
||||
randDisp, blockRayPck_AA_X1Y1_half );
|
||||
|
||||
renderAntiAliasPackets( bgColor, hitPacket_X0Y0, hitPacket_AA_X1Y1, blockRayPck_AA_X1Y0,
|
||||
hitColor_AA_X1Y0 );
|
||||
|
@ -48,6 +48,7 @@ project( kicad )
|
||||
|
||||
# Create a default build type for our QA that doesn't include `NDEBUG`
|
||||
set(CMAKE_CXX_FLAGS_QABUILD "-Os -g1 -ggdb1")
|
||||
string( APPEND CMAKE_EXE_LINKER_FLAGS_QABUILD " -rdynamic" )
|
||||
|
||||
include( GNUInstallDirs )
|
||||
include( CMakeDependentOption )
|
||||
|
@ -130,6 +130,9 @@ set( KICOMMON_SRCS
|
||||
project/project_local_settings.cpp
|
||||
project/time_domain_parameters.cpp
|
||||
|
||||
text_eval/text_eval_wrapper.cpp
|
||||
text_eval/text_eval_parser.cpp
|
||||
|
||||
# This is basically a settings object, but for the toolbar
|
||||
tool/ui/toolbar_configuration.cpp
|
||||
|
||||
@ -542,6 +545,7 @@ set( COMMON_DRAWING_SHEET_SRCS
|
||||
|
||||
set( COMMON_PREVIEW_ITEMS_SRCS
|
||||
preview_items/anchor_debug.cpp
|
||||
preview_items/angle_item.cpp
|
||||
preview_items/arc_assistant.cpp
|
||||
preview_items/arc_geom_manager.cpp
|
||||
preview_items/bezier_assistant.cpp
|
||||
@ -629,7 +633,8 @@ set( COMMON_GIT_SRCS
|
||||
git/project_git_utils.cpp
|
||||
git/kicad_git_common.cpp
|
||||
git/kicad_git_errors.cpp
|
||||
git/project_git_utils.cpp
|
||||
git/git_backend.cpp
|
||||
git/libgit_backend.cpp
|
||||
)
|
||||
|
||||
set( COMMON_SRCS
|
||||
@ -876,11 +881,13 @@ set( PCB_COMMON_SRCS
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pcb_marker.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/footprint.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/fix_board_shape.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/layer_utils.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/netinfo_item.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/netinfo_list.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pad.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pad_utils.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/padstack.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pcb_point.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pcb_target.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pcb_reference_image.cpp
|
||||
${CMAKE_SOURCE_DIR}/pcbnew/pcb_field.cpp
|
||||
@ -1011,6 +1018,13 @@ generate_lemon_grammar(
|
||||
libeval_compiler/grammar.lemon
|
||||
)
|
||||
|
||||
generate_lemon_grammar(
|
||||
kicommon
|
||||
text_eval
|
||||
text_eval/text_eval_wrapper.cpp
|
||||
text_eval/text_eval.lemon
|
||||
)
|
||||
|
||||
# auto-generate stroke_params_lexer.h and stroke_params_keywords.cpp
|
||||
# Called twice one for common and one for gal, to ensure the files are created
|
||||
# on all devel tools ( Linux and msys2 )
|
||||
|
@ -262,7 +262,7 @@ ADVANCED_CFG::ADVANCED_CFG()
|
||||
m_CompactSave = false;
|
||||
m_UpdateUIEventInterval = 0;
|
||||
m_ShowRepairSchematic = false;
|
||||
m_EnablePcbDesignBlocks = false;
|
||||
m_EnablePcbDesignBlocks = true;
|
||||
m_EnableGenerators = false;
|
||||
m_EnableLibWithText = false;
|
||||
m_EnableLibDir = false;
|
||||
|
@ -397,6 +397,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_24.png" ), 24, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_24.png" ), 24, wxT( "light" ) );
|
||||
@ -836,6 +837,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_dark_24.png" ), 24, wxT( "dark" ) );
|
||||
@ -1275,6 +1277,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_16.png" ), 16, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_16.png" ), 16, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_16.png" ), 16, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_16.png" ), 16, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_16.png" ), 16, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_16.png" ), 16, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_16.png" ), 16, wxT( "light" ) );
|
||||
@ -1714,6 +1717,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_dark_16.png" ), 16, wxT( "dark" ) );
|
||||
@ -2153,6 +2157,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_32.png" ), 32, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_32.png" ), 32, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_32.png" ), 32, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_32.png" ), 32, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_32.png" ), 32, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_32.png" ), 32, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_32.png" ), 32, wxT( "light" ) );
|
||||
@ -2592,6 +2597,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_dark_32.png" ), 32, wxT( "dark" ) );
|
||||
@ -3031,6 +3037,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_48.png" ), 48, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_48.png" ), 48, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_48.png" ), 48, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_48.png" ), 48, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_48.png" ), 48, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_48.png" ), 48, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_48.png" ), 48, wxT( "light" ) );
|
||||
@ -3470,6 +3477,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_dark_48.png" ), 48, wxT( "dark" ) );
|
||||
@ -3909,6 +3917,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_64.png" ), 64, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_64.png" ), 64, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_64.png" ), 64, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_64.png" ), 64, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_64.png" ), 64, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_64.png" ), 64, wxT( "light" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_64.png" ), 64, wxT( "light" ) );
|
||||
@ -4348,6 +4357,7 @@ void BuildBitmapInfo( std::unordered_map<BITMAPS, std::vector<BITMAP_INFO>>& aBi
|
||||
aBitmapInfoCache[BITMAPS::add_line].emplace_back( BITMAPS::add_line, wxT( "add_line_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_orthogonal_dimension].emplace_back( BITMAPS::add_orthogonal_dimension, wxT( "add_orthogonal_dimension_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_pcb_target].emplace_back( BITMAPS::add_pcb_target, wxT( "add_pcb_target_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_point].emplace_back( BITMAPS::add_point, wxT( "add_point_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_power].emplace_back( BITMAPS::add_power, wxT( "add_power_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_radial_dimension].emplace_back( BITMAPS::add_radial_dimension, wxT( "add_radial_dimension_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
aBitmapInfoCache[BITMAPS::add_rectangle].emplace_back( BITMAPS::add_rectangle, wxT( "add_rectangle_dark_64.png" ), 64, wxT( "dark" ) );
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "clipboard.h"
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/dataobj.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/string.h>
|
||||
@ -90,10 +91,10 @@ std::unique_ptr<wxImage> GetImageFromClipboard()
|
||||
{
|
||||
if( wxTheClipboard->IsSupported( wxDF_BITMAP ) )
|
||||
{
|
||||
wxImageDataObject data;
|
||||
wxBitmapDataObject data;
|
||||
if( wxTheClipboard->GetData( data ) )
|
||||
{
|
||||
image = std::make_unique<wxImage>( data.GetImage() );
|
||||
image = std::make_unique<wxImage>( data.GetBitmap().ConvertToImage() );
|
||||
}
|
||||
}
|
||||
else if( wxTheClipboard->IsSupported( wxDF_FILENAME ) )
|
||||
|
@ -190,7 +190,7 @@ void DESIGN_BLOCK_LIST_IMPL::loadDesignBlocks()
|
||||
};
|
||||
|
||||
for( size_t ii = 0; ii < num_elements; ++ii )
|
||||
returns[ii] = tp.submit( db_thread );
|
||||
returns[ii] = tp.submit_task( db_thread );
|
||||
|
||||
for( const std::future<size_t>& ret : returns )
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf)
|
||||
// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
|
||||
// http://www.wxformbuilder.org/
|
||||
//
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
@ -26,25 +26,6 @@ WX_TEXT_ENTRY_DIALOG_BASE::WX_TEXT_ENTRY_DIALOG_BASE( wxWindow* parent, wxWindow
|
||||
|
||||
m_ContentSizer->Add( m_textCtrl, 0, wxEXPAND|wxALL, 5 );
|
||||
|
||||
wxBoxSizer* bSizer3;
|
||||
bSizer3 = new wxBoxSizer( wxHORIZONTAL );
|
||||
|
||||
m_choiceLabel = new wxStaticText( this, wxID_ANY, _("MyLabel"), wxDefaultPosition, wxDefaultSize, 0 );
|
||||
m_choiceLabel->Wrap( -1 );
|
||||
m_choiceLabel->Hide();
|
||||
|
||||
bSizer3->Add( m_choiceLabel, 0, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, 5 );
|
||||
|
||||
wxArrayString m_choiceChoices;
|
||||
m_choice = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_choiceChoices, 0 );
|
||||
m_choice->SetSelection( 0 );
|
||||
m_choice->Hide();
|
||||
|
||||
bSizer3->Add( m_choice, 3, wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT, 5 );
|
||||
|
||||
|
||||
m_ContentSizer->Add( bSizer3, 1, wxEXPAND, 5 );
|
||||
|
||||
|
||||
m_mainSizer->Add( m_ContentSizer, 1, wxALL|wxEXPAND, 5 );
|
||||
|
||||
|
@ -1,34 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<wxFormBuilder_Project>
|
||||
<FileVersion major="1" minor="17"/>
|
||||
<FileVersion major="1" minor="18"/>
|
||||
<object class="Project" expanded="true">
|
||||
<property name="class_decoration"></property>
|
||||
<property name="code_generation">C++</property>
|
||||
<property name="disconnect_events">1</property>
|
||||
<property name="disconnect_mode">source_name</property>
|
||||
<property name="disconnect_php_events">0</property>
|
||||
<property name="disconnect_python_events">0</property>
|
||||
<property name="cpp_class_decoration"></property>
|
||||
<property name="cpp_disconnect_events">1</property>
|
||||
<property name="cpp_event_generation">connect</property>
|
||||
<property name="cpp_help_provider">none</property>
|
||||
<property name="cpp_namespace"></property>
|
||||
<property name="cpp_precompiled_header"></property>
|
||||
<property name="cpp_use_array_enum">0</property>
|
||||
<property name="cpp_use_enum">0</property>
|
||||
<property name="embedded_files_path">res</property>
|
||||
<property name="encoding">UTF-8</property>
|
||||
<property name="event_generation">connect</property>
|
||||
<property name="file">dialog_text_entry_base</property>
|
||||
<property name="first_id">1000</property>
|
||||
<property name="help_provider">none</property>
|
||||
<property name="image_path_wrapper_function_name"></property>
|
||||
<property name="indent_with_spaces"></property>
|
||||
<property name="internationalize">1</property>
|
||||
<property name="lua_skip_events">1</property>
|
||||
<property name="lua_ui_table">UI</property>
|
||||
<property name="name">dialog_text_entry_base</property>
|
||||
<property name="namespace"></property>
|
||||
<property name="path">.</property>
|
||||
<property name="precompiled_header"></property>
|
||||
<property name="php_disconnect_events">0</property>
|
||||
<property name="php_disconnect_mode">source_name</property>
|
||||
<property name="php_skip_events">1</property>
|
||||
<property name="python_disconnect_events">0</property>
|
||||
<property name="python_disconnect_mode">source_name</property>
|
||||
<property name="python_image_path_wrapper_function_name"></property>
|
||||
<property name="python_indent_with_spaces"></property>
|
||||
<property name="python_skip_events">1</property>
|
||||
<property name="relative_path">1</property>
|
||||
<property name="skip_lua_events">1</property>
|
||||
<property name="skip_php_events">1</property>
|
||||
<property name="skip_python_events">1</property>
|
||||
<property name="ui_table">UI</property>
|
||||
<property name="use_array_enum">0</property>
|
||||
<property name="use_enum">0</property>
|
||||
<property name="use_microsoft_bom">0</property>
|
||||
<property name="use_native_eol">0</property>
|
||||
<object class="Dialog" expanded="true">
|
||||
<property name="aui_managed">0</property>
|
||||
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
|
||||
@ -80,10 +82,10 @@
|
||||
<property name="LeftDockable">1</property>
|
||||
<property name="RightDockable">1</property>
|
||||
<property name="TopDockable">1</property>
|
||||
<property name="aui_layer"></property>
|
||||
<property name="aui_layer">0</property>
|
||||
<property name="aui_name"></property>
|
||||
<property name="aui_position"></property>
|
||||
<property name="aui_row"></property>
|
||||
<property name="aui_position">0</property>
|
||||
<property name="aui_row">0</property>
|
||||
<property name="best_size"></property>
|
||||
<property name="bg"></property>
|
||||
<property name="caption"></property>
|
||||
@ -142,10 +144,10 @@
|
||||
<property name="LeftDockable">1</property>
|
||||
<property name="RightDockable">1</property>
|
||||
<property name="TopDockable">1</property>
|
||||
<property name="aui_layer"></property>
|
||||
<property name="aui_layer">0</property>
|
||||
<property name="aui_name"></property>
|
||||
<property name="aui_position"></property>
|
||||
<property name="aui_row"></property>
|
||||
<property name="aui_position">0</property>
|
||||
<property name="aui_row">0</property>
|
||||
<property name="best_size"></property>
|
||||
<property name="bg"></property>
|
||||
<property name="caption"></property>
|
||||
@ -169,7 +171,7 @@
|
||||
<property name="max_size"></property>
|
||||
<property name="maximize_button">0</property>
|
||||
<property name="maximum_size"></property>
|
||||
<property name="maxlength"></property>
|
||||
<property name="maxlength">0</property>
|
||||
<property name="min_size"></property>
|
||||
<property name="minimize_button">0</property>
|
||||
<property name="minimum_size">300,-1</property>
|
||||
@ -198,144 +200,6 @@
|
||||
<property name="window_style"></property>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem" expanded="true">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxEXPAND</property>
|
||||
<property name="proportion">1</property>
|
||||
<object class="wxBoxSizer" expanded="true">
|
||||
<property name="minimum_size"></property>
|
||||
<property name="name">bSizer3</property>
|
||||
<property name="orient">wxHORIZONTAL</property>
|
||||
<property name="permission">none</property>
|
||||
<object class="sizeritem" expanded="true">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT</property>
|
||||
<property name="proportion">0</property>
|
||||
<object class="wxStaticText" expanded="true">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
<property name="RightDockable">1</property>
|
||||
<property name="TopDockable">1</property>
|
||||
<property name="aui_layer"></property>
|
||||
<property name="aui_name"></property>
|
||||
<property name="aui_position"></property>
|
||||
<property name="aui_row"></property>
|
||||
<property name="best_size"></property>
|
||||
<property name="bg"></property>
|
||||
<property name="caption"></property>
|
||||
<property name="caption_visible">1</property>
|
||||
<property name="center_pane">0</property>
|
||||
<property name="close_button">1</property>
|
||||
<property name="context_help"></property>
|
||||
<property name="context_menu">1</property>
|
||||
<property name="default_pane">0</property>
|
||||
<property name="dock">Dock</property>
|
||||
<property name="dock_fixed">0</property>
|
||||
<property name="docking">Left</property>
|
||||
<property name="drag_accept_files">0</property>
|
||||
<property name="enabled">1</property>
|
||||
<property name="fg"></property>
|
||||
<property name="floatable">1</property>
|
||||
<property name="font"></property>
|
||||
<property name="gripper">0</property>
|
||||
<property name="hidden">1</property>
|
||||
<property name="id">wxID_ANY</property>
|
||||
<property name="label">MyLabel</property>
|
||||
<property name="markup">0</property>
|
||||
<property name="max_size"></property>
|
||||
<property name="maximize_button">0</property>
|
||||
<property name="maximum_size"></property>
|
||||
<property name="min_size"></property>
|
||||
<property name="minimize_button">0</property>
|
||||
<property name="minimum_size"></property>
|
||||
<property name="moveable">1</property>
|
||||
<property name="name">m_choiceLabel</property>
|
||||
<property name="pane_border">1</property>
|
||||
<property name="pane_position"></property>
|
||||
<property name="pane_size"></property>
|
||||
<property name="permission">protected</property>
|
||||
<property name="pin_button">1</property>
|
||||
<property name="pos"></property>
|
||||
<property name="resize">Resizable</property>
|
||||
<property name="show">1</property>
|
||||
<property name="size"></property>
|
||||
<property name="style"></property>
|
||||
<property name="subclass">; ; forward_declare</property>
|
||||
<property name="toolbar_pane">0</property>
|
||||
<property name="tooltip"></property>
|
||||
<property name="window_extra_style"></property>
|
||||
<property name="window_name"></property>
|
||||
<property name="window_style"></property>
|
||||
<property name="wrap">-1</property>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem" expanded="true">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT</property>
|
||||
<property name="proportion">3</property>
|
||||
<object class="wxChoice" expanded="true">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
<property name="RightDockable">1</property>
|
||||
<property name="TopDockable">1</property>
|
||||
<property name="aui_layer"></property>
|
||||
<property name="aui_name"></property>
|
||||
<property name="aui_position"></property>
|
||||
<property name="aui_row"></property>
|
||||
<property name="best_size"></property>
|
||||
<property name="bg"></property>
|
||||
<property name="caption"></property>
|
||||
<property name="caption_visible">1</property>
|
||||
<property name="center_pane">0</property>
|
||||
<property name="choices"></property>
|
||||
<property name="close_button">1</property>
|
||||
<property name="context_help"></property>
|
||||
<property name="context_menu">1</property>
|
||||
<property name="default_pane">0</property>
|
||||
<property name="dock">Dock</property>
|
||||
<property name="dock_fixed">0</property>
|
||||
<property name="docking">Left</property>
|
||||
<property name="drag_accept_files">0</property>
|
||||
<property name="enabled">1</property>
|
||||
<property name="fg"></property>
|
||||
<property name="floatable">1</property>
|
||||
<property name="font"></property>
|
||||
<property name="gripper">0</property>
|
||||
<property name="hidden">1</property>
|
||||
<property name="id">wxID_ANY</property>
|
||||
<property name="max_size"></property>
|
||||
<property name="maximize_button">0</property>
|
||||
<property name="maximum_size"></property>
|
||||
<property name="min_size"></property>
|
||||
<property name="minimize_button">0</property>
|
||||
<property name="minimum_size"></property>
|
||||
<property name="moveable">1</property>
|
||||
<property name="name">m_choice</property>
|
||||
<property name="pane_border">1</property>
|
||||
<property name="pane_position"></property>
|
||||
<property name="pane_size"></property>
|
||||
<property name="permission">protected</property>
|
||||
<property name="pin_button">1</property>
|
||||
<property name="pos"></property>
|
||||
<property name="resize">Resizable</property>
|
||||
<property name="selection">0</property>
|
||||
<property name="show">1</property>
|
||||
<property name="size"></property>
|
||||
<property name="style"></property>
|
||||
<property name="subclass">; ; forward_declare</property>
|
||||
<property name="toolbar_pane">0</property>
|
||||
<property name="tooltip"></property>
|
||||
<property name="validator_data_type"></property>
|
||||
<property name="validator_style">wxFILTER_NONE</property>
|
||||
<property name="validator_type">wxDefaultValidator</property>
|
||||
<property name="validator_variable"></property>
|
||||
<property name="window_extra_style"></property>
|
||||
<property name="window_name"></property>
|
||||
<property name="window_style"></property>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem" expanded="false">
|
||||
|
@ -1,5 +1,5 @@
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// C++ code generated with wxFormBuilder (version 4.0.0-0-g0efcecf)
|
||||
// C++ code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
|
||||
// http://www.wxformbuilder.org/
|
||||
//
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
@ -18,14 +18,12 @@
|
||||
#include <wx/colour.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/dialog.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class WX_TEXT_ENTRY_DIALOG_BASE
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -37,8 +35,6 @@ class WX_TEXT_ENTRY_DIALOG_BASE : public DIALOG_SHIM
|
||||
wxBoxSizer* m_mainSizer;
|
||||
wxStaticText* m_label;
|
||||
wxTextCtrl* m_textCtrl;
|
||||
wxStaticText* m_choiceLabel;
|
||||
wxChoice* m_choice;
|
||||
wxStdDialogButtonSizer* m_sdbSizer1;
|
||||
wxButton* m_sdbSizer1OK;
|
||||
wxButton* m_sdbSizer1Cancel;
|
||||
|
@ -167,9 +167,7 @@ void PANEL_EMBEDDED_FILES::resizeGrid()
|
||||
bool PANEL_EMBEDDED_FILES::TransferDataToWindow()
|
||||
{
|
||||
m_files_grid->ClearGrid();
|
||||
|
||||
if( m_files_grid->GetNumberRows() > 0 )
|
||||
m_files_grid->DeleteRows( 0, m_files_grid->GetNumberRows() );
|
||||
m_files_grid->ClearRows();
|
||||
|
||||
int ii = 0;
|
||||
|
||||
|
@ -87,6 +87,10 @@ EDA_DRAW_PANEL_GAL::EDA_DRAW_PANEL_GAL( wxWindow* aParentWindow, wxWindowID aWin
|
||||
m_stealsFocus( true ),
|
||||
m_statusPopup( nullptr )
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// need to fix broken cairo rendering on Windows with wx 3.3
|
||||
SetDoubleBuffered( false );
|
||||
#endif
|
||||
m_PaintEventCounter = std::make_unique<PROF_COUNTER>( "Draw panel paint events" );
|
||||
|
||||
if( Pgm().GetCommonSettings()->m_Appearance.show_scrollbars )
|
||||
|
@ -60,6 +60,7 @@ track_segment_length
|
||||
version
|
||||
via
|
||||
via_count
|
||||
via_dangling
|
||||
via_diameter
|
||||
warning
|
||||
within_diff_pairs
|
||||
|
@ -1059,9 +1059,13 @@ std::vector<wxWindow*> EDA_DRAW_FRAME::findDialogs()
|
||||
}
|
||||
|
||||
|
||||
void EDA_DRAW_FRAME::FocusOnLocation( const VECTOR2I& aPos )
|
||||
void EDA_DRAW_FRAME::FocusOnLocation( const VECTOR2I& aPos, bool aAllowScroll )
|
||||
{
|
||||
bool centerView = false;
|
||||
std::vector<BOX2D> dialogScreenRects;
|
||||
|
||||
if( aAllowScroll )
|
||||
{
|
||||
BOX2D r = GetCanvas()->GetView()->GetViewport();
|
||||
|
||||
// Center if we're off the current view, or within 10% of its edge
|
||||
@ -1070,8 +1074,6 @@ void EDA_DRAW_FRAME::FocusOnLocation( const VECTOR2I& aPos )
|
||||
if( !r.Contains( aPos ) )
|
||||
centerView = true;
|
||||
|
||||
std::vector<BOX2D> dialogScreenRects;
|
||||
|
||||
for( wxWindow* dialog : findDialogs() )
|
||||
{
|
||||
dialogScreenRects.emplace_back( ToVECTOR2D( GetCanvas()->ScreenToClient( dialog->GetScreenPosition() ) ),
|
||||
@ -1086,6 +1088,7 @@ void EDA_DRAW_FRAME::FocusOnLocation( const VECTOR2I& aPos )
|
||||
if( rect.Contains( GetCanvas()->GetView()->ToScreen( aPos ) ) )
|
||||
centerView = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( centerView )
|
||||
{
|
||||
|
@ -433,6 +433,7 @@ static struct EDA_ITEM_DESC
|
||||
.Map( PCB_DIM_RADIAL_T, _HKI( "Dimension" ) )
|
||||
.Map( PCB_DIM_LEADER_T, _HKI( "Leader" ) )
|
||||
.Map( PCB_TARGET_T, _HKI( "Target" ) )
|
||||
.Map( PCB_POINT_T, _HKI( "Point" ) )
|
||||
.Map( PCB_ZONE_T, _HKI( "Zone" ) )
|
||||
.Map( PCB_ITEM_LIST_T, _HKI( "ItemList" ) )
|
||||
.Map( PCB_NETINFO_T, _HKI( "NetInfo" ) )
|
||||
|
@ -634,11 +634,11 @@ void EDA_SHAPE::UpdateHatching() const
|
||||
return;
|
||||
|
||||
case SHAPE_T::RECTANGLE:
|
||||
shapeBuffer.NewOutline();
|
||||
|
||||
for( const VECTOR2I& pt : GetRectCorners() )
|
||||
shapeBuffer.Append( pt );
|
||||
|
||||
{
|
||||
ROUNDRECT rr( SHAPE_RECT( getPosition(), GetRectangleWidth(), GetRectangleHeight() ),
|
||||
GetCornerRadius() );
|
||||
rr.TransformToPolygon( shapeBuffer );
|
||||
}
|
||||
break;
|
||||
|
||||
case SHAPE_T::CIRCLE:
|
||||
@ -999,10 +999,9 @@ void EDA_SHAPE::SetCenter( const VECTOR2I& aCenter )
|
||||
|
||||
VECTOR2I EDA_SHAPE::GetArcMid() const
|
||||
{
|
||||
// If none of the input data have changed since we loaded the arc,
|
||||
// keep the original mid point data to minimize churn
|
||||
if( m_arcMidData.start == m_start && m_arcMidData.end == m_end
|
||||
&& m_arcMidData.center == m_arcCenter )
|
||||
// If none of the input data have changed since we loaded the arc, keep the original mid point data
|
||||
// to minimize churn
|
||||
if( m_arcMidData.start == m_start && m_arcMidData.end == m_end && m_arcMidData.center == m_arcCenter )
|
||||
return m_arcMidData.mid;
|
||||
|
||||
VECTOR2I mid = m_start;
|
||||
@ -1204,21 +1203,17 @@ void EDA_SHAPE::ShapeGetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PA
|
||||
break;
|
||||
|
||||
case SHAPE_T::RECTANGLE:
|
||||
aList.emplace_back( _( "Width" ),
|
||||
aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
|
||||
|
||||
aList.emplace_back( _( "Height" ),
|
||||
aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
|
||||
aList.emplace_back( _( "Width" ), aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
|
||||
aList.emplace_back( _( "Height" ), aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
|
||||
break;
|
||||
|
||||
case SHAPE_T::SEGMENT:
|
||||
{
|
||||
aList.emplace_back( _( "Length" ),
|
||||
aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
|
||||
aList.emplace_back( _( "Length" ), aFrame->MessageTextFromValue( GetStart().Distance( GetEnd() ) ));
|
||||
|
||||
// angle counter-clockwise from 3'o-clock
|
||||
EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ),
|
||||
(double)( GetEnd().x - GetStart().x ) ), RADIANS_T );
|
||||
EDA_ANGLE angle( atan2( (double)( GetStart().y - GetEnd().y ), (double)( GetEnd().x - GetStart().x ) ),
|
||||
RADIANS_T );
|
||||
aList.emplace_back( _( "Angle" ), EDA_UNIT_UTILS::UI::MessageTextFromValue( angle ) );
|
||||
break;
|
||||
}
|
||||
@ -1271,6 +1266,7 @@ const BOX2I EDA_SHAPE::getBoundingBox() const
|
||||
// using the bounding box of the curve (not control!) points.
|
||||
for( const VECTOR2I& pt : m_bezierPoints )
|
||||
bbox.Merge( pt );
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1386,6 +1382,15 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
|
||||
|
||||
return poly.Collide( aPosition, maxdist );
|
||||
}
|
||||
else if( m_cornerRadius > 0 )
|
||||
{
|
||||
ROUNDRECT rr( SHAPE_RECT( GetStart(), GetRectangleWidth(), GetRectangleHeight() ), m_cornerRadius );
|
||||
SHAPE_POLY_SET poly;
|
||||
rr.TransformToPolygon( poly );
|
||||
|
||||
if( poly.CollideEdge( aPosition, nullptr, maxdist ) )
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<VECTOR2I> pts = GetRectCorners();
|
||||
@ -1397,12 +1402,12 @@ bool EDA_SHAPE::hitTest( const VECTOR2I& aPosition, int aAccuracy ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if( IsHatchedFill() && GetHatching().Collide( aPosition, maxdist ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
case SHAPE_T::POLY:
|
||||
if( IsFilledForHitTesting() )
|
||||
@ -1445,6 +1450,40 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
|
||||
BOX2I bbox = getBoundingBox();
|
||||
|
||||
auto checkOutline =
|
||||
[&]( const SHAPE_LINE_CHAIN& outline )
|
||||
{
|
||||
int count = (int) outline.GetPointCount();
|
||||
|
||||
for( int ii = 0; ii < count; ii++ )
|
||||
{
|
||||
VECTOR2I vertex = outline.GetPoint( ii );
|
||||
|
||||
// Test if the point is within aRect
|
||||
if( arect.Contains( vertex ) )
|
||||
return true;
|
||||
|
||||
if( ii + 1 < count )
|
||||
{
|
||||
VECTOR2I vertexNext = outline.GetPoint( ii + 1 );
|
||||
|
||||
// Test if this edge intersects aRect
|
||||
if( arect.Intersects( vertex, vertexNext ) )
|
||||
return true;
|
||||
}
|
||||
else if( outline.IsClosed() )
|
||||
{
|
||||
VECTOR2I vertexNext = outline.GetPoint( 0 );
|
||||
|
||||
// Test if this edge intersects aRect
|
||||
if( arect.Intersects( vertex, vertexNext ) )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
switch( m_shape )
|
||||
{
|
||||
case SHAPE_T::CIRCLE:
|
||||
@ -1491,6 +1530,17 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
{
|
||||
return arect.Contains( bbox );
|
||||
}
|
||||
else if( m_cornerRadius > 0 )
|
||||
{
|
||||
ROUNDRECT rr( SHAPE_RECT( GetStart(), GetRectangleWidth(), GetRectangleHeight() ), m_cornerRadius );
|
||||
SHAPE_POLY_SET poly;
|
||||
rr.TransformToPolygon( poly );
|
||||
|
||||
// Account for the width of the line
|
||||
arect.Inflate( GetWidth() / 2 );
|
||||
|
||||
return checkOutline( poly.Outline( 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<VECTOR2I> pts = GetRectCorners();
|
||||
@ -1532,34 +1582,8 @@ bool EDA_SHAPE::hitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) co
|
||||
|
||||
for( int ii = 0; ii < m_poly.OutlineCount(); ++ii )
|
||||
{
|
||||
const SHAPE_LINE_CHAIN& poly = m_poly.Outline( ii );
|
||||
int count = poly.GetPointCount();
|
||||
|
||||
for( int jj = 0; jj < count; jj++ )
|
||||
{
|
||||
VECTOR2I vertex = poly.GetPoint( jj );
|
||||
|
||||
// Test if the point is within aRect
|
||||
if( arect.Contains( vertex ) )
|
||||
if( checkOutline( m_poly.Outline( ii ) ) )
|
||||
return true;
|
||||
|
||||
if( jj + 1 < count )
|
||||
{
|
||||
VECTOR2I vertexNext = poly.GetPoint( jj + 1 );
|
||||
|
||||
// Test if this edge intersects aRect
|
||||
if( arect.Intersects( vertex, vertexNext ) )
|
||||
return true;
|
||||
}
|
||||
else if( poly.IsClosed() )
|
||||
{
|
||||
VECTOR2I vertexNext = poly.GetPoint( 0 );
|
||||
|
||||
// Test if this edge intersects aRect
|
||||
if( arect.Intersects( vertex, vertexNext ) )
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -1832,8 +1856,7 @@ std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh
|
||||
{
|
||||
if( m_cornerRadius > 0 )
|
||||
{
|
||||
ROUNDRECT rr( SHAPE_RECT( GetStart(), GetRectangleWidth(), GetRectangleHeight() ),
|
||||
m_cornerRadius );
|
||||
ROUNDRECT rr( SHAPE_RECT( GetStart(), GetRectangleWidth(), GetRectangleHeight() ), m_cornerRadius );
|
||||
SHAPE_POLY_SET poly;
|
||||
rr.TransformToPolygon( poly );
|
||||
SHAPE_LINE_CHAIN outline = poly.Outline( 0 );
|
||||
@ -1844,10 +1867,13 @@ std::vector<SHAPE*> EDA_SHAPE::makeEffectiveShapes( bool aEdgeOnly, bool aLineCh
|
||||
if( width > 0 || !solidFill )
|
||||
{
|
||||
for( int i = 0; i < outline.PointCount() - 1; ++i )
|
||||
effectiveShapes.emplace_back(
|
||||
new SHAPE_SEGMENT( outline.CPoint( i ), outline.CPoint( i + 1 ), width ) );
|
||||
{
|
||||
effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( i ), outline.CPoint( i + 1 ),
|
||||
width ) );
|
||||
}
|
||||
|
||||
effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( outline.PointCount() - 1 ), outline.CPoint( 0 ), width ) );
|
||||
effectiveShapes.emplace_back( new SHAPE_SEGMENT( outline.CPoint( outline.PointCount() - 1 ),
|
||||
outline.CPoint( 0 ), width ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2024,7 +2050,7 @@ bool EDA_SHAPE::continueEdit( const VECTOR2I& aPosition )
|
||||
SHAPE_LINE_CHAIN& poly = m_poly.Outline( 0 );
|
||||
|
||||
// do not add zero-length segments
|
||||
if( poly.CPoint( poly.GetPointCount() - 2 ) != poly.CLastPoint() )
|
||||
if( poly.CPoint( (int) poly.GetPointCount() - 2 ) != poly.CLastPoint() )
|
||||
poly.Append( aPosition, true );
|
||||
}
|
||||
return true;
|
||||
@ -2058,12 +2084,19 @@ void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
|
||||
SetBezierC1( aPosition );
|
||||
SetBezierC2( aPosition );
|
||||
break;
|
||||
|
||||
case 1:
|
||||
SetBezierC2( aPosition );
|
||||
SetEnd( aPosition );
|
||||
break;
|
||||
case 2: SetBezierC1( aPosition ); break;
|
||||
case 3: SetBezierC2( aPosition ); break;
|
||||
|
||||
case 2:
|
||||
SetBezierC1( aPosition );
|
||||
break;
|
||||
|
||||
case 3:
|
||||
SetBezierC2( aPosition );
|
||||
break;
|
||||
}
|
||||
|
||||
RebuildBezierToSegmentsPointsList( getMaxError() );
|
||||
@ -2114,16 +2147,16 @@ void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
|
||||
|
||||
if( ratio != 0 )
|
||||
radius = std::max( sqrt( sq( radius ) * ratio ), sqrt( chordAfter ) / 2 );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 4:
|
||||
{
|
||||
double radialA = m_start.Distance( aPosition );
|
||||
double radialB = m_end.Distance( aPosition );
|
||||
radius = ( radialA + radialB ) / 2.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 5:
|
||||
SetArcGeometry( GetStart(), aPosition, GetEnd() );
|
||||
@ -2177,8 +2210,9 @@ void EDA_SHAPE::calcEdit( const VECTOR2I& aPosition )
|
||||
m_arcCenter = c1.Distance( aPosition ) < c2.Distance( aPosition ) ? c1 : c2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SHAPE_T::POLY:
|
||||
m_poly.Outline( 0 ).SetPoint( m_poly.Outline( 0 ).GetPointCount() - 1, aPosition );
|
||||
@ -2218,8 +2252,9 @@ void EDA_SHAPE::endEdit( bool aClosed )
|
||||
poly.Remove( poly.GetPointCount() - 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
UNIMPLEMENTED_FOR( SHAPE_T_asString() );
|
||||
@ -2299,8 +2334,7 @@ int EDA_SHAPE::Compare( const EDA_SHAPE* aOther ) const
|
||||
|
||||
|
||||
void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance, int aError,
|
||||
ERROR_LOC aErrorLoc, bool ignoreLineWidth,
|
||||
bool includeFill ) const
|
||||
ERROR_LOC aErrorLoc, bool ignoreLineWidth, bool includeFill ) const
|
||||
{
|
||||
bool solidFill = IsSolidFill() || ( IsHatchedFill() && !includeFill ) || IsProxyItem();
|
||||
int width = ignoreLineWidth ? 0 : GetWidth();
|
||||
@ -2329,16 +2363,25 @@ void EDA_SHAPE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, int aClearance
|
||||
VECTOR2I size( GetRectangleWidth(), GetRectangleHeight() );
|
||||
VECTOR2I position = GetStart() + size / 2; // Center position
|
||||
|
||||
TransformRoundChamferedRectToPolygon( aBuffer, position, size, ANGLE_0,
|
||||
GetCornerRadius(), 0.0, 0,
|
||||
solidFill ? 0 : width / 2, aError, aErrorLoc );
|
||||
|
||||
if( solidFill && width > 0 )
|
||||
if( solidFill )
|
||||
{
|
||||
// Add outline for filled shapes with border
|
||||
TransformRoundChamferedRectToPolygon( aBuffer, position, size, ANGLE_0,
|
||||
GetCornerRadius(), 0.0, 0,
|
||||
width / 2, aError, aErrorLoc );
|
||||
TransformRoundChamferedRectToPolygon( aBuffer, position, size, ANGLE_0, GetCornerRadius(),
|
||||
0.0, 0, width / 2, aError, aErrorLoc );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Export outline as a set of thick segments:
|
||||
SHAPE_POLY_SET poly;
|
||||
TransformRoundChamferedRectToPolygon( poly, position, size, ANGLE_0, GetCornerRadius(),
|
||||
0.0, 0, 0, aError, aErrorLoc );
|
||||
SHAPE_LINE_CHAIN& outline = poly.Outline( 0 );
|
||||
outline.SetClosed( true );
|
||||
|
||||
for( int ii = 0; ii < outline.PointCount(); ii++ )
|
||||
{
|
||||
TransformOvalToPolygon( aBuffer, outline.CPoint( ii ), outline.CPoint( ii+1 ), width,
|
||||
aError, aErrorLoc );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -2734,16 +2777,16 @@ static struct EDA_SHAPE_DESC
|
||||
|
||||
int radius = aValue.As<int>();
|
||||
|
||||
EDA_SHAPE* shape = dynamic_cast<EDA_SHAPE*>( aItem );
|
||||
EDA_SHAPE* prop_shape = dynamic_cast<EDA_SHAPE*>( aItem );
|
||||
|
||||
if( !shape )
|
||||
if( !prop_shape )
|
||||
{
|
||||
wxLogDebug( wxT( "Corner Radius Validator: Not an EDA_SHAPE" ) );
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
int maxRadius = std::min( shape->GetRectangleWidth(),
|
||||
shape->GetRectangleHeight() ) / 2;
|
||||
int maxRadius = std::min( prop_shape->GetRectangleWidth(),
|
||||
prop_shape->GetRectangleHeight() ) / 2;
|
||||
|
||||
if( radius > maxRadius )
|
||||
return std::make_unique<VALIDATION_ERROR_TOO_LARGE<int>>( radius, maxRadius );
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include <font/glyph.h>
|
||||
#include <gr_text.h>
|
||||
#include <string_utils.h> // for UnescapeString
|
||||
#include <text_eval/text_eval_wrapper.h>
|
||||
#include <math/util.h> // for KiROUND
|
||||
#include <math/vector2d.h>
|
||||
#include <core/kicad_algo.h>
|
||||
@ -638,6 +639,14 @@ void EDA_TEXT::cacheShownText()
|
||||
}
|
||||
|
||||
|
||||
wxString EDA_TEXT::EvaluateText( const wxString& aText ) const
|
||||
{
|
||||
static EXPRESSION_EVALUATOR evaluator;
|
||||
|
||||
return evaluator.Evaluate( aText );
|
||||
}
|
||||
|
||||
|
||||
KIFONT::FONT* EDA_TEXT::GetDrawFont( const RENDER_SETTINGS* aSettings ) const
|
||||
{
|
||||
KIFONT::FONT* font = GetFont();
|
||||
|
@ -1364,6 +1364,11 @@ CAIRO_GAL::CAIRO_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions, wxWindow* aParent,
|
||||
m_currentTarget = TARGET_NONCACHED;
|
||||
SetTarget( TARGET_NONCACHED );
|
||||
|
||||
#ifdef _WIN32
|
||||
// need to fix broken cairo rendering on Windows with wx 3.3
|
||||
SetDoubleBuffered( false );
|
||||
#endif
|
||||
|
||||
m_bitmapBuffer = nullptr;
|
||||
m_wxOutput = nullptr;
|
||||
|
||||
|
@ -22,16 +22,13 @@
|
||||
*/
|
||||
|
||||
#include "git_add_to_index_handler.h"
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#include <wx/string.h>
|
||||
#include <wx/log.h>
|
||||
|
||||
GIT_ADD_TO_INDEX_HANDLER::GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository )
|
||||
GIT_ADD_TO_INDEX_HANDLER::GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository ) :
|
||||
KIGIT_COMMON( aRepository )
|
||||
{
|
||||
m_repository = aRepository;
|
||||
m_filesToAdd.clear();
|
||||
}
|
||||
|
||||
@ -43,66 +40,11 @@ GIT_ADD_TO_INDEX_HANDLER::~GIT_ADD_TO_INDEX_HANDLER()
|
||||
|
||||
bool GIT_ADD_TO_INDEX_HANDLER::AddToIndex( const wxString& aFilePath )
|
||||
{
|
||||
// Test if file is currently in the index
|
||||
|
||||
git_index* index = nullptr;
|
||||
size_t at_pos = 0;
|
||||
|
||||
if( git_repository_index( &index, m_repository ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to get repository index" );
|
||||
return false;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) == GIT_OK )
|
||||
{
|
||||
wxLogError( "%s already in index", aFilePath );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add file to index if not already there
|
||||
m_filesToAdd.push_back( aFilePath );
|
||||
|
||||
return true;
|
||||
return GetGitBackend()->AddToIndex( this, aFilePath );
|
||||
}
|
||||
|
||||
|
||||
bool GIT_ADD_TO_INDEX_HANDLER::PerformAddToIndex()
|
||||
{
|
||||
git_index* index = nullptr;
|
||||
|
||||
m_filesFailedToAdd.clear();
|
||||
|
||||
if( git_repository_index( &index, m_repository ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to get repository index" );
|
||||
std::copy( m_filesToAdd.begin(), m_filesToAdd.end(), std::back_inserter( m_filesFailedToAdd ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
for( auto& file : m_filesToAdd )
|
||||
{
|
||||
if( git_index_add_bypath( index, file.ToUTF8().data() ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to add %s to index", file );
|
||||
m_filesFailedToAdd.push_back( file );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if( git_index_write( index ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to write index" );
|
||||
m_filesFailedToAdd.clear();
|
||||
std::copy( m_filesToAdd.begin(), m_filesToAdd.end(),
|
||||
std::back_inserter( m_filesFailedToAdd ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return GetGitBackend()->PerformAddToIndex( this );
|
||||
}
|
||||
|
@ -24,12 +24,13 @@
|
||||
#ifndef GIT_ADD_TO_INDEX_HANDLER_H_
|
||||
#define GIT_ADD_TO_INDEX_HANDLER_H_
|
||||
|
||||
#include <git2.h>
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
class wxString;
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
class GIT_ADD_TO_INDEX_HANDLER
|
||||
class GIT_ADD_TO_INDEX_HANDLER : public KIGIT_COMMON
|
||||
{
|
||||
public:
|
||||
GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository );
|
||||
@ -40,8 +41,7 @@ public:
|
||||
bool PerformAddToIndex();
|
||||
|
||||
private:
|
||||
git_repository* m_repository;
|
||||
|
||||
friend class LIBGIT_BACKEND;
|
||||
std::vector<wxString> m_filesToAdd;
|
||||
std::vector<wxString> m_filesFailedToAdd;
|
||||
};
|
||||
|
36
common/git/git_backend.cpp
Normal file
36
common/git/git_backend.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.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 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, you may find one here:
|
||||
* http://www.gnu.org/licenses/gpl-3.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 3 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include "git_backend.h"
|
||||
|
||||
static GIT_BACKEND* s_backend = nullptr;
|
||||
|
||||
GIT_BACKEND* GetGitBackend()
|
||||
{
|
||||
return s_backend;
|
||||
}
|
||||
|
||||
void SetGitBackend( GIT_BACKEND* aBackend )
|
||||
{
|
||||
s_backend = aBackend;
|
||||
}
|
127
common/git/git_backend.h
Normal file
127
common/git/git_backend.h
Normal file
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.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 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, you may find one here:
|
||||
* http://www.gnu.org/licenses/gpl-3.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 3 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#ifndef GIT_BACKEND_H_
|
||||
#define GIT_BACKEND_H_
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
class GIT_CLONE_HANDLER;
|
||||
class GIT_COMMIT_HANDLER;
|
||||
class GIT_PUSH_HANDLER;
|
||||
class GIT_STATUS_HANDLER;
|
||||
class GIT_ADD_TO_INDEX_HANDLER;
|
||||
class GIT_REMOVE_FROM_INDEX_HANDLER;
|
||||
class GIT_CONFIG_HANDLER;
|
||||
class GIT_INIT_HANDLER;
|
||||
class GIT_BRANCH_HANDLER;
|
||||
class GIT_PULL_HANDLER;
|
||||
class GIT_REVERT_HANDLER;
|
||||
struct RemoteConfig;
|
||||
struct git_repository;
|
||||
enum class InitResult;
|
||||
enum class BranchResult;
|
||||
enum class PullResult;
|
||||
struct FileStatus;
|
||||
enum class PushResult;
|
||||
|
||||
// Commit result shared across backend and handlers
|
||||
enum class CommitResult
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
class GIT_BACKEND
|
||||
{
|
||||
public:
|
||||
virtual ~GIT_BACKEND() = default;
|
||||
|
||||
virtual void Init() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
|
||||
// Whether the libgit2 library is available/initialized enough for use
|
||||
virtual bool IsLibraryAvailable() = 0;
|
||||
|
||||
virtual bool Clone( GIT_CLONE_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual CommitResult Commit( GIT_COMMIT_HANDLER* aHandler,
|
||||
const std::vector<wxString>& aFiles,
|
||||
const wxString& aMessage,
|
||||
const wxString& aAuthorName,
|
||||
const wxString& aAuthorEmail ) = 0;
|
||||
|
||||
virtual PushResult Push( GIT_PUSH_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual bool HasChangedFiles( GIT_STATUS_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual std::map<wxString, FileStatus> GetFileStatus( GIT_STATUS_HANDLER* aHandler,
|
||||
const wxString& aPathspec ) = 0;
|
||||
|
||||
virtual wxString GetCurrentBranchName( GIT_STATUS_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual void UpdateRemoteStatus( GIT_STATUS_HANDLER* aHandler,
|
||||
const std::set<wxString>& aLocalChanges,
|
||||
const std::set<wxString>& aRemoteChanges,
|
||||
std::map<wxString, FileStatus>& aFileStatus ) = 0;
|
||||
|
||||
virtual wxString GetWorkingDirectory( GIT_STATUS_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual wxString GetWorkingDirectory( GIT_CONFIG_HANDLER* aHandler ) = 0;
|
||||
virtual bool GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey,
|
||||
wxString& aValue ) = 0;
|
||||
|
||||
virtual bool IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) = 0;
|
||||
virtual InitResult InitializeRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) = 0;
|
||||
virtual bool SetupRemote( GIT_INIT_HANDLER* aHandler, const RemoteConfig& aConfig ) = 0;
|
||||
|
||||
virtual BranchResult SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) = 0;
|
||||
virtual bool BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) = 0;
|
||||
|
||||
virtual bool PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock ) = 0;
|
||||
virtual PullResult PerformPull( GIT_PULL_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual void PerformRevert( GIT_REVERT_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual git_repository* GetRepositoryForFile( const char* aFilename ) = 0;
|
||||
virtual int CreateBranch( git_repository* aRepo, const wxString& aBranchName ) = 0;
|
||||
virtual bool RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
|
||||
bool aRemoveGitDir, wxString* aErrors ) = 0;
|
||||
|
||||
virtual bool AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath ) = 0;
|
||||
|
||||
virtual bool PerformAddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler ) = 0;
|
||||
|
||||
virtual bool RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, const wxString& aFilePath ) = 0;
|
||||
|
||||
virtual void PerformRemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler ) = 0;
|
||||
};
|
||||
|
||||
GIT_BACKEND* GetGitBackend();
|
||||
void SetGitBackend( GIT_BACKEND* aBackend );
|
||||
|
||||
#endif
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
#include "git_branch_handler.h"
|
||||
#include "git_backend.h"
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <trace_helpers.h>
|
||||
@ -35,89 +36,12 @@ GIT_BRANCH_HANDLER::~GIT_BRANCH_HANDLER()
|
||||
|
||||
bool GIT_BRANCH_HANDLER::BranchExists( const wxString& aBranchName )
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return false;
|
||||
|
||||
git_reference* branchRef = nullptr;
|
||||
bool exists = LookupBranchReference( aBranchName, &branchRef );
|
||||
|
||||
if( branchRef )
|
||||
git_reference_free( branchRef );
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
bool GIT_BRANCH_HANDLER::LookupBranchReference( const wxString& aBranchName, git_reference** aReference )
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return false;
|
||||
|
||||
// Try direct lookup first
|
||||
if( git_reference_lookup( aReference, repo, aBranchName.mb_str() ) == GIT_OK )
|
||||
return true;
|
||||
|
||||
// Try dwim (Do What I Mean) lookup for short branch names
|
||||
if( git_reference_dwim( aReference, repo, aBranchName.mb_str() ) == GIT_OK )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return GetGitBackend()->BranchExists( this, aBranchName );
|
||||
}
|
||||
|
||||
BranchResult GIT_BRANCH_HANDLER::SwitchToBranch( const wxString& aBranchName )
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
{
|
||||
AddErrorString( _( "No repository available" ) );
|
||||
return BranchResult::Error;
|
||||
}
|
||||
|
||||
// Look up the branch reference
|
||||
git_reference* branchRef = nullptr;
|
||||
|
||||
if( !LookupBranchReference( aBranchName, &branchRef ) )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to lookup branch '%s': %s" ),
|
||||
aBranchName, KIGIT_COMMON::GetLastGitError() ) );
|
||||
return BranchResult::BranchNotFound;
|
||||
}
|
||||
|
||||
KIGIT::GitReferencePtr branchRefPtr( branchRef );
|
||||
const char* branchRefName = git_reference_name( branchRef );
|
||||
git_object* branchObj = nullptr;
|
||||
|
||||
if( git_revparse_single( &branchObj, repo, aBranchName.mb_str() ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to find branch head for '%s': %s" ),
|
||||
aBranchName, KIGIT_COMMON::GetLastGitError() ) );
|
||||
return BranchResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitObjectPtr branchObjPtr( branchObj );
|
||||
|
||||
// Switch to the branch
|
||||
if( git_checkout_tree( repo, branchObj, nullptr ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to switch to branch '%s': %s" ),
|
||||
aBranchName, KIGIT_COMMON::GetLastGitError() ) );
|
||||
return BranchResult::CheckoutFailed;
|
||||
}
|
||||
|
||||
// Update the HEAD reference
|
||||
if( git_repository_set_head( repo, branchRefName ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to update HEAD reference for branch '%s': %s" ),
|
||||
aBranchName, KIGIT_COMMON::GetLastGitError() ) );
|
||||
return BranchResult::Error;
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "Successfully switched to branch '%s'", aBranchName );
|
||||
return BranchResult::Success;
|
||||
return GetGitBackend()->SwitchToBranch( this, aBranchName );
|
||||
}
|
||||
|
||||
void GIT_BRANCH_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
|
||||
|
@ -57,15 +57,6 @@ public:
|
||||
bool BranchExists( const wxString& aBranchName );
|
||||
|
||||
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Look up a branch reference by name
|
||||
* @param aBranchName Name of the branch
|
||||
* @param aReference Output parameter for the reference
|
||||
* @return True if successful, false otherwise
|
||||
*/
|
||||
bool LookupBranchReference( const wxString& aBranchName, git_reference** aReference );
|
||||
};
|
||||
|
||||
#endif // GIT_BRANCH_HANDLER_H
|
@ -23,13 +23,8 @@
|
||||
|
||||
#include "git_clone_handler.h"
|
||||
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <trace_helpers.h>
|
||||
|
||||
#include <git2.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/log.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
GIT_CLONE_HANDLER::GIT_CLONE_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
|
||||
{}
|
||||
@ -41,55 +36,7 @@ GIT_CLONE_HANDLER::~GIT_CLONE_HANDLER()
|
||||
|
||||
bool GIT_CLONE_HANDLER::PerformClone()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock( GetCommon()->m_gitActionMutex, std::try_to_lock );
|
||||
|
||||
if( !lock.owns_lock() )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_CLONE_HANDLER::PerformClone() could not lock" );
|
||||
return false;
|
||||
}
|
||||
|
||||
wxFileName clonePath( m_clonePath );
|
||||
|
||||
if( !clonePath.DirExists() )
|
||||
{
|
||||
if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Could not create directory '%s'" ),
|
||||
m_clonePath ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
git_clone_options cloneOptions;
|
||||
git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION );
|
||||
cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
cloneOptions.checkout_opts.progress_cb = clone_progress_cb;
|
||||
cloneOptions.checkout_opts.progress_payload = this;
|
||||
cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb;
|
||||
cloneOptions.fetch_opts.callbacks.credentials = credentials_cb;
|
||||
cloneOptions.fetch_opts.callbacks.payload = this;
|
||||
|
||||
TestedTypes() = 0;
|
||||
ResetNextKey();
|
||||
git_repository* newRepo = nullptr;
|
||||
wxString remote = GetCommon()->m_remote;
|
||||
|
||||
if( git_clone( &newRepo, remote.mbc_str(), m_clonePath.mbc_str(),
|
||||
&cloneOptions ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Could not clone repository '%s'" ), remote ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
GetCommon()->SetRepo( newRepo );
|
||||
|
||||
if( m_progressReporter )
|
||||
m_progressReporter->Hide();
|
||||
|
||||
m_previousProgress = 0;
|
||||
|
||||
return true;
|
||||
return GetGitBackend()->Clone( this );
|
||||
}
|
||||
|
||||
|
||||
|
@ -22,8 +22,7 @@
|
||||
*/
|
||||
|
||||
#include "git_commit_handler.h"
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <wx/log.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
GIT_COMMIT_HANDLER::GIT_COMMIT_HANDLER( git_repository* aRepo ) :
|
||||
KIGIT_COMMON( aRepo )
|
||||
@ -34,121 +33,13 @@ GIT_COMMIT_HANDLER::~GIT_COMMIT_HANDLER()
|
||||
{}
|
||||
|
||||
|
||||
GIT_COMMIT_HANDLER::CommitResult
|
||||
CommitResult
|
||||
GIT_COMMIT_HANDLER::PerformCommit( const std::vector<wxString>& aFiles,
|
||||
const wxString& aMessage,
|
||||
const wxString& aAuthorName,
|
||||
const wxString& aAuthorEmail )
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return CommitResult::Error;
|
||||
|
||||
git_index* index = nullptr;
|
||||
|
||||
if( git_repository_index( &index, repo ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to get repository index: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
for( const wxString& file : aFiles )
|
||||
{
|
||||
if( git_index_add_bypath( index, file.mb_str() ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to add file to index: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
if( git_index_write( index ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to write index: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
git_oid tree_id;
|
||||
|
||||
if( git_index_write_tree( &tree_id, index ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to write tree: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
git_tree* tree = nullptr;
|
||||
|
||||
if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to lookup tree: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitTreePtr treePtr( tree );
|
||||
git_commit* parent = nullptr;
|
||||
|
||||
if( git_repository_head_unborn( repo ) == 0 )
|
||||
{
|
||||
git_reference* headRef = nullptr;
|
||||
|
||||
if( git_repository_head( &headRef, repo ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to get HEAD reference: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitReferencePtr headRefPtr( headRef );
|
||||
|
||||
if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to get commit: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
KIGIT::GitCommitPtr parentPtr( parent );
|
||||
|
||||
git_signature* author = nullptr;
|
||||
|
||||
if( git_signature_now( &author, aAuthorName.mb_str(), aAuthorEmail.mb_str() ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to create author signature: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitSignaturePtr authorPtr( author );
|
||||
git_oid oid;
|
||||
size_t parentsCount = parent ? 1 : 0;
|
||||
#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
|
||||
&& ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
|
||||
git_commit* const parents[1] = { parent };
|
||||
git_commit** const parentsPtr = parent ? parents : nullptr;
|
||||
#else
|
||||
const git_commit* parents[1] = { parent };
|
||||
const git_commit** parentsPtr = parent ? parents : nullptr;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr,
|
||||
aMessage.mb_str(), tree, parentsCount, parentsPtr ) != 0 )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to create commit: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return CommitResult::Error;
|
||||
}
|
||||
|
||||
return CommitResult::Success;
|
||||
return GetGitBackend()->Commit( this, aFiles, aMessage, aAuthorName, aAuthorEmail );
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,25 +27,20 @@
|
||||
// Define a class to handle git commit operations
|
||||
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git2.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
class GIT_COMMIT_HANDLER : public KIGIT_COMMON
|
||||
{
|
||||
public:
|
||||
GIT_COMMIT_HANDLER( git_repository* aRepo );
|
||||
virtual ~GIT_COMMIT_HANDLER();
|
||||
|
||||
enum class CommitResult
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
Cancelled
|
||||
};
|
||||
|
||||
CommitResult PerformCommit( const std::vector<wxString>& aFiles,
|
||||
const wxString& aMessage,
|
||||
const wxString& aAuthorName,
|
||||
@ -54,6 +49,7 @@ public:
|
||||
wxString GetErrorString() const;
|
||||
|
||||
private:
|
||||
friend class LIBGIT_BACKEND;
|
||||
void AddErrorString( const wxString& aErrorString );
|
||||
|
||||
wxString m_errorString;
|
||||
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
#include "git_config_handler.h"
|
||||
#include "git_backend.h"
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <pgm_base.h>
|
||||
@ -59,48 +60,12 @@ GitUserConfig GIT_CONFIG_HANDLER::GetUserConfig()
|
||||
|
||||
wxString GIT_CONFIG_HANDLER::GetWorkingDirectory()
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return wxEmptyString;
|
||||
|
||||
const char* workdir = git_repository_workdir( repo );
|
||||
|
||||
if( !workdir )
|
||||
return wxEmptyString;
|
||||
|
||||
return wxString( workdir );
|
||||
return GetGitBackend()->GetWorkingDirectory( this );
|
||||
}
|
||||
|
||||
bool GIT_CONFIG_HANDLER::GetConfigString( const wxString& aKey, wxString& aValue )
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return false;
|
||||
|
||||
git_config* config = nullptr;
|
||||
|
||||
if( git_repository_config( &config, repo ) != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to get repository config: %s", KIGIT_COMMON::GetLastGitError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
KIGIT::GitConfigPtr configPtr( config );
|
||||
|
||||
git_config_entry* entry = nullptr;
|
||||
int result = git_config_get_entry( &entry, config, aKey.mb_str() );
|
||||
KIGIT::GitConfigEntryPtr entryPtr( entry );
|
||||
|
||||
if( result != GIT_OK || entry == nullptr )
|
||||
{
|
||||
wxLogTrace( traceGit, "Config key '%s' not found", aKey );
|
||||
return false;
|
||||
}
|
||||
|
||||
aValue = wxString( entry->value );
|
||||
return true;
|
||||
return GetGitBackend()->GetConfigString( this, aKey, aValue );
|
||||
}
|
||||
|
||||
void GIT_CONFIG_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
|
||||
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
#include "git_init_handler.h"
|
||||
#include "git_backend.h"
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <trace_helpers.h>
|
||||
@ -35,118 +36,17 @@ GIT_INIT_HANDLER::~GIT_INIT_HANDLER()
|
||||
|
||||
bool GIT_INIT_HANDLER::IsRepository( const wxString& aPath )
|
||||
{
|
||||
git_repository* repo = nullptr;
|
||||
int error = git_repository_open( &repo, aPath.mb_str() );
|
||||
|
||||
if( error == 0 )
|
||||
{
|
||||
git_repository_free( repo );
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return GetGitBackend()->IsRepository( this, aPath );
|
||||
}
|
||||
|
||||
InitResult GIT_INIT_HANDLER::InitializeRepository( const wxString& aPath )
|
||||
{
|
||||
// Check if directory is already a git repository
|
||||
if( IsRepository( aPath ) )
|
||||
{
|
||||
return InitResult::AlreadyExists;
|
||||
}
|
||||
|
||||
git_repository* repo = nullptr;
|
||||
|
||||
if( git_repository_init( &repo, aPath.mb_str(), 0 ) != GIT_OK )
|
||||
{
|
||||
if( repo )
|
||||
git_repository_free( repo );
|
||||
|
||||
AddErrorString( wxString::Format( _( "Failed to initialize Git repository: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return InitResult::Error;
|
||||
}
|
||||
|
||||
// Update the common repository pointer
|
||||
GetCommon()->SetRepo( repo );
|
||||
|
||||
wxLogTrace( traceGit, "Successfully initialized Git repository at %s", aPath );
|
||||
return InitResult::Success;
|
||||
return GetGitBackend()->InitializeRepository( this, aPath );
|
||||
}
|
||||
|
||||
bool GIT_INIT_HANDLER::SetupRemote( const RemoteConfig& aConfig )
|
||||
{
|
||||
// This is an optional step
|
||||
if( aConfig.url.IsEmpty() )
|
||||
return true;
|
||||
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
{
|
||||
AddErrorString( _( "No repository available to set up remote" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update connection settings in common
|
||||
GetCommon()->SetUsername( aConfig.username );
|
||||
GetCommon()->SetPassword( aConfig.password );
|
||||
GetCommon()->SetSSHKey( aConfig.sshKey );
|
||||
|
||||
git_remote* remote = nullptr;
|
||||
wxString fullURL;
|
||||
|
||||
// Build the full URL based on connection type
|
||||
if( aConfig.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
|
||||
{
|
||||
fullURL = aConfig.username + "@" + aConfig.url;
|
||||
}
|
||||
else if( aConfig.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
|
||||
{
|
||||
fullURL = aConfig.url.StartsWith( "https" ) ? "https://" : "http://";
|
||||
|
||||
if( !aConfig.username.empty() )
|
||||
{
|
||||
fullURL.append( aConfig.username );
|
||||
|
||||
if( !aConfig.password.empty() )
|
||||
{
|
||||
fullURL.append( wxS( ":" ) );
|
||||
fullURL.append( aConfig.password );
|
||||
}
|
||||
|
||||
fullURL.append( wxS( "@" ) );
|
||||
}
|
||||
|
||||
// Extract the bare URL (without protocol prefix)
|
||||
wxString bareURL = aConfig.url;
|
||||
if( bareURL.StartsWith( "https://" ) )
|
||||
bareURL = bareURL.Mid( 8 );
|
||||
else if( bareURL.StartsWith( "http://" ) )
|
||||
bareURL = bareURL.Mid( 7 );
|
||||
|
||||
fullURL.append( bareURL );
|
||||
}
|
||||
else
|
||||
{
|
||||
fullURL = aConfig.url;
|
||||
}
|
||||
|
||||
int error = git_remote_create_with_fetchspec( &remote, repo, "origin",
|
||||
fullURL.ToStdString().c_str(),
|
||||
"+refs/heads/*:refs/remotes/origin/*" );
|
||||
|
||||
KIGIT::GitRemotePtr remotePtr( remote );
|
||||
|
||||
if( error != GIT_OK )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to create remote: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "Successfully set up remote origin" );
|
||||
return true;
|
||||
return GetGitBackend()->SetupRemote( this, aConfig );
|
||||
}
|
||||
|
||||
void GIT_INIT_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
|
||||
|
@ -22,188 +22,22 @@
|
||||
*/
|
||||
|
||||
#include "git_pull_handler.h"
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <trace_helpers.h>
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <time.h>
|
||||
#include <memory>
|
||||
#include "git_backend.h"
|
||||
|
||||
GIT_PULL_HANDLER::GIT_PULL_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
|
||||
{
|
||||
}
|
||||
|
||||
{}
|
||||
|
||||
GIT_PULL_HANDLER::~GIT_PULL_HANDLER()
|
||||
{
|
||||
}
|
||||
|
||||
{}
|
||||
|
||||
bool GIT_PULL_HANDLER::PerformFetch( bool aSkipLock )
|
||||
{
|
||||
if( !GetRepo() )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - No repository found" );
|
||||
return false;
|
||||
return GetGitBackend()->PerformFetch( this, aSkipLock );
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock( GetCommon()->m_gitActionMutex, std::try_to_lock );
|
||||
|
||||
if( !aSkipLock && !lock.owns_lock() )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fetch updates from remote repository
|
||||
git_remote* remote = nullptr;
|
||||
|
||||
if( git_remote_lookup( &remote, GetRepo(), "origin" ) != 0 )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote 'origin'" );
|
||||
AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ), "origin" ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
KIGIT::GitRemotePtr remotePtr( remote );
|
||||
|
||||
git_remote_callbacks remoteCallbacks;
|
||||
git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
|
||||
remoteCallbacks.sideband_progress = progress_cb;
|
||||
remoteCallbacks.transfer_progress = transfer_progress_cb;
|
||||
remoteCallbacks.credentials = credentials_cb;
|
||||
remoteCallbacks.payload = this;
|
||||
GetCommon()->SetCancelled( false );
|
||||
|
||||
TestedTypes() = 0;
|
||||
ResetNextKey();
|
||||
|
||||
if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
|
||||
{
|
||||
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to connect to remote: %s", errorMsg );
|
||||
AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ), "origin", errorMsg ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
git_fetch_options fetchOptions;
|
||||
git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
|
||||
fetchOptions.callbacks = remoteCallbacks;
|
||||
|
||||
if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
|
||||
{
|
||||
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to fetch from remote: %s", errorMsg );
|
||||
AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ), "origin", errorMsg ) );
|
||||
return false;
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Fetch completed successfully" );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
PullResult GIT_PULL_HANDLER::PerformPull()
|
||||
{
|
||||
PullResult result = PullResult::Success;
|
||||
std::unique_lock<std::mutex> lock( GetCommon()->m_gitActionMutex, std::try_to_lock );
|
||||
|
||||
if( !lock.owns_lock() )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
if( !PerformFetch( true ) )
|
||||
return PullResult::Error;
|
||||
|
||||
git_oid pull_merge_oid = {};
|
||||
|
||||
if( git_repository_fetchhead_foreach( GetRepo(), fetchhead_foreach_cb,
|
||||
&pull_merge_oid ) )
|
||||
{
|
||||
AddErrorString( _( "Could not read 'FETCH_HEAD'" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
git_annotated_commit* fetchhead_commit;
|
||||
|
||||
if( git_annotated_commit_lookup( &fetchhead_commit, GetRepo(), &pull_merge_oid ) )
|
||||
{
|
||||
AddErrorString( _( "Could not lookup commit" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitAnnotatedCommitPtr fetchheadCommitPtr( fetchhead_commit );
|
||||
const git_annotated_commit* merge_commits[] = { fetchhead_commit };
|
||||
git_merge_analysis_t merge_analysis;
|
||||
git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
|
||||
|
||||
if( git_merge_analysis( &merge_analysis, &merge_preference, GetRepo(), merge_commits, 1 ) )
|
||||
{
|
||||
AddErrorString( _( "Could not analyze merge" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
|
||||
{
|
||||
AddErrorString( _( "Invalid HEAD. Cannot merge." ) );
|
||||
return PullResult::MergeFailed;
|
||||
}
|
||||
|
||||
// Nothing to do if the repository is up to date
|
||||
if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Repository is up to date" );
|
||||
git_repository_state_cleanup( GetRepo() );
|
||||
return PullResult::UpToDate;
|
||||
}
|
||||
|
||||
// Fast-forward is easy, just update the local reference
|
||||
if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" );
|
||||
return handleFastForward();
|
||||
}
|
||||
|
||||
if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" );
|
||||
|
||||
// Check git config to determine if we should rebase or merge
|
||||
git_config* config = nullptr;
|
||||
|
||||
if( git_repository_config( &config, GetRepo() ) != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" );
|
||||
AddErrorString( _( "Could not access repository configuration" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitConfigPtr configPtr( config );
|
||||
|
||||
// Check for pull.rebase config
|
||||
int rebase_value = 0;
|
||||
int ret = git_config_get_bool( &rebase_value, config, "pull.rebase" );
|
||||
|
||||
// If pull.rebase is set to true, use rebase; otherwise use merge
|
||||
if( ret == GIT_OK && rebase_value )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" );
|
||||
return handleRebase( merge_commits, 1 );
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using merge based on config" );
|
||||
return handleMerge( merge_commits, 1 );
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" );
|
||||
//TODO: handle merges when they need to be resolved
|
||||
|
||||
return result;
|
||||
return GetGitBackend()->PerformPull( this );
|
||||
}
|
||||
|
||||
const std::vector<std::pair<std::string, std::vector<CommitDetails>>>&
|
||||
@ -212,384 +46,7 @@ GIT_PULL_HANDLER::GetFetchResults() const
|
||||
return m_fetchResults;
|
||||
}
|
||||
|
||||
|
||||
std::string GIT_PULL_HANDLER::getFirstLineFromCommitMessage( const std::string& aMessage )
|
||||
{
|
||||
if( aMessage.empty() )
|
||||
return aMessage;
|
||||
|
||||
size_t firstLineEnd = aMessage.find_first_of( '\n' );
|
||||
|
||||
if( firstLineEnd != std::string::npos )
|
||||
return aMessage.substr( 0, firstLineEnd );
|
||||
|
||||
return aMessage;
|
||||
}
|
||||
|
||||
|
||||
std::string GIT_PULL_HANDLER::getFormattedCommitDate( const git_time& aTime )
|
||||
{
|
||||
char dateBuffer[64];
|
||||
time_t time = static_cast<time_t>( aTime.time );
|
||||
struct tm timeInfo;
|
||||
#ifdef _WIN32
|
||||
localtime_s( &timeInfo, &time );
|
||||
#else
|
||||
gmtime_r( &time, &timeInfo );
|
||||
#endif
|
||||
strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", &timeInfo );
|
||||
return dateBuffer;
|
||||
}
|
||||
|
||||
|
||||
PullResult GIT_PULL_HANDLER::handleFastForward()
|
||||
{
|
||||
git_reference* rawRef = nullptr;
|
||||
|
||||
// Get the current HEAD reference
|
||||
if( git_repository_head( &rawRef, GetRepo() ) )
|
||||
{
|
||||
AddErrorString( _( "Could not get repository head" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitReferencePtr headRef( rawRef );
|
||||
|
||||
git_oid updatedRefOid;
|
||||
const char* currentBranchName = git_reference_name( rawRef );
|
||||
const char* branch_shorthand = git_reference_shorthand( rawRef );
|
||||
wxString remote_name = GetRemotename();
|
||||
wxString remoteBranchName = wxString::Format( "refs/remotes/%s/%s",
|
||||
remote_name, branch_shorthand );
|
||||
|
||||
// Get the OID of the updated reference (remote-tracking branch)
|
||||
if( git_reference_name_to_id( &updatedRefOid, GetRepo(), remoteBranchName.c_str() ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ),
|
||||
remoteBranchName ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
// Get the target commit object
|
||||
git_commit* targetCommit = nullptr;
|
||||
|
||||
if( git_commit_lookup( &targetCommit, GetRepo(), &updatedRefOid ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( _( "Could not look up target commit" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitCommitPtr targetCommitPtr( targetCommit );
|
||||
|
||||
// Get the tree from the target commit
|
||||
git_tree* targetTree = nullptr;
|
||||
|
||||
if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
|
||||
{
|
||||
git_commit_free( targetCommit );
|
||||
AddErrorString( _( "Could not get tree from target commit" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitTreePtr targetTreePtr( targetTree );
|
||||
|
||||
// Perform a checkout to update the working directory
|
||||
git_checkout_options checkoutOptions;
|
||||
git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
|
||||
auto notify_cb = []( git_checkout_notify_t why, const char* path, const git_diff_file* baseline,
|
||||
const git_diff_file* target, const git_diff_file* workdir, void* payload ) -> int
|
||||
{
|
||||
switch( why )
|
||||
{
|
||||
case GIT_CHECKOUT_NOTIFY_CONFLICT:
|
||||
wxLogTrace( traceGit, "Checkout conflict: %s", path ? path : "unknown" );
|
||||
break;
|
||||
case GIT_CHECKOUT_NOTIFY_DIRTY:
|
||||
wxLogTrace( traceGit, "Checkout dirty: %s", path ? path : "unknown" );
|
||||
break;
|
||||
case GIT_CHECKOUT_NOTIFY_UPDATED:
|
||||
wxLogTrace( traceGit, "Checkout updated: %s", path ? path : "unknown" );
|
||||
break;
|
||||
case GIT_CHECKOUT_NOTIFY_UNTRACKED:
|
||||
wxLogTrace( traceGit, "Checkout untracked: %s", path ? path : "unknown" );
|
||||
break;
|
||||
case GIT_CHECKOUT_NOTIFY_IGNORED:
|
||||
wxLogTrace( traceGit, "Checkout ignored: %s", path ? path : "unknown" );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
|
||||
checkoutOptions.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
|
||||
checkoutOptions.notify_cb = notify_cb;
|
||||
|
||||
if( git_checkout_tree( GetRepo(), reinterpret_cast<git_object*>( targetTree ), &checkoutOptions ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( _( "Failed to perform checkout operation." ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
git_reference* updatedRef = nullptr;
|
||||
|
||||
// Update the current branch to point to the new commit
|
||||
if (git_reference_set_target(&updatedRef, rawRef, &updatedRefOid, nullptr) != GIT_OK)
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Failed to update reference '%s' to point to '%s'" ), currentBranchName,
|
||||
git_oid_tostr_s( &updatedRefOid ) ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitReferencePtr updatedRefPtr( updatedRef );
|
||||
|
||||
// Clean up the repository state
|
||||
if( git_repository_state_cleanup( GetRepo() ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( _( "Failed to clean up repository state after fast-forward." ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
git_revwalk* revWalker = nullptr;
|
||||
|
||||
// Collect commit details for updated references
|
||||
if( git_revwalk_new( &revWalker, GetRepo() ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( _( "Failed to initialize revision walker." ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitRevWalkPtr revWalkerPtr( revWalker );
|
||||
git_revwalk_sorting( revWalker, GIT_SORT_TIME );
|
||||
|
||||
if( git_revwalk_push_glob( revWalker, currentBranchName ) != GIT_OK )
|
||||
{
|
||||
AddErrorString( _( "Failed to push reference to revision walker." ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
std::pair<std::string, std::vector<CommitDetails>>& branchCommits = m_fetchResults.emplace_back();
|
||||
branchCommits.first = currentBranchName;
|
||||
|
||||
git_oid commitOid;
|
||||
|
||||
while( git_revwalk_next( &commitOid, revWalker ) == GIT_OK )
|
||||
{
|
||||
git_commit* commit = nullptr;
|
||||
|
||||
if( git_commit_lookup( &commit, GetRepo(), &commitOid ) )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Could not lookup commit '%s'" ),
|
||||
git_oid_tostr_s( &commitOid ) ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitCommitPtr commitPtr( commit );
|
||||
|
||||
CommitDetails details;
|
||||
details.m_sha = git_oid_tostr_s( &commitOid );
|
||||
details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) );
|
||||
details.m_author = git_commit_author( commit )->name;
|
||||
details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
|
||||
|
||||
branchCommits.second.push_back( details );
|
||||
}
|
||||
|
||||
return PullResult::FastForward;
|
||||
}
|
||||
|
||||
|
||||
PullResult GIT_PULL_HANDLER::handleMerge( const git_annotated_commit** aMergeHeads,
|
||||
size_t aMergeHeadsCount )
|
||||
{
|
||||
git_merge_options merge_opts;
|
||||
git_merge_options_init( &merge_opts, GIT_MERGE_OPTIONS_VERSION );
|
||||
|
||||
git_checkout_options checkout_opts;
|
||||
git_checkout_init_options( &checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION );
|
||||
|
||||
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
|
||||
if( git_merge( GetRepo(), aMergeHeads, aMergeHeadsCount, &merge_opts, &checkout_opts ) )
|
||||
{
|
||||
AddErrorString( _( "Could not merge commits" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
// Get the repository index
|
||||
git_index* index = nullptr;
|
||||
|
||||
if( git_repository_index( &index, GetRepo() ) )
|
||||
{
|
||||
AddErrorString( _( "Could not get repository index" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
// Check for conflicts
|
||||
git_index_conflict_iterator* conflicts = nullptr;
|
||||
|
||||
if( git_index_conflict_iterator_new( &conflicts, index ) )
|
||||
{
|
||||
AddErrorString( _( "Could not get conflict iterator" ) );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexConflictIteratorPtr conflictsPtr( conflicts );
|
||||
|
||||
const git_index_entry* ancestor = nullptr;
|
||||
const git_index_entry* our = nullptr;
|
||||
const git_index_entry* their = nullptr;
|
||||
std::vector<ConflictData> conflict_data;
|
||||
|
||||
while( git_index_conflict_next( &ancestor, &our, &their, conflicts ) == 0 )
|
||||
{
|
||||
// Case 3: Both files have changed
|
||||
if( ancestor && our && their )
|
||||
{
|
||||
ConflictData conflict_datum;
|
||||
conflict_datum.filename = our->path;
|
||||
conflict_datum.our_oid = our->id;
|
||||
conflict_datum.their_oid = their->id;
|
||||
conflict_datum.our_commit_time = our->mtime.seconds;
|
||||
conflict_datum.their_commit_time = their->mtime.seconds;
|
||||
conflict_datum.our_status = _( "Changed" );
|
||||
conflict_datum.their_status = _( "Changed" );
|
||||
conflict_datum.use_ours = true;
|
||||
|
||||
conflict_data.push_back( conflict_datum );
|
||||
}
|
||||
// Case 4: File added in both ours and theirs
|
||||
else if( !ancestor && our && their )
|
||||
{
|
||||
ConflictData conflict_datum;
|
||||
conflict_datum.filename = our->path;
|
||||
conflict_datum.our_oid = our->id;
|
||||
conflict_datum.their_oid = their->id;
|
||||
conflict_datum.our_commit_time = our->mtime.seconds;
|
||||
conflict_datum.their_commit_time = their->mtime.seconds;
|
||||
conflict_datum.our_status = _( "Added" );
|
||||
conflict_datum.their_status = _( "Added" );
|
||||
conflict_datum.use_ours = true;
|
||||
|
||||
conflict_data.push_back( conflict_datum );
|
||||
}
|
||||
// Case 1: Remote file has changed or been added, local file has not
|
||||
else if( their && !our )
|
||||
{
|
||||
// Accept their changes
|
||||
git_index_add( index, their );
|
||||
}
|
||||
// Case 2: Local file has changed or been added, remote file has not
|
||||
else if( our && !their )
|
||||
{
|
||||
// Accept our changes
|
||||
git_index_add( index, our );
|
||||
}
|
||||
else
|
||||
{
|
||||
wxLogError( wxS( "Unexpected conflict state" ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( conflict_data.empty() )
|
||||
{
|
||||
git_index_conflict_cleanup( index );
|
||||
git_index_write( index );
|
||||
}
|
||||
|
||||
return conflict_data.empty() ? PullResult::Success : PullResult::MergeFailed;
|
||||
}
|
||||
|
||||
|
||||
PullResult GIT_PULL_HANDLER::handleRebase( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount )
|
||||
{
|
||||
// Get the current branch reference
|
||||
git_reference* head_ref = nullptr;
|
||||
|
||||
if( git_repository_head( &head_ref, GetRepo() ) )
|
||||
{
|
||||
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to get HEAD: %s", errorMsg );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitReferencePtr headRefPtr(head_ref);
|
||||
|
||||
// Initialize rebase operation
|
||||
git_rebase* rebase = nullptr;
|
||||
git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT;
|
||||
rebase_opts.checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
|
||||
if( git_rebase_init( &rebase, GetRepo(), nullptr, nullptr, aMergeHeads[0], &rebase_opts ) )
|
||||
{
|
||||
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to initialize rebase: %s", errorMsg );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitRebasePtr rebasePtr( rebase );
|
||||
git_rebase_operation* operation = nullptr;
|
||||
|
||||
while( git_rebase_next( &operation, rebase ) != GIT_ITEROVER )
|
||||
{
|
||||
// Check for conflicts
|
||||
git_index* index = nullptr;
|
||||
if( git_repository_index( &index, GetRepo() ) )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to get index: %s",
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return PullResult::Error;
|
||||
}
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
if( git_index_has_conflicts( index ) )
|
||||
{
|
||||
// Abort the rebase if there are conflicts because we need to merge manually
|
||||
git_rebase_abort( rebase );
|
||||
AddErrorString( _( "Conflicts detected during rebase" ) );
|
||||
return PullResult::MergeFailed;
|
||||
}
|
||||
|
||||
git_oid commit_id;
|
||||
git_signature* committer = nullptr;
|
||||
|
||||
if( git_signature_default( &committer, GetRepo() ) )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to create signature: %s",
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitSignaturePtr committerPtr( committer );
|
||||
|
||||
if( git_rebase_commit( &commit_id, rebase, nullptr, committer, nullptr, nullptr ) != GIT_OK )
|
||||
{
|
||||
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to commit operation: %s", errorMsg );
|
||||
git_rebase_abort( rebase );
|
||||
return PullResult::Error;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the rebase
|
||||
if( git_rebase_finish( rebase, nullptr ) )
|
||||
{
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to finish rebase: %s",
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return PullResult::Error;
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Rebase completed successfully" );
|
||||
git_repository_state_cleanup( GetRepo() );
|
||||
return PullResult::Success;
|
||||
}
|
||||
|
||||
|
||||
void GIT_PULL_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
|
||||
{
|
||||
|
||||
ReportProgress( aCurrent, aTotal, aMessage );
|
||||
}
|
||||
|
@ -29,7 +29,6 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <git2.h>
|
||||
|
||||
// Structure to store commit details
|
||||
struct CommitDetails
|
||||
@ -50,22 +49,12 @@ enum class PullResult : int
|
||||
FastForward
|
||||
};
|
||||
|
||||
struct ConflictData
|
||||
{
|
||||
std::string filename;
|
||||
std::string our_status;
|
||||
std::string their_status;
|
||||
git_oid our_oid;
|
||||
git_oid their_oid;
|
||||
git_time_t our_commit_time;
|
||||
git_time_t their_commit_time;
|
||||
bool use_ours; // Flag indicating user's choice (true = ours, false = theirs)
|
||||
};
|
||||
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
class GIT_PULL_HANDLER : public KIGIT_REPO_MIXIN
|
||||
{
|
||||
public:
|
||||
friend class LIBGIT_BACKEND;
|
||||
GIT_PULL_HANDLER( KIGIT_COMMON* aCommon );
|
||||
~GIT_PULL_HANDLER();
|
||||
|
||||
@ -77,15 +66,8 @@ public:
|
||||
// Implementation for progress updates
|
||||
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
|
||||
|
||||
|
||||
private:
|
||||
std::vector<std::pair<std::string, std::vector<CommitDetails>>> m_fetchResults;
|
||||
|
||||
std::string getFirstLineFromCommitMessage( const std::string& aMessage );
|
||||
std::string getFormattedCommitDate( const git_time& aTime );
|
||||
PullResult handleFastForward();
|
||||
PullResult handleMerge( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount );
|
||||
PullResult handleRebase( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount );
|
||||
};
|
||||
|
||||
#endif // _GIT_PULL_HANDLER_H_
|
||||
|
@ -22,13 +22,8 @@
|
||||
*/
|
||||
|
||||
#include "git_push_handler.h"
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <trace_helpers.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <wx/log.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( KIGIT_COMMON* aRepo ) : KIGIT_REPO_MIXIN( aRepo )
|
||||
{}
|
||||
@ -38,79 +33,7 @@ GIT_PUSH_HANDLER::~GIT_PUSH_HANDLER()
|
||||
|
||||
PushResult GIT_PUSH_HANDLER::PerformPush()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock( GetCommon()->m_gitActionMutex, std::try_to_lock );
|
||||
|
||||
if(!lock.owns_lock())
|
||||
{
|
||||
wxLogTrace(traceGit, "GIT_PUSH_HANDLER::PerformPush: Could not lock mutex");
|
||||
return PushResult::Error;
|
||||
}
|
||||
|
||||
PushResult result = PushResult::Success;
|
||||
|
||||
// Fetch updates from remote repository
|
||||
git_remote* remote = nullptr;
|
||||
|
||||
if(git_remote_lookup(&remote, GetRepo(), "origin") != 0)
|
||||
{
|
||||
AddErrorString(_("Could not lookup remote"));
|
||||
return PushResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitRemotePtr remotePtr(remote);
|
||||
|
||||
git_remote_callbacks remoteCallbacks;
|
||||
git_remote_init_callbacks(&remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION);
|
||||
remoteCallbacks.sideband_progress = progress_cb;
|
||||
remoteCallbacks.transfer_progress = transfer_progress_cb;
|
||||
remoteCallbacks.update_tips = update_cb;
|
||||
remoteCallbacks.push_transfer_progress = push_transfer_progress_cb;
|
||||
remoteCallbacks.credentials = credentials_cb;
|
||||
remoteCallbacks.payload = this;
|
||||
GetCommon()->SetCancelled( false );
|
||||
|
||||
TestedTypes() = 0;
|
||||
ResetNextKey();
|
||||
|
||||
if( git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, nullptr, nullptr ) )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Could not connect to remote: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
return PushResult::Error;
|
||||
}
|
||||
|
||||
git_push_options pushOptions;
|
||||
git_push_init_options( &pushOptions, GIT_PUSH_OPTIONS_VERSION );
|
||||
pushOptions.callbacks = remoteCallbacks;
|
||||
|
||||
// Get the current HEAD reference
|
||||
git_reference* head = nullptr;
|
||||
|
||||
if( git_repository_head( &head, GetRepo() ) != 0 )
|
||||
{
|
||||
git_remote_disconnect( remote );
|
||||
AddErrorString( _( "Could not get repository head" ) );
|
||||
return PushResult::Error;
|
||||
}
|
||||
|
||||
KIGIT::GitReferencePtr headPtr( head );
|
||||
|
||||
// Create refspec for current branch
|
||||
const char* refs[1];
|
||||
refs[0] = git_reference_name( head );
|
||||
const git_strarray refspecs = { (char**) refs, 1 };
|
||||
|
||||
if( git_remote_push( remote, &refspecs, &pushOptions ) )
|
||||
{
|
||||
AddErrorString( wxString::Format( _( "Could not push to remote: %s" ),
|
||||
KIGIT_COMMON::GetLastGitError() ) );
|
||||
git_remote_disconnect( remote );
|
||||
return PushResult::Error;
|
||||
}
|
||||
|
||||
git_remote_disconnect( remote );
|
||||
|
||||
return result;
|
||||
return GetGitBackend()->Push( this );
|
||||
}
|
||||
|
||||
|
||||
|
@ -21,15 +21,14 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <wx/string.h>
|
||||
#include "git_remove_from_index_handler.h"
|
||||
#include "git_backend.h"
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include "git_remove_from_index_handler.h"
|
||||
|
||||
GIT_REMOVE_FROM_INDEX_HANDLER::GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository )
|
||||
GIT_REMOVE_FROM_INDEX_HANDLER::GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository ) :
|
||||
KIGIT_COMMON( aRepository )
|
||||
{
|
||||
m_repository = aRepository;
|
||||
m_filesToRemove.clear();
|
||||
}
|
||||
|
||||
@ -41,61 +40,11 @@ GIT_REMOVE_FROM_INDEX_HANDLER::~GIT_REMOVE_FROM_INDEX_HANDLER()
|
||||
|
||||
bool GIT_REMOVE_FROM_INDEX_HANDLER::RemoveFromIndex( const wxString& aFilePath )
|
||||
{
|
||||
// Test if file is currently in the index
|
||||
|
||||
git_index* index = nullptr;
|
||||
size_t at_pos = 0;
|
||||
|
||||
if( git_repository_index( &index, m_repository ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to get repository index" );
|
||||
return false;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to find index entry for %s", aFilePath );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_filesToRemove.push_back( aFilePath );
|
||||
return true;
|
||||
return GetGitBackend()->RemoveFromIndex( this, aFilePath );
|
||||
}
|
||||
|
||||
|
||||
void GIT_REMOVE_FROM_INDEX_HANDLER::PerformRemoveFromIndex()
|
||||
{
|
||||
for( auto& file : m_filesToRemove )
|
||||
{
|
||||
git_index* index = nullptr;
|
||||
git_oid oid;
|
||||
|
||||
if( git_repository_index( &index, m_repository ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to get repository index" );
|
||||
return;
|
||||
}
|
||||
|
||||
KIGIT::GitIndexPtr indexPtr( index );
|
||||
|
||||
if( git_index_remove_bypath( index, file.ToUTF8().data() ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to remove index entry for %s", file );
|
||||
return;
|
||||
}
|
||||
|
||||
if( git_index_write( index ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to write index" );
|
||||
return;
|
||||
}
|
||||
|
||||
if( git_index_write_tree( &oid, index ) != 0 )
|
||||
{
|
||||
wxLogError( "Failed to write index tree" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
GetGitBackend()->PerformRemoveFromIndex( this );
|
||||
}
|
||||
|
@ -24,12 +24,13 @@
|
||||
#ifndef GIT_REMOVE_FROM_INDEX_HANDLER_H_
|
||||
#define GIT_REMOVE_FROM_INDEX_HANDLER_H_
|
||||
|
||||
#include <git2.h>
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
class wxString;
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
class GIT_REMOVE_FROM_INDEX_HANDLER
|
||||
class GIT_REMOVE_FROM_INDEX_HANDLER : public KIGIT_COMMON
|
||||
{
|
||||
public:
|
||||
GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository );
|
||||
@ -41,7 +42,7 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
git_repository* m_repository;
|
||||
friend class LIBGIT_BACKEND;
|
||||
|
||||
std::vector<wxString> m_filesToRemove;
|
||||
};
|
||||
|
@ -22,11 +22,7 @@
|
||||
*/
|
||||
|
||||
#include "git_revert_handler.h"
|
||||
|
||||
#include <wx/log.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include <trace_helpers.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
|
||||
GIT_REVERT_HANDLER::GIT_REVERT_HANDLER( git_repository* aRepository )
|
||||
@ -46,74 +42,8 @@ bool GIT_REVERT_HANDLER::Revert( const wxString& aFilePath )
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void checkout_progress_cb( const char *path, size_t cur, size_t tot, void *payload )
|
||||
{
|
||||
wxLogTrace( traceGit, wxS( "checkout_progress_cb: %s %zu/%zu" ), path, cur, tot );
|
||||
}
|
||||
|
||||
|
||||
static int checkout_notify_cb( git_checkout_notify_t why, const char *path,
|
||||
const git_diff_file *baseline,
|
||||
const git_diff_file *target,
|
||||
const git_diff_file *workdir, void *payload )
|
||||
{
|
||||
GIT_REVERT_HANDLER* handler = static_cast<GIT_REVERT_HANDLER*>(payload);
|
||||
|
||||
if( why & ( GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_IGNORED
|
||||
| GIT_CHECKOUT_NOTIFY_UPDATED ) )
|
||||
handler->PushFailedFile( path );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void GIT_REVERT_HANDLER::PerformRevert()
|
||||
{
|
||||
git_object* head_commit = NULL;
|
||||
git_checkout_options opts;
|
||||
git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
|
||||
|
||||
// Get the HEAD commit
|
||||
if( git_revparse_single( &head_commit, m_repository, "HEAD" ) != 0 )
|
||||
{
|
||||
// Handle error. If we cannot get the HEAD, then there's no point proceeding.
|
||||
return;
|
||||
}
|
||||
|
||||
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
|
||||
char** paths = new char*[m_filesToRevert.size()];
|
||||
|
||||
for( size_t ii = 0; ii < m_filesToRevert.size(); ii++ )
|
||||
{
|
||||
// Set paths to the specific file
|
||||
paths[ii] = wxStrdup( m_filesToRevert[ii].ToUTF8() );
|
||||
}
|
||||
|
||||
git_strarray arr = { paths, m_filesToRevert.size() };
|
||||
|
||||
opts.paths = arr;
|
||||
opts.progress_cb = checkout_progress_cb;
|
||||
opts.notify_cb = checkout_notify_cb;
|
||||
opts.notify_payload = static_cast<void*>( this );
|
||||
|
||||
// Attempt to checkout the file(s)
|
||||
if( git_checkout_tree(m_repository, head_commit, &opts ) != 0 )
|
||||
{
|
||||
const git_error *e = git_error_last();
|
||||
|
||||
if( e )
|
||||
{
|
||||
wxLogTrace( traceGit, wxS( "Checkout failed: %d: %s" ), e->klass, e->message );
|
||||
}
|
||||
}
|
||||
|
||||
// Free the HEAD commit
|
||||
for( size_t ii = 0; ii < m_filesToRevert.size(); ii++ )
|
||||
delete( paths[ii] );
|
||||
|
||||
delete[] paths;
|
||||
|
||||
git_object_free( head_commit );
|
||||
GetGitBackend()->PerformRevert( this );
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,10 @@
|
||||
#include <git2.h>
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
// TEMPORARY HACKFIX INCLUDE FOR STD::VECTOR EXPORT OUT OF KICOMMON ON WINDOWS
|
||||
#include <settings/parameters.h>
|
||||
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
class GIT_REVERT_HANDLER
|
||||
{
|
||||
@ -44,6 +48,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
friend class LIBGIT_BACKEND;
|
||||
git_repository* m_repository;
|
||||
|
||||
std::vector<wxString> m_filesToRevert;
|
||||
|
@ -22,10 +22,8 @@
|
||||
*/
|
||||
|
||||
#include "git_status_handler.h"
|
||||
#include <git/kicad_git_common.h>
|
||||
#include <git/kicad_git_memory.h>
|
||||
#include <trace_helpers.h>
|
||||
#include <wx/log.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
GIT_STATUS_HANDLER::GIT_STATUS_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
|
||||
{}
|
||||
@ -35,171 +33,29 @@ GIT_STATUS_HANDLER::~GIT_STATUS_HANDLER()
|
||||
|
||||
bool GIT_STATUS_HANDLER::HasChangedFiles()
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return false;
|
||||
|
||||
git_status_options opts;
|
||||
git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
|
||||
|
||||
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
|
||||
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
|
||||
| GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
|
||||
|
||||
git_status_list* status_list = nullptr;
|
||||
|
||||
if( git_status_list_new( &status_list, repo, &opts ) != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
KIGIT::GitStatusListPtr status_list_ptr( status_list );
|
||||
bool hasChanges = ( git_status_list_entrycount( status_list ) > 0 );
|
||||
|
||||
return hasChanges;
|
||||
return GetGitBackend()->HasChangedFiles( this );
|
||||
}
|
||||
|
||||
std::map<wxString, FileStatus> GIT_STATUS_HANDLER::GetFileStatus( const wxString& aPathspec )
|
||||
{
|
||||
std::map<wxString, FileStatus> fileStatusMap;
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return fileStatusMap;
|
||||
|
||||
git_status_options status_options;
|
||||
git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
|
||||
status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
|
||||
status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
|
||||
|
||||
// Set up pathspec if provided
|
||||
std::string pathspec_str;
|
||||
std::vector<const char*> pathspec_ptrs;
|
||||
|
||||
if( !aPathspec.IsEmpty() )
|
||||
{
|
||||
pathspec_str = aPathspec.ToStdString();
|
||||
pathspec_ptrs.push_back( pathspec_str.c_str() );
|
||||
|
||||
status_options.pathspec.strings = const_cast<char**>( pathspec_ptrs.data() );
|
||||
status_options.pathspec.count = pathspec_ptrs.size();
|
||||
}
|
||||
|
||||
git_status_list* status_list = nullptr;
|
||||
|
||||
if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to get git status list: %s", KIGIT_COMMON::GetLastGitError() );
|
||||
return fileStatusMap;
|
||||
}
|
||||
|
||||
KIGIT::GitStatusListPtr statusListPtr( status_list );
|
||||
|
||||
size_t count = git_status_list_entrycount( status_list );
|
||||
wxString repoWorkDir( git_repository_workdir( repo ) );
|
||||
|
||||
for( size_t ii = 0; ii < count; ++ii )
|
||||
{
|
||||
const git_status_entry* entry = git_status_byindex( status_list, ii );
|
||||
std::string path( entry->head_to_index ? entry->head_to_index->old_file.path
|
||||
: entry->index_to_workdir->old_file.path );
|
||||
|
||||
wxString absPath = repoWorkDir + path;
|
||||
|
||||
FileStatus fileStatus;
|
||||
fileStatus.filePath = absPath;
|
||||
fileStatus.gitStatus = entry->status;
|
||||
fileStatus.status = ConvertStatus( entry->status );
|
||||
|
||||
fileStatusMap[absPath] = fileStatus;
|
||||
}
|
||||
|
||||
return fileStatusMap;
|
||||
return GetGitBackend()->GetFileStatus( this, aPathspec );
|
||||
}
|
||||
|
||||
wxString GIT_STATUS_HANDLER::GetCurrentBranchName()
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return wxEmptyString;
|
||||
|
||||
git_reference* currentBranchReference = nullptr;
|
||||
int rc = git_repository_head( ¤tBranchReference, repo );
|
||||
KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference );
|
||||
|
||||
if( currentBranchReference )
|
||||
{
|
||||
return git_reference_shorthand( currentBranchReference );
|
||||
}
|
||||
else if( rc == GIT_EUNBORNBRANCH )
|
||||
{
|
||||
// Unborn branch - could return empty or a default name
|
||||
return wxEmptyString;
|
||||
}
|
||||
else
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() );
|
||||
return wxEmptyString;
|
||||
}
|
||||
return GetGitBackend()->GetCurrentBranchName( this );
|
||||
}
|
||||
|
||||
void GIT_STATUS_HANDLER::UpdateRemoteStatus( const std::set<wxString>& aLocalChanges,
|
||||
const std::set<wxString>& aRemoteChanges,
|
||||
std::map<wxString, FileStatus>& aFileStatus )
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return;
|
||||
|
||||
wxString repoWorkDir( git_repository_workdir( repo ) );
|
||||
|
||||
// Update status based on local/remote changes
|
||||
for( auto& [absPath, fileStatus] : aFileStatus )
|
||||
{
|
||||
// Convert absolute path to relative path for comparison
|
||||
wxString relativePath = absPath;
|
||||
if( relativePath.StartsWith( repoWorkDir ) )
|
||||
{
|
||||
relativePath = relativePath.Mid( repoWorkDir.length() );
|
||||
#ifdef _WIN32
|
||||
relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string relativePathStd = relativePath.ToStdString();
|
||||
|
||||
// Only update if the file is not already modified/added/deleted
|
||||
if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT )
|
||||
{
|
||||
if( aLocalChanges.count( relativePathStd ) )
|
||||
{
|
||||
fileStatus.status = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD;
|
||||
}
|
||||
else if( aRemoteChanges.count( relativePathStd ) )
|
||||
{
|
||||
fileStatus.status = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND;
|
||||
}
|
||||
}
|
||||
}
|
||||
GetGitBackend()->UpdateRemoteStatus( this, aLocalChanges, aRemoteChanges, aFileStatus );
|
||||
}
|
||||
|
||||
wxString GIT_STATUS_HANDLER::GetWorkingDirectory()
|
||||
{
|
||||
git_repository* repo = GetRepo();
|
||||
|
||||
if( !repo )
|
||||
return wxEmptyString;
|
||||
|
||||
const char* workdir = git_repository_workdir( repo );
|
||||
|
||||
if( !workdir )
|
||||
return wxEmptyString;
|
||||
|
||||
return wxString( workdir );
|
||||
return GetGitBackend()->GetWorkingDirectory( this );
|
||||
}
|
||||
|
||||
KIGIT_COMMON::GIT_STATUS GIT_STATUS_HANDLER::ConvertStatus( unsigned int aGitStatus )
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
struct FileStatus
|
||||
{
|
||||
wxString filePath;
|
||||
@ -80,6 +82,7 @@ public:
|
||||
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
|
||||
|
||||
private:
|
||||
friend class LIBGIT_BACKEND;
|
||||
/**
|
||||
* Convert git status flags to KIGIT_COMMON::GIT_STATUS
|
||||
* @param aGitStatus Raw git status flags
|
||||
|
@ -33,6 +33,8 @@
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
class LIBGIT_BACKEND;
|
||||
|
||||
class KIGIT_COMMON
|
||||
{
|
||||
|
||||
@ -174,6 +176,7 @@ protected:
|
||||
friend class GIT_PUSH_HANDLER;
|
||||
friend class GIT_PULL_HANDLER;
|
||||
friend class GIT_CLONE_HANDLER;
|
||||
friend class LIBGIT_BACKEND;
|
||||
friend class PROJECT_TREE_PANE;
|
||||
|
||||
private:
|
||||
|
1358
common/git/libgit_backend.cpp
Normal file
1358
common/git/libgit_backend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
100
common/git/libgit_backend.h
Normal file
100
common/git/libgit_backend.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.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 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, you may find one here:
|
||||
* http://www.gnu.org/licenses/gpl-3.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 3 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#ifndef LIBGIT_BACKEND_H_
|
||||
#define LIBGIT_BACKEND_H_
|
||||
|
||||
#include "git_backend.h"
|
||||
|
||||
// Forward declarations to avoid exposing libgit2 headers
|
||||
struct git_annotated_commit;
|
||||
|
||||
class LIBGIT_BACKEND : public GIT_BACKEND
|
||||
{
|
||||
public:
|
||||
void Init() override;
|
||||
void Shutdown() override;
|
||||
bool IsLibraryAvailable() override;
|
||||
|
||||
bool Clone( GIT_CLONE_HANDLER* aHandler ) override;
|
||||
|
||||
CommitResult Commit( GIT_COMMIT_HANDLER* aHandler,
|
||||
const std::vector<wxString>& aFiles,
|
||||
const wxString& aMessage,
|
||||
const wxString& aAuthorName,
|
||||
const wxString& aAuthorEmail ) override;
|
||||
|
||||
PushResult Push( GIT_PUSH_HANDLER* aHandler ) override;
|
||||
|
||||
bool HasChangedFiles( GIT_STATUS_HANDLER* aHandler ) override;
|
||||
|
||||
std::map<wxString, FileStatus> GetFileStatus( GIT_STATUS_HANDLER* aHandler,
|
||||
const wxString& aPathspec ) override;
|
||||
|
||||
wxString GetCurrentBranchName( GIT_STATUS_HANDLER* aHandler ) override;
|
||||
|
||||
void UpdateRemoteStatus( GIT_STATUS_HANDLER* aHandler,
|
||||
const std::set<wxString>& aLocalChanges,
|
||||
const std::set<wxString>& aRemoteChanges,
|
||||
std::map<wxString, FileStatus>& aFileStatus ) override;
|
||||
|
||||
wxString GetWorkingDirectory( GIT_STATUS_HANDLER* aHandler ) override;
|
||||
|
||||
wxString GetWorkingDirectory( GIT_CONFIG_HANDLER* aHandler ) override;
|
||||
bool GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey,
|
||||
wxString& aValue ) override;
|
||||
|
||||
bool IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) override;
|
||||
InitResult InitializeRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) override;
|
||||
bool SetupRemote( GIT_INIT_HANDLER* aHandler, const RemoteConfig& aConfig ) override;
|
||||
|
||||
BranchResult SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) override;
|
||||
bool BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) override;
|
||||
|
||||
bool PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock ) override;
|
||||
PullResult PerformPull( GIT_PULL_HANDLER* aHandler ) override;
|
||||
|
||||
void PerformRevert( GIT_REVERT_HANDLER* aHandler ) override;
|
||||
|
||||
git_repository* GetRepositoryForFile( const char* aFilename ) override;
|
||||
int CreateBranch( git_repository* aRepo, const wxString& aBranchName ) override;
|
||||
bool RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
|
||||
bool aRemoveGitDir, wxString* aErrors ) override;
|
||||
|
||||
bool AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath ) override;
|
||||
|
||||
bool PerformAddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler ) override;
|
||||
|
||||
bool RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, const wxString& aFilePath ) override;
|
||||
|
||||
void PerformRemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler ) override;
|
||||
|
||||
private:
|
||||
PullResult handleFastForward( GIT_PULL_HANDLER* aHandler );
|
||||
PullResult handleMerge( GIT_PULL_HANDLER* aHandler, const git_annotated_commit** aMergeHeads,
|
||||
size_t aMergeHeadsCount );
|
||||
PullResult handleRebase( GIT_PULL_HANDLER* aHandler, const git_annotated_commit** aMergeHeads,
|
||||
size_t aMergeHeadsCount );
|
||||
};
|
||||
|
||||
#endif
|
@ -22,105 +22,25 @@
|
||||
*/
|
||||
|
||||
#include "project_git_utils.h"
|
||||
#include "kicad_git_common.h"
|
||||
#include "kicad_git_memory.h"
|
||||
#include <git/kicad_git_compat.h>
|
||||
#include <trace_helpers.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/filename.h>
|
||||
#include <gestfich.h>
|
||||
#include "git_backend.h"
|
||||
|
||||
namespace KIGIT
|
||||
{
|
||||
|
||||
git_repository* PROJECT_GIT_UTILS::GetRepositoryForFile( const char* aFilename )
|
||||
{
|
||||
git_repository* repo = nullptr;
|
||||
git_buf repo_path = GIT_BUF_INIT;
|
||||
|
||||
if( git_repository_discover( &repo_path, aFilename, 0, nullptr ) != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Can't repo discover %s: %s", aFilename,
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KIGIT::GitBufPtr repo_path_ptr( &repo_path );
|
||||
|
||||
if( git_repository_open( &repo, repo_path.ptr ) != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Can't open repo for %s: %s", repo_path.ptr,
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return repo;
|
||||
return GetGitBackend()->GetRepositoryForFile( aFilename );
|
||||
}
|
||||
|
||||
int PROJECT_GIT_UTILS::CreateBranch( git_repository* aRepo, const wxString& aBranchName )
|
||||
{
|
||||
git_oid head_oid;
|
||||
|
||||
if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ); error != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to lookup HEAD reference: %s",
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return error;
|
||||
}
|
||||
|
||||
git_commit* commit = nullptr;
|
||||
|
||||
if( int error = git_commit_lookup( &commit, aRepo, &head_oid ); error != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to lookup commit: %s",
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return error;
|
||||
}
|
||||
|
||||
KIGIT::GitCommitPtr commitPtr( commit );
|
||||
git_reference* branchRef = nullptr;
|
||||
|
||||
if( int error = git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ); error != GIT_OK )
|
||||
{
|
||||
wxLogTrace( traceGit, "Failed to create branch: %s",
|
||||
KIGIT_COMMON::GetLastGitError() );
|
||||
return error;
|
||||
}
|
||||
|
||||
git_reference_free( branchRef );
|
||||
return 0;
|
||||
return GetGitBackend()->CreateBranch( aRepo, aBranchName );
|
||||
}
|
||||
|
||||
bool PROJECT_GIT_UTILS::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
|
||||
bool aRemoveGitDir, wxString* aErrors )
|
||||
{
|
||||
if( aRepo )
|
||||
{
|
||||
git_repository_free( aRepo );
|
||||
aRepo = nullptr;
|
||||
}
|
||||
|
||||
if( aRemoveGitDir )
|
||||
{
|
||||
wxFileName gitDir( aProjectPath, wxEmptyString );
|
||||
gitDir.AppendDir( ".git" );
|
||||
|
||||
if( gitDir.DirExists() )
|
||||
{
|
||||
wxString errors;
|
||||
if( !RmDirRecursive( gitDir.GetPath(), &errors ) )
|
||||
{
|
||||
if( aErrors )
|
||||
*aErrors = errors;
|
||||
|
||||
wxLogTrace( traceGit, "Failed to remove .git directory: %s", errors );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wxLogTrace( traceGit, "Successfully removed VCS from project" );
|
||||
return true;
|
||||
return GetGitBackend()->RemoveVCS( aRepo, aProjectPath, aRemoveGitDir, aErrors );
|
||||
}
|
||||
|
||||
} // namespace KIGIT
|
@ -29,6 +29,7 @@
|
||||
#include <trigo.h>
|
||||
#include <math/util.h> // for KiROUND
|
||||
#include <font/font.h>
|
||||
#include <text_eval/text_eval_wrapper.h>
|
||||
|
||||
#include <callback_gal.h>
|
||||
|
||||
@ -100,8 +101,15 @@ int GRTextWidth( const wxString& aText, KIFONT::FONT* aFont, const VECTOR2I& aSi
|
||||
{
|
||||
if( !aFont )
|
||||
aFont = KIFONT::FONT::GetFont();
|
||||
wxString evaluated( aText );
|
||||
|
||||
return KiROUND( aFont->StringBoundaryLimits( aText, aSize, aThickness, aBold, aItalic,
|
||||
if( evaluated.Contains( wxS( "@{" ) ) )
|
||||
{
|
||||
EXPRESSION_EVALUATOR evaluator;
|
||||
evaluated = evaluator.Evaluate( evaluated );
|
||||
}
|
||||
|
||||
return KiROUND( aFont->StringBoundaryLimits( evaluated, aSize, aThickness, aBold, aItalic,
|
||||
aFontMetrics ).x );
|
||||
}
|
||||
|
||||
@ -114,6 +122,13 @@ void GRPrintText( wxDC* aDC, const VECTOR2I& aPos, const COLOR4D& aColor, const
|
||||
{
|
||||
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
|
||||
bool fill_mode = true;
|
||||
wxString evaluatedText( aText );
|
||||
|
||||
if( evaluatedText.Contains( wxS( "@{" ) ) )
|
||||
{
|
||||
EXPRESSION_EVALUATOR evaluator;
|
||||
evaluatedText = evaluator.Evaluate( evaluatedText );
|
||||
}
|
||||
|
||||
if( !aFont )
|
||||
aFont = KIFONT::FONT::GetFont();
|
||||
@ -156,7 +171,7 @@ void GRPrintText( wxDC* aDC, const VECTOR2I& aPos, const COLOR4D& aColor, const
|
||||
attributes.m_Valign = aV_justify;
|
||||
attributes.m_Size = aSize;
|
||||
|
||||
aFont->Draw( &callback_gal, aText, aPos, attributes, aFontMetrics );
|
||||
aFont->Draw( &callback_gal, evaluatedText, aPos, attributes, aFontMetrics );
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include <wx/txtstrm.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <tool/tool_action.h>
|
||||
#include <tool/tool_event.h>
|
||||
|
||||
|
||||
/*
|
||||
@ -159,6 +160,10 @@ static struct hotkey_name_descr hotkeyNameList[] =
|
||||
#define MODIFIER_CMD_MAC wxT( "Cmd+" )
|
||||
#define MODIFIER_CTRL_BASE wxT( "Ctrl+" )
|
||||
#define MODIFIER_SHIFT wxT( "Shift+" )
|
||||
#define MODIFIER_META wxT( "Meta+" )
|
||||
#define MODIFIER_WIN wxT( "Win+" )
|
||||
#define MODIFIER_SUPER wxT( "Super+" )
|
||||
#define MODIFIER_ALTGR wxT( "AltGr+" )
|
||||
|
||||
|
||||
wxString KeyNameFromKeyCode( int aKeycode, bool* aIsFound )
|
||||
@ -175,6 +180,14 @@ wxString KeyNameFromKeyCode( int aKeycode, bool* aIsFound )
|
||||
return wxString( MODIFIER_SHIFT ).BeforeFirst( '+' );
|
||||
else if( aKeycode == WXK_ALT )
|
||||
return wxString( MODIFIER_ALT ).BeforeFirst( '+' );
|
||||
#ifdef WXK_WINDOWS_LEFT
|
||||
else if( aKeycode == WXK_WINDOWS_LEFT || aKeycode == WXK_WINDOWS_RIGHT )
|
||||
return wxString( MODIFIER_WIN ).BeforeFirst( '+' );
|
||||
#endif
|
||||
#ifdef WXK_META
|
||||
else if( aKeycode == WXK_META )
|
||||
return wxString( MODIFIER_META ).BeforeFirst( '+' );
|
||||
#endif
|
||||
|
||||
// Assume keycode of 0 is "unassigned"
|
||||
if( (aKeycode & MD_CTRL) != 0 )
|
||||
@ -186,7 +199,16 @@ wxString KeyNameFromKeyCode( int aKeycode, bool* aIsFound )
|
||||
if( (aKeycode & MD_SHIFT) != 0 )
|
||||
modifier << MODIFIER_SHIFT;
|
||||
|
||||
aKeycode &= ~( MD_CTRL | MD_ALT | MD_SHIFT );
|
||||
if( (aKeycode & MD_META) != 0 )
|
||||
modifier << MODIFIER_META;
|
||||
|
||||
if( (aKeycode & MD_SUPER) != 0 )
|
||||
modifier << MODIFIER_WIN;
|
||||
|
||||
if( (aKeycode & MD_ALTGR) != 0 )
|
||||
modifier << MODIFIER_ALTGR;
|
||||
|
||||
aKeycode &= ~MD_MODIFIER_MASK;
|
||||
|
||||
if( (aKeycode > ' ') && (aKeycode < 0x7F ) )
|
||||
{
|
||||
@ -262,7 +284,7 @@ int KeyCodeFromKeyName( const wxString& keyname )
|
||||
{
|
||||
int ii, keycode = KEY_NON_FOUND;
|
||||
|
||||
// Search for modifiers: Ctrl+ Alt+ and Shift+
|
||||
// Search for modifiers: Ctrl+ Alt+ Shift+ and others
|
||||
// Note: on Mac OSX, the Cmd key is equiv here to Ctrl
|
||||
wxString key = keyname;
|
||||
wxString prefix;
|
||||
@ -292,6 +314,26 @@ int KeyCodeFromKeyName( const wxString& keyname )
|
||||
modifier |= MD_SHIFT;
|
||||
prefix = MODIFIER_SHIFT;
|
||||
}
|
||||
else if( key.StartsWith( MODIFIER_META ) )
|
||||
{
|
||||
modifier |= MD_META;
|
||||
prefix = MODIFIER_META;
|
||||
}
|
||||
else if( key.StartsWith( MODIFIER_WIN ) )
|
||||
{
|
||||
modifier |= MD_SUPER;
|
||||
prefix = MODIFIER_WIN;
|
||||
}
|
||||
else if( key.StartsWith( MODIFIER_SUPER ) )
|
||||
{
|
||||
modifier |= MD_SUPER;
|
||||
prefix = MODIFIER_SUPER;
|
||||
}
|
||||
else if( key.StartsWith( MODIFIER_ALTGR ) )
|
||||
{
|
||||
modifier |= MD_ALTGR;
|
||||
prefix = MODIFIER_ALTGR;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
|
@ -40,8 +40,7 @@ JOB_EXPORT_SCH_BOM::JOB_EXPORT_SCH_BOM() :
|
||||
m_sortField(),
|
||||
m_sortAsc( true ),
|
||||
m_filterString(),
|
||||
m_excludeDNP( false ),
|
||||
m_includeExcludedFromBOM( false )
|
||||
m_excludeDNP( false )
|
||||
{
|
||||
m_params.emplace_back( new JOB_PARAM<wxString>( "field_delimiter",
|
||||
&m_fieldDelimiter,
|
||||
@ -70,13 +69,8 @@ JOB_EXPORT_SCH_BOM::JOB_EXPORT_SCH_BOM() :
|
||||
m_fieldsGroupBy ) );
|
||||
m_params.emplace_back( new JOB_PARAM<wxString>( "sort_field", &m_sortField, m_sortField ) );
|
||||
m_params.emplace_back( new JOB_PARAM<bool>( "sort_asc", &m_sortAsc, m_sortAsc ) );
|
||||
m_params.emplace_back( new JOB_PARAM<wxString>( "filter_string",
|
||||
&m_filterString,
|
||||
m_filterString ) );
|
||||
m_params.emplace_back( new JOB_PARAM<wxString>( "filter_string", &m_filterString, m_filterString ) );
|
||||
m_params.emplace_back( new JOB_PARAM<bool>( "exclude_dnp", &m_excludeDNP, m_excludeDNP ) );
|
||||
m_params.emplace_back( new JOB_PARAM<bool>( "include_excluded_from_bom",
|
||||
&m_includeExcludedFromBOM,
|
||||
m_includeExcludedFromBOM ) );
|
||||
|
||||
m_params.emplace_back( new JOB_PARAM<wxString>( "bom_preset_name",
|
||||
&m_bomPresetName,
|
||||
|
@ -55,7 +55,6 @@ public:
|
||||
bool m_sortAsc;
|
||||
wxString m_filterString;
|
||||
bool m_excludeDNP;
|
||||
bool m_includeExcludedFromBOM;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -142,6 +142,7 @@ wxString LayerName( int aLayer )
|
||||
case LAYER_DRC_EXCLUSION: return _( "DRC exclusions" );
|
||||
case LAYER_MARKER_SHADOWS: return _( "DRC marker shadows" );
|
||||
case LAYER_ANCHOR: return _( "Anchors" );
|
||||
case LAYER_POINTS: return _( "Points" );
|
||||
case LAYER_DRAWINGSHEET: return _( "Drawing sheet" );
|
||||
case LAYER_PAGE_LIMITS: return _( "Page limits" );
|
||||
case LAYER_CURSOR: return _( "Cursor" );
|
||||
|
@ -19,11 +19,23 @@
|
||||
|
||||
#include "lib_table_grid_tricks.h"
|
||||
#include "lib_table_grid.h"
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/log.h>
|
||||
|
||||
|
||||
LIB_TABLE_GRID_TRICKS::LIB_TABLE_GRID_TRICKS( WX_GRID* aGrid ) :
|
||||
GRID_TRICKS( aGrid )
|
||||
{
|
||||
m_grid->Disconnect( wxEVT_CHAR_HOOK );
|
||||
m_grid->Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( LIB_TABLE_GRID_TRICKS::onCharHook ), nullptr, this );
|
||||
}
|
||||
|
||||
LIB_TABLE_GRID_TRICKS::LIB_TABLE_GRID_TRICKS( WX_GRID* aGrid,
|
||||
std::function<void( wxCommandEvent& )> aAddHandler ) :
|
||||
GRID_TRICKS( aGrid, aAddHandler )
|
||||
{
|
||||
m_grid->Disconnect( wxEVT_CHAR_HOOK );
|
||||
m_grid->Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( LIB_TABLE_GRID_TRICKS::onCharHook ), nullptr, this );
|
||||
}
|
||||
|
||||
|
||||
@ -134,6 +146,61 @@ void LIB_TABLE_GRID_TRICKS::doPopupSelection( wxCommandEvent& event )
|
||||
GRID_TRICKS::doPopupSelection( event );
|
||||
}
|
||||
}
|
||||
void LIB_TABLE_GRID_TRICKS::onCharHook( wxKeyEvent& ev )
|
||||
{
|
||||
if( ev.GetModifiers() == wxMOD_CONTROL && ev.GetKeyCode() == 'V' && m_grid->IsCellEditControlShown() )
|
||||
{
|
||||
wxLogNull doNotLog;
|
||||
|
||||
if( wxTheClipboard->Open() )
|
||||
{
|
||||
if( wxTheClipboard->IsSupported( wxDF_TEXT ) || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) )
|
||||
{
|
||||
wxTextDataObject data;
|
||||
wxTheClipboard->GetData( data );
|
||||
|
||||
wxString text = data.GetText();
|
||||
|
||||
if( !text.Contains( '\t' ) && text.Contains( ',' ) )
|
||||
text.Replace( ',', '\t' );
|
||||
|
||||
if( text.Contains( '\t' ) || text.Contains( '\n' ) || text.Contains( '\r' ) )
|
||||
{
|
||||
m_grid->CancelPendingChanges();
|
||||
int row = m_grid->GetGridCursorRow();
|
||||
|
||||
// Check if the current row already has data (has a nickname)
|
||||
wxGridTableBase* table = m_grid->GetTable();
|
||||
if( table && row >= 0 && row < table->GetNumberRows() )
|
||||
{
|
||||
// Check if the row has a nickname (indicating it has existing data)
|
||||
wxString nickname = table->GetValue( row, COL_NICKNAME );
|
||||
if( !nickname.IsEmpty() )
|
||||
{
|
||||
// Row already has data, don't allow pasting over it
|
||||
wxTheClipboard->Close();
|
||||
wxBell(); // Provide audio feedback
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_grid->ClearSelection();
|
||||
m_grid->SelectRow( row );
|
||||
m_grid->SetGridCursor( row, 0 );
|
||||
getSelectedArea();
|
||||
paste_text( text );
|
||||
wxTheClipboard->Close();
|
||||
m_grid->ForceRefresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
wxTheClipboard->Close();
|
||||
}
|
||||
}
|
||||
|
||||
GRID_TRICKS::onCharHook( ev );
|
||||
}
|
||||
|
||||
|
||||
bool LIB_TABLE_GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/dc.h>
|
||||
#include <wx/log.h>
|
||||
#include <string_utils.h>
|
||||
|
||||
|
||||
@ -308,6 +309,15 @@ void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool a
|
||||
|
||||
const LIB_TREE_NODE* firstMatch = ShowResults();
|
||||
|
||||
#ifdef __WXGTK__
|
||||
// Ensure the control is repainted with the updated data. Without an explicit
|
||||
// refresh the Gtk port can display stale rows until the user interacts with
|
||||
// them, leading to mismatched tree contents.
|
||||
m_widget->Refresh();
|
||||
m_widget->Update();
|
||||
wxYield();
|
||||
#endif
|
||||
|
||||
if( firstMatch )
|
||||
{
|
||||
wxDataViewItem item = ToItem( firstMatch );
|
||||
|
@ -880,7 +880,6 @@ bool COMPILER::generateUCode( UCODE* aCode, CONTEXT* aPreflightContext )
|
||||
pos -= static_cast<int>( formatNode( node->leaf[0] ).length() );
|
||||
|
||||
reportError( CST_CODEGEN, _( "Unknown parent of property" ), pos );
|
||||
return false;
|
||||
|
||||
node->leaf[0]->isVisited = true;
|
||||
node->leaf[1]->isVisited = true;
|
||||
@ -1161,7 +1160,9 @@ void UOP::Exec( CONTEXT* ctx )
|
||||
return;
|
||||
|
||||
case TR_OP_METHOD_CALL:
|
||||
if( m_func )
|
||||
m_func( ctx, m_ref.get() );
|
||||
|
||||
return;
|
||||
|
||||
default:
|
||||
@ -1322,9 +1323,8 @@ VALUE* UCODE::Run( CONTEXT* ctx )
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
// rules which fail outright should not be fired
|
||||
std::unique_ptr<VALUE> temp_false = std::make_unique<VALUE>( 0 );
|
||||
return ctx->StoreValue( temp_false.get() );
|
||||
// rules which fail outright should not be fired; return 0/false
|
||||
return ctx->StoreValue( new VALUE( 0 ) );
|
||||
}
|
||||
|
||||
if( ctx->SP() == 1 )
|
||||
@ -1340,8 +1340,7 @@ VALUE* UCODE::Run( CONTEXT* ctx )
|
||||
wxASSERT( ctx->SP() == 1 );
|
||||
|
||||
// non-well-formed rules should not be fired on a release build
|
||||
std::unique_ptr<VALUE> temp_false = std::make_unique<VALUE>( 0 );
|
||||
return ctx->StoreValue( temp_false.get() );
|
||||
return ctx->StoreValue( new VALUE( 0 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -806,7 +806,8 @@ GAL_SET GAL_SET::DefaultVisible()
|
||||
LAYER_FILLED_SHAPES,
|
||||
LAYER_LOCKED_ITEM_SHADOW,
|
||||
// LAYER_BOARD_OUTLINE_AREA, // currently hidden by default
|
||||
LAYER_CONFLICTS_SHADOW
|
||||
LAYER_CONFLICTS_SHADOW,
|
||||
LAYER_POINTS
|
||||
};
|
||||
|
||||
static const GAL_SET saved( visible, arrayDim( visible ) );
|
||||
|
@ -280,6 +280,7 @@ pintype
|
||||
placed
|
||||
placement
|
||||
plus
|
||||
point
|
||||
polygon
|
||||
portrait
|
||||
precision
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
#include <trigo.h>
|
||||
#include <plotters/plotter.h>
|
||||
#include <text_eval/text_eval_wrapper.h>
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <bezier_curves.h>
|
||||
#include <callback_gal.h>
|
||||
@ -637,6 +638,13 @@ void PLOTTER::Text( const VECTOR2I& aPos,
|
||||
void* aData )
|
||||
{
|
||||
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
|
||||
wxString text( aText );
|
||||
|
||||
if( text.Contains( wxS( "@{" ) ) )
|
||||
{
|
||||
EXPRESSION_EVALUATOR evaluator;
|
||||
text = evaluator.Evaluate( text );
|
||||
}
|
||||
|
||||
SetColor( aColor );
|
||||
|
||||
@ -680,7 +688,7 @@ void PLOTTER::Text( const VECTOR2I& aPos,
|
||||
if( !aFont )
|
||||
aFont = KIFONT::FONT::GetFont( m_renderSettings->GetDefaultFont() );
|
||||
|
||||
aFont->Draw( &callback_gal, aText, aPos, attributes, aFontMetrics );
|
||||
aFont->Draw( &callback_gal, text, aPos, attributes, aFontMetrics );
|
||||
}
|
||||
|
||||
|
||||
@ -693,6 +701,13 @@ void PLOTTER::PlotText( const VECTOR2I& aPos,
|
||||
void* aData )
|
||||
{
|
||||
KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
|
||||
wxString text( aText );
|
||||
|
||||
if( text.Contains( wxS( "@{" ) ) )
|
||||
{
|
||||
EXPRESSION_EVALUATOR evaluator;
|
||||
text = evaluator.Evaluate( text );
|
||||
}
|
||||
|
||||
TEXT_ATTRIBUTES attributes = aAttributes;
|
||||
int penWidth = attributes.m_StrokeWidth;
|
||||
@ -725,5 +740,5 @@ void PLOTTER::PlotText( const VECTOR2I& aPos,
|
||||
if( !aFont )
|
||||
aFont = KIFONT::FONT::GetFont( m_renderSettings->GetDefaultFont() );
|
||||
|
||||
aFont->Draw( &callback_gal, aText, aPos, attributes, aFontMetrics );
|
||||
aFont->Draw( &callback_gal, text, aPos, attributes, aFontMetrics );
|
||||
}
|
||||
|
208
common/preview_items/angle_item.cpp
Normal file
208
common/preview_items/angle_item.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* This program source code file is part of KICAD, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2024 The KiCad Developers, see AUTHORS.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 <preview_items/angle_item.h>
|
||||
#include <tool/edit_points.h>
|
||||
#include <gal/graphics_abstraction_layer.h>
|
||||
#include <gal/painter.h>
|
||||
#include <view/view.h>
|
||||
#include <geometry/seg.h>
|
||||
#include <preview_items/preview_utils.h>
|
||||
#include <font/font.h>
|
||||
#include <wx/string.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
using namespace KIGFX::PREVIEW;
|
||||
|
||||
ANGLE_ITEM::ANGLE_ITEM( EDIT_POINTS* aPoints ) :
|
||||
SIMPLE_OVERLAY_ITEM(),
|
||||
m_points( aPoints )
|
||||
{
|
||||
}
|
||||
|
||||
const BOX2I ANGLE_ITEM::ViewBBox() const
|
||||
{
|
||||
if( m_points )
|
||||
return m_points->ViewBBox();
|
||||
else
|
||||
return BOX2I();
|
||||
}
|
||||
|
||||
void ANGLE_ITEM::drawPreviewShape( KIGFX::VIEW* aView ) const
|
||||
{
|
||||
if( !m_points )
|
||||
return;
|
||||
|
||||
KIGFX::GAL* gal = aView->GetGAL();
|
||||
KIGFX::RENDER_SETTINGS* settings = aView->GetPainter()->GetSettings();
|
||||
KIGFX::COLOR4D drawColor = settings->GetLayerColor( LAYER_AUX_ITEMS );
|
||||
|
||||
double size = aView->ToWorld( EDIT_POINT::POINT_SIZE ) / 2.0;
|
||||
double borderSize = aView->ToWorld( EDIT_POINT::BORDER_SIZE );
|
||||
double radius = size * 10.0;
|
||||
|
||||
gal->SetStrokeColor( drawColor );
|
||||
gal->SetFillColor( drawColor );
|
||||
gal->SetIsFill( false );
|
||||
gal->SetLineWidth( borderSize * 2.0 );
|
||||
gal->SetGlyphSize( VECTOR2I( radius / 2, radius / 2 ) );
|
||||
|
||||
std::vector<const EDIT_POINT*> anglePoints;
|
||||
|
||||
for( unsigned i = 0; i < m_points->PointsSize(); ++i )
|
||||
{
|
||||
const EDIT_POINT& point = m_points->Point( i );
|
||||
|
||||
if( point.IsActive() || point.IsHover() )
|
||||
{
|
||||
anglePoints.push_back( &point );
|
||||
|
||||
EDIT_POINT* prev = m_points->Previous( point );
|
||||
EDIT_POINT* next = m_points->Next( point );
|
||||
|
||||
if( prev )
|
||||
anglePoints.push_back( prev );
|
||||
|
||||
if( next )
|
||||
anglePoints.push_back( next );
|
||||
}
|
||||
}
|
||||
|
||||
std::sort( anglePoints.begin(), anglePoints.end() );
|
||||
anglePoints.erase( std::unique( anglePoints.begin(), anglePoints.end() ), anglePoints.end() );
|
||||
|
||||
// First pass: collect all angles and identify congruent ones
|
||||
struct AngleInfo
|
||||
{
|
||||
const EDIT_POINT* point;
|
||||
EDA_ANGLE angle;
|
||||
VECTOR2D center;
|
||||
VECTOR2D v1, v2;
|
||||
EDA_ANGLE start;
|
||||
EDA_ANGLE sweep;
|
||||
EDA_ANGLE mid;
|
||||
bool isRightAngle;
|
||||
};
|
||||
|
||||
std::vector<AngleInfo> angles;
|
||||
std::map<int, int> angleCount; // Map angle (in tenths of degree) to count
|
||||
|
||||
for( const EDIT_POINT* pt : anglePoints )
|
||||
{
|
||||
EDIT_POINT* prev = m_points->Previous( *pt );
|
||||
EDIT_POINT* next = m_points->Next( *pt );
|
||||
|
||||
if( !( prev && next ) )
|
||||
continue;
|
||||
|
||||
SEG seg1( pt->GetPosition(), prev->GetPosition() );
|
||||
SEG seg2( pt->GetPosition(), next->GetPosition() );
|
||||
|
||||
// Calculate the interior angle (0-180 degrees) instead of the smallest angle
|
||||
VECTOR2I c = pt->GetPosition();
|
||||
VECTOR2I v1 = prev->GetPosition() - c;
|
||||
VECTOR2I v2 = next->GetPosition() - c;
|
||||
EDA_ANGLE angle = seg2.Angle( seg1 );
|
||||
|
||||
EDA_ANGLE start = EDA_ANGLE( VECTOR2D( v1 ) );
|
||||
EDA_ANGLE sweep = ( EDA_ANGLE( VECTOR2D( v2 ) ) - start ).Normalize180();
|
||||
EDA_ANGLE mid = start + sweep / 2.0;
|
||||
VECTOR2D center( c );
|
||||
|
||||
AngleInfo info;
|
||||
info.point = pt;
|
||||
info.angle = angle;
|
||||
info.center = center;
|
||||
info.v1 = v1;
|
||||
info.v2 = v2;
|
||||
info.start = start;
|
||||
info.sweep = sweep;
|
||||
info.mid = mid;
|
||||
info.isRightAngle = ( angle.AsTenthsOfADegree() == 900 );
|
||||
|
||||
angles.push_back( info );
|
||||
angleCount[angle.AsTenthsOfADegree()]++;
|
||||
}
|
||||
|
||||
// Second pass: draw the angles with congruence markings
|
||||
for( const AngleInfo& angleInfo : angles )
|
||||
{
|
||||
bool isCongruent = angleCount[angleInfo.angle.AsTenthsOfADegree()] > 1;
|
||||
|
||||
if( angleInfo.isRightAngle )
|
||||
{
|
||||
VECTOR2D u1 = VECTOR2D( angleInfo.v1 ).Resize( radius );
|
||||
VECTOR2D u2 = VECTOR2D( angleInfo.v2 ).Resize( radius );
|
||||
VECTOR2D p1 = angleInfo.center + u1;
|
||||
VECTOR2D p2 = angleInfo.center + u2;
|
||||
VECTOR2D corner = angleInfo.center + u1 + u2;
|
||||
|
||||
// Draw the primary right angle marker
|
||||
gal->DrawLine( p1, corner );
|
||||
gal->DrawLine( p2, corner );
|
||||
|
||||
// Draw congruence marking for right angles
|
||||
if( isCongruent )
|
||||
{
|
||||
double innerRadius = radius * 0.6;
|
||||
VECTOR2D u1_inner = VECTOR2D( angleInfo.v1 ).Resize( innerRadius );
|
||||
VECTOR2D u2_inner = VECTOR2D( angleInfo.v2 ).Resize( innerRadius );
|
||||
VECTOR2D p1_inner = angleInfo.center + u1_inner;
|
||||
VECTOR2D p2_inner = angleInfo.center + u2_inner;
|
||||
VECTOR2D corner_inner = angleInfo.center + u1_inner + u2_inner;
|
||||
|
||||
gal->DrawLine( p1_inner, corner_inner );
|
||||
gal->DrawLine( p2_inner, corner_inner );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw the primary arc
|
||||
gal->DrawArc( angleInfo.center, radius, angleInfo.start, angleInfo.sweep );
|
||||
|
||||
// Draw congruence marking for non-right angles
|
||||
if( isCongruent )
|
||||
{
|
||||
double innerRadius = radius * 0.7;
|
||||
gal->DrawArc( angleInfo.center, innerRadius, angleInfo.start, angleInfo.sweep );
|
||||
}
|
||||
}
|
||||
|
||||
VECTOR2D textDir( angleInfo.mid.Cos(), angleInfo.mid.Sin() );
|
||||
wxString label = wxString::Format( wxT( "%.1f°" ), angleInfo.angle.AsDegrees() );
|
||||
|
||||
// Calculate actual text dimensions to ensure proper clearance
|
||||
KIFONT::FONT* font = KIFONT::FONT::GetFont();
|
||||
VECTOR2I textSize = font->StringBoundaryLimits( label, gal->GetGlyphSize(), 0, false, false,
|
||||
KIFONT::METRICS::Default() );
|
||||
|
||||
// Calculate offset based on text direction - use width for horizontal, height for vertical
|
||||
double absX = std::abs( textDir.x );
|
||||
double absY = std::abs( textDir.y );
|
||||
double textClearance = ( absX * textSize.x + absY * textSize.y ) / 2.0;
|
||||
double textOffset = radius + borderSize + textClearance;
|
||||
VECTOR2I textPos = angleInfo.center + VECTOR2I( textDir * textOffset );
|
||||
gal->BitmapText( label, textPos, ANGLE_HORIZONTAL );
|
||||
}
|
||||
}
|
@ -24,6 +24,16 @@
|
||||
#include <properties/pg_properties.h>
|
||||
#include <widgets/color_swatch.h>
|
||||
#include <widgets/unit_binder.h>
|
||||
#include <bitmaps.h>
|
||||
#include <frame_type.h>
|
||||
#include <kiway_player.h>
|
||||
#include <kiway.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/intl.h>
|
||||
#include <eda_doc.h>
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
@ -31,6 +41,8 @@ const wxString PG_UNIT_EDITOR::EDITOR_NAME = wxS( "KiCadUnitEditor" );
|
||||
const wxString PG_CHECKBOX_EDITOR::EDITOR_NAME = wxS( "KiCadCheckboxEditor" );
|
||||
const wxString PG_COLOR_EDITOR::EDITOR_NAME = wxS( "KiCadColorEditor" );
|
||||
const wxString PG_RATIO_EDITOR::EDITOR_NAME = wxS( "KiCadRatioEditor" );
|
||||
const wxString PG_FPID_EDITOR::EDITOR_NAME = wxS( "KiCadFpidEditor" );
|
||||
const wxString PG_URL_EDITOR::EDITOR_NAME = wxS( "KiCadUrlEditor" );
|
||||
|
||||
|
||||
PG_UNIT_EDITOR::PG_UNIT_EDITOR( EDA_DRAW_FRAME* aFrame ) :
|
||||
@ -51,6 +63,9 @@ PG_UNIT_EDITOR::~PG_UNIT_EDITOR()
|
||||
|
||||
wxString PG_UNIT_EDITOR::BuildEditorName( EDA_DRAW_FRAME* aFrame )
|
||||
{
|
||||
if( !aFrame )
|
||||
return EDITOR_NAME + "NoFrame";
|
||||
|
||||
return EDITOR_NAME + aFrame->GetName();
|
||||
}
|
||||
|
||||
@ -458,3 +473,164 @@ void PG_RATIO_EDITOR::UpdateControl( wxPGProperty* aProperty, wxWindow* aCtrl )
|
||||
"properties!" ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PG_FPID_EDITOR::PG_FPID_EDITOR( EDA_DRAW_FRAME* aFrame ) : m_frame( aFrame )
|
||||
{
|
||||
m_editorName = BuildEditorName( aFrame );
|
||||
}
|
||||
|
||||
|
||||
void PG_FPID_EDITOR::UpdateFrame( EDA_DRAW_FRAME* aFrame )
|
||||
{
|
||||
m_frame = aFrame;
|
||||
m_editorName = BuildEditorName( aFrame );
|
||||
}
|
||||
|
||||
|
||||
wxString PG_FPID_EDITOR::BuildEditorName( EDA_DRAW_FRAME* aFrame )
|
||||
{
|
||||
if( !aFrame )
|
||||
return EDITOR_NAME + "NoFrame";
|
||||
|
||||
return EDITOR_NAME + aFrame->GetName();
|
||||
}
|
||||
|
||||
|
||||
wxPGWindowList PG_FPID_EDITOR::CreateControls( wxPropertyGrid* aGrid, wxPGProperty* aProperty,
|
||||
const wxPoint& aPos, const wxSize& aSize ) const
|
||||
{
|
||||
wxPGMultiButton* buttons = new wxPGMultiButton( aGrid, aSize );
|
||||
buttons->Add( KiBitmap( BITMAPS::small_library ) );
|
||||
buttons->Finalize( aGrid, aPos );
|
||||
wxSize textSize = buttons->GetPrimarySize();
|
||||
wxWindow* textCtrl = aGrid->GenerateEditorTextCtrl( aPos, textSize,
|
||||
aProperty->GetValueAsString(), nullptr, 0,
|
||||
aProperty->GetMaxLength() );
|
||||
wxPGWindowList ret( textCtrl, buttons );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool PG_FPID_EDITOR::OnEvent( wxPropertyGrid* aGrid, wxPGProperty* aProperty, wxWindow* aCtrl,
|
||||
wxEvent& aEvent ) const
|
||||
{
|
||||
if( aEvent.GetEventType() == wxEVT_BUTTON )
|
||||
{
|
||||
wxString fpid = aProperty->GetValue().GetString();
|
||||
|
||||
if( KIWAY_PLAYER* frame = m_frame->Kiway().Player( FRAME_FOOTPRINT_CHOOSER, true, m_frame ) )
|
||||
{
|
||||
if( frame->ShowModal( &fpid, m_frame ) )
|
||||
aGrid->ChangePropertyValue( aProperty, fpid );
|
||||
|
||||
frame->Destroy();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return wxPGTextCtrlEditor::OnEvent( aGrid, aProperty, aCtrl, aEvent );
|
||||
}
|
||||
|
||||
|
||||
PG_URL_EDITOR::PG_URL_EDITOR( EDA_DRAW_FRAME* aFrame ) : m_frame( aFrame )
|
||||
{
|
||||
m_editorName = BuildEditorName( aFrame );
|
||||
}
|
||||
|
||||
|
||||
void PG_URL_EDITOR::UpdateFrame( EDA_DRAW_FRAME* aFrame )
|
||||
{
|
||||
m_frame = aFrame;
|
||||
m_editorName = BuildEditorName( aFrame );
|
||||
}
|
||||
|
||||
|
||||
wxString PG_URL_EDITOR::BuildEditorName( EDA_DRAW_FRAME* aFrame )
|
||||
{
|
||||
if( !aFrame )
|
||||
return EDITOR_NAME + "NoFrame";
|
||||
|
||||
return EDITOR_NAME + aFrame->GetName();
|
||||
}
|
||||
|
||||
|
||||
wxPGWindowList PG_URL_EDITOR::CreateControls( wxPropertyGrid* aGrid, wxPGProperty* aProperty,
|
||||
const wxPoint& aPos, const wxSize& aSize ) const
|
||||
{
|
||||
wxPGMultiButton* buttons = new wxPGMultiButton( aGrid, aSize );
|
||||
// Use a folder icon when no datasheet is set; otherwise use a globe icon.
|
||||
wxString urlValue = aProperty->GetValueAsString();
|
||||
bool hasUrl = !( urlValue.IsEmpty() || urlValue == wxS( "~" ) );
|
||||
buttons->Add( KiBitmap( hasUrl ? BITMAPS::www : BITMAPS::small_folder ) );
|
||||
buttons->Finalize( aGrid, aPos );
|
||||
wxSize textSize = buttons->GetPrimarySize();
|
||||
wxWindow* textCtrl = aGrid->GenerateEditorTextCtrl( aPos, textSize,
|
||||
aProperty->GetValueAsString(), nullptr, 0,
|
||||
aProperty->GetMaxLength() );
|
||||
wxPGWindowList ret( textCtrl, buttons );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool PG_URL_EDITOR::OnEvent( wxPropertyGrid* aGrid, wxPGProperty* aProperty, wxWindow* aCtrl,
|
||||
wxEvent& aEvent ) const
|
||||
{
|
||||
if( aEvent.GetEventType() == wxEVT_BUTTON )
|
||||
{
|
||||
wxString filename = aProperty->GetValue().GetString();
|
||||
|
||||
if( filename.IsEmpty() || filename == wxS( "~" ) )
|
||||
{
|
||||
wxFileDialog openFileDialog( m_frame, _( "Open file" ), wxS( "" ), wxS( "" ),
|
||||
_( "All Files" ) + wxS( " (*.*)|*.*" ),
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST );
|
||||
|
||||
if( openFileDialog.ShowModal() == wxID_OK )
|
||||
{
|
||||
filename = openFileDialog.GetPath();
|
||||
aGrid->ChangePropertyValue( aProperty, wxString::Format( wxS( "file://%s" ),
|
||||
filename ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetAssociatedDocument( m_frame, filename, &m_frame->Prj() );
|
||||
}
|
||||
|
||||
// Update the button icon to reflect presence/absence of URL
|
||||
if( wxObject* src = aEvent.GetEventObject() )
|
||||
{
|
||||
wxString newUrl = aProperty->GetValueAsString();
|
||||
bool hasUrl = !( newUrl.IsEmpty() || newUrl == wxS( "~" ) );
|
||||
auto bmp = KiBitmap( hasUrl ? BITMAPS::www : BITMAPS::small_folder );
|
||||
|
||||
if( wxWindow* win = wxDynamicCast( src, wxWindow ) )
|
||||
{
|
||||
if( wxBitmapButton* bb = wxDynamicCast( win, wxBitmapButton ) )
|
||||
{
|
||||
bb->SetBitmap( bmp );
|
||||
}
|
||||
else if( wxButton* b = wxDynamicCast( win, wxButton ) )
|
||||
{
|
||||
b->SetBitmap( bmp );
|
||||
}
|
||||
else if( wxWindow* parent = win->GetParent() )
|
||||
{
|
||||
if( wxPGMultiButton* buttons = wxDynamicCast( parent, wxPGMultiButton ) )
|
||||
{
|
||||
wxWindow* btn0 = buttons->GetButton( 0 );
|
||||
if( wxBitmapButton* bb0 = wxDynamicCast( btn0, wxBitmapButton ) )
|
||||
bb0->SetBitmap( bmp );
|
||||
else if( wxButton* b0 = wxDynamicCast( btn0, wxButton ) )
|
||||
b0->SetBitmap( bmp );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return wxPGTextCtrlEditor::OnEvent( aGrid, aProperty, aCtrl, aEvent );
|
||||
}
|
||||
|
@ -696,7 +696,9 @@ PGPROPERTY_COLOR4D::PGPROPERTY_COLOR4D( const wxString& aLabel, const wxString&
|
||||
m_backgroundColor( aBackgroundColor )
|
||||
{
|
||||
SetEditor( PG_COLOR_EDITOR::EDITOR_NAME );
|
||||
#if wxCHECK_VERSION( 3, 3, 0 )
|
||||
#if wxCHECK_VERSION( 3, 3, 1 )
|
||||
SetFlag( wxPGFlags::NoEditor );
|
||||
#elif wxCHECK_VERSION( 3, 3, 0 )
|
||||
SetFlag( wxPGPropertyFlags::NoEditor );
|
||||
#else
|
||||
SetFlag( wxPG_PROP_NOEDITOR );
|
||||
|
@ -280,6 +280,9 @@ APP_SETTINGS_BASE::APP_SETTINGS_BASE( const std::string& aFilename, int aSchemaV
|
||||
|
||||
m_params.emplace_back( new PARAM<bool>( "cross_probing.auto_highlight",
|
||||
&m_CrossProbing.auto_highlight, true ) );
|
||||
|
||||
m_params.emplace_back( new PARAM<bool>( "cross_probing.flash_selection",
|
||||
&m_CrossProbing.flash_selection, false ) );
|
||||
}
|
||||
|
||||
|
||||
|
@ -175,6 +175,7 @@ static const std::map<int, COLOR4D> s_defaultTheme =
|
||||
{ NETNAMES_LAYER_ID_START, CSS_COLOR( 255, 255, 255, 0.7 ) },
|
||||
{ LAYER_PAD_NETNAMES, CSS_COLOR( 255, 255, 255, 0.9 ) },
|
||||
{ LAYER_VIA_NETNAMES, CSS_COLOR( 50, 50, 50, 0.9 ) },
|
||||
{ LAYER_POINTS, CSS_COLOR( 255, 38, 226, 1 ) },
|
||||
|
||||
{ F_Cu, CSS_COLOR( 200, 52, 52, 1 ) },
|
||||
{ In1_Cu, CSS_COLOR( 127, 200, 127, 1 ) },
|
||||
@ -450,6 +451,7 @@ static const std::map<int, COLOR4D> s_classicTheme =
|
||||
{ NETNAMES_LAYER_ID_START, CSS_COLOR( 255, 255, 255, 0.7 ) },
|
||||
{ LAYER_PAD_NETNAMES, CSS_COLOR( 255, 255, 255, 0.9 ) },
|
||||
{ LAYER_VIA_NETNAMES, CSS_COLOR( 50, 50, 50, 0.9 ) },
|
||||
{ LAYER_POINTS, COLOR4D( BLUE ) },
|
||||
|
||||
{ F_Cu, COLOR4D( RED ) },
|
||||
{ In1_Cu, COLOR4D( YELLOW ) },
|
||||
|
@ -142,6 +142,7 @@ COLOR_SETTINGS::COLOR_SETTINGS( const wxString& aFilename, bool aAbsolutePath )
|
||||
CLR( "board.track_net_names", NETNAMES_LAYER_ID_START );
|
||||
CLR( "board.pad_net_names", LAYER_PAD_NETNAMES );
|
||||
CLR( "board.via_net_names", LAYER_VIA_NETNAMES );
|
||||
CLR( "board.points", LAYER_POINTS );
|
||||
|
||||
CLR( "board.copper.f", F_Cu );
|
||||
CLR( "board.copper.in1", In1_Cu );
|
||||
|
@ -39,6 +39,7 @@ GAL_SET UserVisbilityLayers()
|
||||
LAYER_FP_REFERENCES,
|
||||
LAYER_FP_TEXT,
|
||||
LAYER_ANCHOR,
|
||||
LAYER_POINTS,
|
||||
LAYER_RATSNEST,
|
||||
LAYER_DRC_WARNING,
|
||||
LAYER_DRC_ERROR,
|
||||
@ -71,6 +72,7 @@ GAL_LAYER_ID RenderLayerFromVisibilityLayer( VISIBILITY_LAYER aLayer )
|
||||
case VISIBILITY_LAYER::FOOTPRINT_REFERENCES: return LAYER_FP_REFERENCES;
|
||||
case VISIBILITY_LAYER::FOOTPRINT_TEXT: return LAYER_FP_TEXT;
|
||||
case VISIBILITY_LAYER::FOOTPRINT_ANCHORS: return LAYER_ANCHOR;
|
||||
case VISIBILITY_LAYER::LY_POINTS: return LAYER_POINTS;
|
||||
case VISIBILITY_LAYER::RATSNEST: return LAYER_RATSNEST;
|
||||
case VISIBILITY_LAYER::DRC_WARNINGS: return LAYER_DRC_WARNING;
|
||||
case VISIBILITY_LAYER::DRC_ERRORS: return LAYER_DRC_ERROR;
|
||||
@ -102,6 +104,7 @@ std::optional<VISIBILITY_LAYER> VisibilityLayerFromRenderLayer( GAL_LAYER_ID aLa
|
||||
case LAYER_FP_REFERENCES: return VISIBILITY_LAYER::FOOTPRINT_REFERENCES;
|
||||
case LAYER_FP_TEXT: return VISIBILITY_LAYER::FOOTPRINT_TEXT;
|
||||
case LAYER_ANCHOR: return VISIBILITY_LAYER::FOOTPRINT_ANCHORS;
|
||||
case LAYER_POINTS: return VISIBILITY_LAYER::LY_POINTS;
|
||||
case LAYER_RATSNEST: return VISIBILITY_LAYER::RATSNEST;
|
||||
case LAYER_DRC_WARNING: return VISIBILITY_LAYER::DRC_WARNINGS;
|
||||
case LAYER_DRC_ERROR: return VISIBILITY_LAYER::DRC_ERRORS;
|
||||
|
@ -38,6 +38,7 @@
|
||||
#include <fmt/chrono.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/regex.h>
|
||||
#include <wx/tokenzr.h>
|
||||
#include "locale_io.h"
|
||||
|
||||
|
||||
@ -1503,3 +1504,124 @@ wxString NormalizeFileUri( const wxString& aFileUri )
|
||||
|
||||
return retv;
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// Extract (prefix, numericValue) where numericValue = -1 if no numeric suffix
|
||||
std::pair<wxString, long> ParseAlphaNumericPin( const wxString& pinNum )
|
||||
{
|
||||
wxString prefix;
|
||||
long numValue = -1;
|
||||
|
||||
size_t numStart = pinNum.length();
|
||||
for( int i = static_cast<int>( pinNum.length() ) - 1; i >= 0; --i )
|
||||
{
|
||||
if( !wxIsdigit( pinNum[i] ) )
|
||||
{
|
||||
numStart = i + 1;
|
||||
break;
|
||||
}
|
||||
if( i == 0 )
|
||||
numStart = 0; // all digits
|
||||
}
|
||||
|
||||
if( numStart < pinNum.length() )
|
||||
{
|
||||
prefix = pinNum.Left( numStart );
|
||||
wxString numericPart = pinNum.Mid( numStart );
|
||||
numericPart.ToLong( &numValue );
|
||||
}
|
||||
|
||||
return { prefix, numValue };
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<wxString> ExpandStackedPinNotation( const wxString& aPinName, bool* aValid )
|
||||
{
|
||||
if( aValid )
|
||||
*aValid = true;
|
||||
|
||||
std::vector<wxString> expanded;
|
||||
|
||||
const bool hasOpenBracket = aPinName.Contains( wxT( "[" ) );
|
||||
const bool hasCloseBracket = aPinName.Contains( wxT( "]" ) );
|
||||
|
||||
if( hasOpenBracket || hasCloseBracket )
|
||||
{
|
||||
if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
|
||||
{
|
||||
if( aValid )
|
||||
*aValid = false;
|
||||
expanded.push_back( aPinName );
|
||||
return expanded;
|
||||
}
|
||||
}
|
||||
|
||||
if( !aPinName.StartsWith( wxT( "[" ) ) || !aPinName.EndsWith( wxT( "]" ) ) )
|
||||
{
|
||||
expanded.push_back( aPinName );
|
||||
return expanded;
|
||||
}
|
||||
|
||||
const wxString inner = aPinName.Mid( 1, aPinName.Length() - 2 );
|
||||
|
||||
size_t start = 0;
|
||||
while( start < inner.length() )
|
||||
{
|
||||
size_t comma = inner.find( ',', start );
|
||||
wxString part = ( comma == wxString::npos ) ? inner.Mid( start ) : inner.Mid( start, comma - start );
|
||||
part.Trim( true ).Trim( false );
|
||||
if( part.empty() )
|
||||
{
|
||||
start = ( comma == wxString::npos ) ? inner.length() : comma + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int dashPos = part.Find( '-' );
|
||||
if( dashPos != wxNOT_FOUND )
|
||||
{
|
||||
wxString startTxt = part.Left( dashPos );
|
||||
wxString endTxt = part.Mid( dashPos + 1 );
|
||||
startTxt.Trim( true ).Trim( false );
|
||||
endTxt.Trim( true ).Trim( false );
|
||||
|
||||
auto [startPrefix, startVal] = ParseAlphaNumericPin( startTxt );
|
||||
auto [endPrefix, endVal] = ParseAlphaNumericPin( endTxt );
|
||||
|
||||
if( startPrefix != endPrefix || startVal == -1 || endVal == -1 || startVal > endVal )
|
||||
{
|
||||
if( aValid )
|
||||
*aValid = false;
|
||||
expanded.clear();
|
||||
expanded.push_back( aPinName );
|
||||
return expanded;
|
||||
}
|
||||
|
||||
for( long ii = startVal; ii <= endVal; ++ii )
|
||||
{
|
||||
if( startPrefix.IsEmpty() )
|
||||
expanded.emplace_back( wxString::Format( wxT( "%ld" ), ii ) );
|
||||
else
|
||||
expanded.emplace_back( wxString::Format( wxT( "%s%ld" ), startPrefix, ii ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
expanded.push_back( part );
|
||||
}
|
||||
|
||||
if( comma == wxString::npos )
|
||||
break;
|
||||
start = comma + 1;
|
||||
}
|
||||
|
||||
if( expanded.empty() )
|
||||
{
|
||||
expanded.push_back( aPinName );
|
||||
if( aValid )
|
||||
*aValid = false;
|
||||
}
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
200
common/text_eval/text_eval.lemon
Normal file
200
common/text_eval/text_eval.lemon
Normal file
@ -0,0 +1,200 @@
|
||||
%include {
|
||||
#include <text_eval/text_eval_parser.h>
|
||||
using namespace calc_parser;
|
||||
}
|
||||
|
||||
%token_type {calc_parser::TOKEN_TYPE}
|
||||
%token_prefix KI_EVAL_
|
||||
%default_type {calc_parser::NODE*}
|
||||
%extra_argument {calc_parser::DOC** pDocument}
|
||||
|
||||
%type document {calc_parser::DOC*}
|
||||
%type content_list {calc_parser::DOC*}
|
||||
%type content_item {calc_parser::NODE*}
|
||||
%type calculation {calc_parser::NODE*}
|
||||
%type expression {calc_parser::NODE*}
|
||||
%type term {calc_parser::NODE*}
|
||||
%type factor {calc_parser::NODE*}
|
||||
%type variable {calc_parser::NODE*}
|
||||
%type function_call {calc_parser::NODE*}
|
||||
%type arg_list {std::vector<std::unique_ptr<NODE>>*}
|
||||
|
||||
%destructor document { delete $$; }
|
||||
%destructor content_list { delete $$; }
|
||||
%destructor content_item { delete $$; }
|
||||
%destructor calculation { delete $$; }
|
||||
%destructor expression { delete $$; }
|
||||
%destructor term { delete $$; }
|
||||
%destructor factor { delete $$; }
|
||||
%destructor variable { delete $$; }
|
||||
%destructor function_call { delete $$; }
|
||||
%destructor arg_list { delete $$; }
|
||||
|
||||
%left LT GT LE GE EQ NE.
|
||||
%left PLUS MINUS.
|
||||
%left MULTIPLY DIVIDE MODULO.
|
||||
%right UMINUS.
|
||||
%right POWER.
|
||||
%token COMMA.
|
||||
|
||||
%start_symbol document
|
||||
|
||||
// Main document structure
|
||||
document(D) ::= content_list(L). {
|
||||
D = L;
|
||||
*pDocument = D; // Store the result in the extra argument
|
||||
}
|
||||
|
||||
content_list(L) ::= . {
|
||||
L = new DOC();
|
||||
}
|
||||
|
||||
content_list(L) ::= content_list(L) content_item(I). {
|
||||
L->AddNodeRaw(I);
|
||||
}
|
||||
|
||||
content_item(I) ::= TEXT(T). {
|
||||
I = NODE::CreateTextRaw(GetTokenString(T));
|
||||
}
|
||||
|
||||
content_item(I) ::= calculation(C). {
|
||||
I = C;
|
||||
}
|
||||
|
||||
content_item(I) ::= variable(V). {
|
||||
I = NODE::CreateCalcRaw(V);
|
||||
}
|
||||
|
||||
calculation(C) ::= AT_OPEN expression(E) CLOSE_BRACE. {
|
||||
C = NODE::CreateCalcRaw(E);
|
||||
}
|
||||
|
||||
// Mathematical expressions with proper precedence
|
||||
expression(E) ::= expression(L) LT expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, '<', R);
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) GT expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, '>', R);
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) LE expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, 1, R); // Using 1 for <=
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) GE expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, 2, R); // Using 2 for >=
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) EQ expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, 3, R); // Using 3 for ==
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) NE expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, 4, R); // Using 4 for !=
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) PLUS expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, '+', R);
|
||||
}
|
||||
|
||||
expression(E) ::= expression(L) MINUS expression(R). {
|
||||
E = NODE::CreateBinOpRaw(L, '-', R);
|
||||
}
|
||||
|
||||
expression(E) ::= term(T). {
|
||||
E = T;
|
||||
}
|
||||
|
||||
term(T) ::= term(L) MULTIPLY term(R). {
|
||||
T = NODE::CreateBinOpRaw(L, '*', R);
|
||||
}
|
||||
|
||||
term(T) ::= term(L) DIVIDE term(R). {
|
||||
T = NODE::CreateBinOpRaw(L, '/', R);
|
||||
}
|
||||
|
||||
term(T) ::= term(L) MODULO term(R). {
|
||||
T = NODE::CreateBinOpRaw(L, '%', R);
|
||||
}
|
||||
|
||||
term(T) ::= factor(F). {
|
||||
T = F;
|
||||
}
|
||||
|
||||
factor(F) ::= factor(L) POWER factor(R). {
|
||||
F = NODE::CreateBinOpRaw(L, '^', R);
|
||||
}
|
||||
|
||||
factor(F) ::= MINUS factor(R). [UMINUS] {
|
||||
F = NODE::CreateBinOpRaw(NODE::CreateNumberRaw(0.0), '-', R);
|
||||
}
|
||||
|
||||
factor(F) ::= PLUS factor(R). [UMINUS] {
|
||||
F = R;
|
||||
}
|
||||
|
||||
factor(F) ::= LPAREN expression(E) RPAREN. {
|
||||
F = E;
|
||||
}
|
||||
|
||||
factor(F) ::= NUMBER(N). {
|
||||
try
|
||||
{
|
||||
F = NODE::CreateNumberRaw( N.isString ? std::stod( GetTokenString( N ) ) : GetTokenDouble( N ) );
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
if (g_errorCollector)
|
||||
{
|
||||
g_errorCollector->AddError( fmt::format( "Invalid number format: {}", GetTokenString( N ) ) );
|
||||
}
|
||||
F = NODE::CreateNumberRaw( 0.0 );
|
||||
}
|
||||
}
|
||||
|
||||
factor(F) ::= STRING(S). {
|
||||
F = NODE::CreateStringRaw( GetTokenString( S ) );
|
||||
}
|
||||
|
||||
factor(F) ::= variable(V). {
|
||||
F = V;
|
||||
}
|
||||
|
||||
factor(F) ::= function_call(FC). {
|
||||
F = FC;
|
||||
}
|
||||
|
||||
function_call(FC) ::= IDENTIFIER(I) LPAREN RPAREN. {
|
||||
auto empty_args = new std::vector<std::unique_ptr<NODE>>();
|
||||
FC = NODE::CreateFunctionRaw(GetTokenString(I), empty_args);
|
||||
}
|
||||
|
||||
function_call(FC) ::= IDENTIFIER(I) LPAREN arg_list(AL) RPAREN. {
|
||||
FC = NODE::CreateFunctionRaw(GetTokenString(I), AL);
|
||||
}
|
||||
|
||||
arg_list(AL) ::= expression(E). {
|
||||
AL = new std::vector<std::unique_ptr<NODE>>();
|
||||
AL->emplace_back(std::unique_ptr<NODE>(E));
|
||||
}
|
||||
|
||||
arg_list(AL) ::= arg_list(AL) COMMA expression(E). {
|
||||
AL->emplace_back(std::unique_ptr<NODE>(E));
|
||||
}
|
||||
|
||||
variable(V) ::= DOLLAR_OPEN IDENTIFIER(I) CLOSE_BRACE. {
|
||||
V = NODE::CreateVarRaw(GetTokenString(I));
|
||||
}
|
||||
|
||||
%syntax_error {
|
||||
if (g_errorCollector) {
|
||||
g_errorCollector->AddSyntaxError();
|
||||
}
|
||||
}
|
||||
|
||||
%parse_failure {
|
||||
if (g_errorCollector) {
|
||||
g_errorCollector->AddParseFailure();
|
||||
}
|
||||
}
|
637
common/text_eval/text_eval_parser.cpp
Normal file
637
common/text_eval/text_eval_parser.cpp
Normal file
@ -0,0 +1,637 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.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 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <text_eval/text_eval_parser.h>
|
||||
#include <fmt/format.h>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
|
||||
namespace calc_parser
|
||||
{
|
||||
thread_local ERROR_COLLECTOR* g_errorCollector = nullptr;
|
||||
|
||||
class DATE_UTILS
|
||||
{
|
||||
private:
|
||||
static constexpr int epochYear = 1970;
|
||||
static constexpr std::array<int, 12> daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
static constexpr std::array<const char*, 12> monthNames = {
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
};
|
||||
static constexpr std::array<const char*, 12> monthAbbrev = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
static constexpr std::array<const char*, 7> weekdayNames = {
|
||||
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
|
||||
};
|
||||
|
||||
static auto isLeapYear( int aYear ) -> bool
|
||||
{
|
||||
return ( aYear % 4 == 0 && aYear % 100 != 0 ) || ( aYear % 400 == 0 );
|
||||
}
|
||||
|
||||
static auto daysInYear( int aYear ) -> int
|
||||
{
|
||||
return isLeapYear( aYear ) ? 366 : 365;
|
||||
}
|
||||
|
||||
static auto daysInMonthForYear( int aMonth, int aYear ) -> int
|
||||
{
|
||||
if( aMonth == 2 && isLeapYear( aYear ) )
|
||||
return 29;
|
||||
|
||||
return daysInMonth[aMonth - 1];
|
||||
}
|
||||
|
||||
public:
|
||||
static auto DaysToYmd( int aDaysSinceEpoch ) -> std::tuple<int, int, int>
|
||||
{
|
||||
int year = epochYear;
|
||||
int remainingDays = aDaysSinceEpoch;
|
||||
|
||||
if( remainingDays >= 0 )
|
||||
{
|
||||
while( remainingDays >= daysInYear( year ) )
|
||||
{
|
||||
remainingDays -= daysInYear( year );
|
||||
year++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while( remainingDays < 0 )
|
||||
{
|
||||
year--;
|
||||
remainingDays += daysInYear( year );
|
||||
}
|
||||
}
|
||||
|
||||
int month = 1;
|
||||
while( month <= 12 && remainingDays >= daysInMonthForYear( month, year ) )
|
||||
{
|
||||
remainingDays -= daysInMonthForYear( month, year );
|
||||
month++;
|
||||
}
|
||||
|
||||
int day = remainingDays + 1;
|
||||
return {year, month, day};
|
||||
}
|
||||
|
||||
static auto YmdToDays( int aYear, int aMonth, int aDay ) -> int
|
||||
{
|
||||
int totalDays = 0;
|
||||
|
||||
if( aYear >= epochYear )
|
||||
{
|
||||
for( int y = epochYear; y < aYear; ++y )
|
||||
totalDays += daysInYear( y );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( int y = aYear; y < epochYear; ++y )
|
||||
totalDays -= daysInYear( y );
|
||||
}
|
||||
|
||||
for( int m = 1; m < aMonth; ++m )
|
||||
totalDays += daysInMonthForYear( m, aYear );
|
||||
|
||||
totalDays += aDay - 1;
|
||||
return totalDays;
|
||||
}
|
||||
|
||||
static auto ParseDate( const std::string& aDateStr ) -> std::optional<int>
|
||||
{
|
||||
std::istringstream iss( aDateStr );
|
||||
std::string token;
|
||||
std::vector<int> parts;
|
||||
|
||||
char separator = 0;
|
||||
bool isCjkFormat = false;
|
||||
|
||||
// Check for CJK date formats first (Chinese, Korean, or mixed)
|
||||
bool hasChineseYear = aDateStr.find( "年" ) != std::string::npos;
|
||||
bool hasChineseMonth = aDateStr.find( "月" ) != std::string::npos;
|
||||
bool hasChineseDay = aDateStr.find( "日" ) != std::string::npos;
|
||||
bool hasKoreanYear = aDateStr.find( "년" ) != std::string::npos;
|
||||
bool hasKoreanMonth = aDateStr.find( "월" ) != std::string::npos;
|
||||
bool hasKoreanDay = aDateStr.find( "일" ) != std::string::npos;
|
||||
|
||||
// Check if we have any CJK date format (pure or mixed)
|
||||
if( (hasChineseYear || hasKoreanYear) &&
|
||||
(hasChineseMonth || hasKoreanMonth) &&
|
||||
(hasChineseDay || hasKoreanDay) )
|
||||
{
|
||||
// CJK format: Support pure Chinese, pure Korean, or mixed formats
|
||||
isCjkFormat = true;
|
||||
|
||||
size_t yearPos, monthPos, dayPos;
|
||||
|
||||
// Find year position and marker
|
||||
if( hasChineseYear )
|
||||
yearPos = aDateStr.find( "年" );
|
||||
else
|
||||
yearPos = aDateStr.find( "년" );
|
||||
|
||||
// Find month position and marker
|
||||
if( hasChineseMonth )
|
||||
monthPos = aDateStr.find( "月" );
|
||||
else
|
||||
monthPos = aDateStr.find( "월" );
|
||||
|
||||
// Find day position and marker
|
||||
if( hasChineseDay )
|
||||
dayPos = aDateStr.find( "日" );
|
||||
else
|
||||
dayPos = aDateStr.find( "일" );
|
||||
|
||||
try
|
||||
{
|
||||
int year = std::stoi( aDateStr.substr( 0, yearPos ) );
|
||||
int month = std::stoi( aDateStr.substr( yearPos + 3, monthPos - yearPos - 3 ) ); // 3 bytes for CJK year marker
|
||||
int day = std::stoi( aDateStr.substr( monthPos + 3, dayPos - monthPos - 3 ) ); // 3 bytes for CJK month marker
|
||||
|
||||
parts = { year, month, day };
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else if( aDateStr.find( '-' ) != std::string::npos )
|
||||
separator = '-';
|
||||
else if( aDateStr.find( '/' ) != std::string::npos )
|
||||
separator = '/';
|
||||
else if( aDateStr.find( '.' ) != std::string::npos )
|
||||
separator = '.';
|
||||
|
||||
if( separator )
|
||||
{
|
||||
while( std::getline( iss, token, separator ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
parts.push_back( std::stoi( token ) );
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( !isCjkFormat && aDateStr.length() == 8 )
|
||||
{
|
||||
try
|
||||
{
|
||||
int dateNum = std::stoi( aDateStr );
|
||||
int year = dateNum / 10000;
|
||||
int month = ( dateNum / 100 ) % 100;
|
||||
int day = dateNum % 100;
|
||||
return YmdToDays( year, month, day );
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else if( !isCjkFormat )
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if( parts.empty() || parts.size() > 3 )
|
||||
return std::nullopt;
|
||||
|
||||
int year, month, day;
|
||||
|
||||
if( parts.size() == 1 )
|
||||
{
|
||||
year = parts[0];
|
||||
month = 1;
|
||||
day = 1;
|
||||
}
|
||||
else if( parts.size() == 2 )
|
||||
{
|
||||
year = parts[0];
|
||||
month = parts[1];
|
||||
day = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( isCjkFormat )
|
||||
{
|
||||
// CJK formats are always in YYYY年MM月DD日 or YYYY년 MM월 DD일 order
|
||||
year = parts[0];
|
||||
month = parts[1];
|
||||
day = parts[2];
|
||||
}
|
||||
else if( separator == '/' && parts[0] <= 12 && parts[1] <= 31 )
|
||||
{
|
||||
month = parts[0];
|
||||
day = parts[1];
|
||||
year = parts[2];
|
||||
}
|
||||
else if( separator == '/' && parts[1] <= 12 )
|
||||
{
|
||||
day = parts[0];
|
||||
month = parts[1];
|
||||
year = parts[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
year = parts[0];
|
||||
month = parts[1];
|
||||
day = parts[2];
|
||||
}
|
||||
}
|
||||
|
||||
if( month < 1 || month > 12 )
|
||||
return std::nullopt;
|
||||
if( day < 1 || day > daysInMonthForYear( month, year ) )
|
||||
return std::nullopt;
|
||||
|
||||
return YmdToDays( year, month, day );
|
||||
}
|
||||
|
||||
static auto FormatDate( int aDaysSinceEpoch, const std::string& aFormat ) -> std::string
|
||||
{
|
||||
auto [year, month, day] = DaysToYmd( aDaysSinceEpoch );
|
||||
|
||||
if( aFormat == "ISO" || aFormat == "iso" )
|
||||
return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
|
||||
else if( aFormat == "US" || aFormat == "us" )
|
||||
return fmt::format( "{:02d}/{:02d}/{:04d}", month, day, year );
|
||||
else if( aFormat == "EU" || aFormat == "european" )
|
||||
return fmt::format( "{:02d}/{:02d}/{:04d}", day, month, year );
|
||||
else if( aFormat == "long" )
|
||||
return fmt::format( "{} {}, {}", monthNames[month-1], day, year );
|
||||
else if( aFormat == "short" )
|
||||
return fmt::format( "{} {}, {}", monthAbbrev[month-1], day, year );
|
||||
else if( aFormat == "Chinese" || aFormat == "chinese" || aFormat == "CN" || aFormat == "cn" || aFormat == "中文" )
|
||||
return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
|
||||
else if( aFormat == "Japanese" || aFormat == "japanese" || aFormat == "JP" || aFormat == "jp" || aFormat == "日本語" )
|
||||
return fmt::format( "{}年{:02d}月{:02d}日", year, month, day );
|
||||
else if( aFormat == "Korean" || aFormat == "korean" || aFormat == "KR" || aFormat == "kr" || aFormat == "한국어" )
|
||||
return fmt::format( "{}년 {:02d}월 {:02d}일", year, month, day );
|
||||
else
|
||||
return fmt::format( "{:04d}-{:02d}-{:02d}", year, month, day );
|
||||
}
|
||||
|
||||
static auto GetWeekdayName( int aDaysSinceEpoch ) -> std::string
|
||||
{
|
||||
int weekday = ( ( aDaysSinceEpoch + 3 ) % 7 ); // +3 because epoch was Thursday (Monday = 0)
|
||||
|
||||
if( weekday < 0 )
|
||||
weekday += 7;
|
||||
|
||||
return std::string{ weekdayNames[weekday] };
|
||||
}
|
||||
|
||||
static auto GetCurrentDays() -> int
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto timeT = std::chrono::system_clock::to_time_t( now );
|
||||
return static_cast<int>( timeT / ( 24 * 3600 ) );
|
||||
}
|
||||
|
||||
static auto GetCurrentTimestamp() -> double
|
||||
{
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto timeT = std::chrono::system_clock::to_time_t( now );
|
||||
return static_cast<double>( timeT );
|
||||
}
|
||||
};
|
||||
|
||||
EVAL_VISITOR::EVAL_VISITOR( VariableCallback aVariableCallback, ERROR_COLLECTOR& aErrorCollector ) :
|
||||
m_variableCallback( std::move( aVariableCallback ) ),
|
||||
m_errors( aErrorCollector ),
|
||||
m_gen( m_rd() )
|
||||
{}
|
||||
|
||||
auto EVAL_VISITOR::operator()( const NODE& aNode ) const -> Result<Value>
|
||||
{
|
||||
switch( aNode.type )
|
||||
{
|
||||
case NodeType::Number:
|
||||
return MakeValue<Value>( std::get<double>( aNode.data ) );
|
||||
|
||||
case NodeType::String:
|
||||
return MakeValue<Value>( std::get<std::string>( aNode.data ) );
|
||||
|
||||
case NodeType::Var:
|
||||
{
|
||||
const auto& varName = std::get<std::string>( aNode.data );
|
||||
|
||||
// Use callback to resolve variable
|
||||
if( m_variableCallback )
|
||||
return m_variableCallback( varName );
|
||||
|
||||
return MakeError<Value>( fmt::format( "No variable resolver configured for: {}", varName ) );
|
||||
}
|
||||
|
||||
case NodeType::BinOp:
|
||||
{
|
||||
const auto& binop = std::get<BIN_OP_DATA>( aNode.data );
|
||||
auto leftResult = binop.left->Accept( *this );
|
||||
if( !leftResult )
|
||||
return leftResult;
|
||||
|
||||
auto rightResult = binop.right ?
|
||||
binop.right->Accept( *this ) : MakeValue<Value>( 0.0 );
|
||||
if( !rightResult )
|
||||
return rightResult;
|
||||
|
||||
// Special handling for string concatenation with +
|
||||
if( binop.op == '+' )
|
||||
{
|
||||
const auto& leftVal = leftResult.GetValue();
|
||||
const auto& rightVal = rightResult.GetValue();
|
||||
|
||||
// If either operand is a string, concatenate
|
||||
if( std::holds_alternative<std::string>( leftVal ) ||
|
||||
std::holds_alternative<std::string>( rightVal ) )
|
||||
{
|
||||
return MakeValue<Value>( VALUE_UTILS::ConcatStrings( leftVal, rightVal ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, perform arithmetic
|
||||
return VALUE_UTILS::ArithmeticOp( leftResult.GetValue(), rightResult.GetValue(), binop.op );
|
||||
}
|
||||
|
||||
case NodeType::Function:
|
||||
{
|
||||
const auto& func = std::get<FUNC_DATA>( aNode.data );
|
||||
return evaluateFunction( func );
|
||||
}
|
||||
|
||||
default:
|
||||
return MakeError<Value>( "Cannot evaluate this node type" );
|
||||
}
|
||||
}
|
||||
|
||||
auto EVAL_VISITOR::evaluateFunction( const FUNC_DATA& aFunc ) const -> Result<Value>
|
||||
{
|
||||
const auto& name = aFunc.name;
|
||||
const auto& args = aFunc.args;
|
||||
|
||||
// Zero-argument functions
|
||||
if( args.empty() )
|
||||
{
|
||||
if( name == "today" )
|
||||
return MakeValue<Value>( static_cast<double>( DATE_UTILS::GetCurrentDays() ) );
|
||||
else if( name == "now" )
|
||||
return MakeValue<Value>( DATE_UTILS::GetCurrentTimestamp() );
|
||||
else if( name == "random" )
|
||||
{
|
||||
std::uniform_real_distribution<double> dis( 0.0, 1.0 );
|
||||
return MakeValue<Value>( dis( m_gen ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate arguments to mixed types
|
||||
std::vector<Value> argValues;
|
||||
argValues.reserve( args.size() );
|
||||
|
||||
for( const auto& arg : args )
|
||||
{
|
||||
auto result = arg->Accept( *this );
|
||||
if( !result )
|
||||
return result;
|
||||
|
||||
argValues.push_back( result.GetValue() );
|
||||
}
|
||||
|
||||
const auto argc = argValues.size();
|
||||
|
||||
// String formatting functions (return strings!)
|
||||
if( name == "format" && argc >= 1 )
|
||||
{
|
||||
auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !numResult )
|
||||
return MakeError<Value>( numResult.GetError() );
|
||||
|
||||
const auto value = numResult.GetValue();
|
||||
int decimals = 2;
|
||||
if( argc > 1 )
|
||||
{
|
||||
auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
|
||||
if( decResult )
|
||||
decimals = static_cast<int>( decResult.GetValue() );
|
||||
}
|
||||
|
||||
return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
|
||||
}
|
||||
else if( name == "currency" && argc >= 1 )
|
||||
{
|
||||
auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !numResult )
|
||||
return MakeError<Value>( numResult.GetError() );
|
||||
|
||||
const auto amount = numResult.GetValue();
|
||||
const auto symbol = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "$";
|
||||
|
||||
return MakeValue<Value>( fmt::format( "{}{:.2f}", symbol, amount ) );
|
||||
}
|
||||
else if( name == "fixed" && argc >= 1 )
|
||||
{
|
||||
auto numResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !numResult )
|
||||
return MakeError<Value>( numResult.GetError() );
|
||||
|
||||
const auto value = numResult.GetValue();
|
||||
int decimals = 2;
|
||||
if( argc > 1 )
|
||||
{
|
||||
auto decResult = VALUE_UTILS::ToDouble( argValues[1] );
|
||||
if( decResult )
|
||||
decimals = static_cast<int>( decResult.GetValue() );
|
||||
}
|
||||
|
||||
return MakeValue<Value>( fmt::format( "{:.{}f}", value, decimals ) );
|
||||
}
|
||||
|
||||
// Date formatting functions (return strings!)
|
||||
else if( name == "dateformat" && argc >= 1 )
|
||||
{
|
||||
auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !dateResult )
|
||||
return MakeError<Value>( dateResult.GetError() );
|
||||
|
||||
const auto days = static_cast<int>( dateResult.GetValue() );
|
||||
const auto format = argc > 1 ? VALUE_UTILS::ToString( argValues[1] ) : "ISO";
|
||||
|
||||
return MakeValue<Value>( DATE_UTILS::FormatDate( days, format ) );
|
||||
}
|
||||
else if( name == "datestring" && argc == 1 )
|
||||
{
|
||||
auto dateStr = VALUE_UTILS::ToString( argValues[0] );
|
||||
auto daysResult = DATE_UTILS::ParseDate( dateStr );
|
||||
|
||||
if( !daysResult )
|
||||
return MakeError<Value>( "Invalid date format: " + dateStr );
|
||||
|
||||
return MakeValue<Value>( static_cast<double>( daysResult.value() ) );
|
||||
}
|
||||
else if( name == "weekdayname" && argc == 1 )
|
||||
{
|
||||
auto dateResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !dateResult )
|
||||
return MakeError<Value>( dateResult.GetError() );
|
||||
|
||||
const auto days = static_cast<int>( dateResult.GetValue() );
|
||||
return MakeValue<Value>( DATE_UTILS::GetWeekdayName( days ) );
|
||||
}
|
||||
|
||||
// String functions (return strings!)
|
||||
else if( name == "upper" && argc == 1 )
|
||||
{
|
||||
auto str = VALUE_UTILS::ToString( argValues[0] );
|
||||
std::transform( str.begin(), str.end(), str.begin(), ::toupper );
|
||||
return MakeValue<Value>( str );
|
||||
}
|
||||
else if( name == "lower" && argc == 1 )
|
||||
{
|
||||
auto str = VALUE_UTILS::ToString( argValues[0] );
|
||||
std::transform( str.begin(), str.end(), str.begin(), ::tolower );
|
||||
return MakeValue<Value>( str );
|
||||
}
|
||||
else if( name == "concat" && argc >= 2 )
|
||||
{
|
||||
std::string result;
|
||||
for( const auto& val : argValues )
|
||||
result += VALUE_UTILS::ToString( val );
|
||||
|
||||
return MakeValue<Value>( result );
|
||||
}
|
||||
|
||||
// Conditional functions (handle mixed types)
|
||||
if( name == "if" && argc == 3 )
|
||||
{
|
||||
// Convert only the condition to a number
|
||||
auto conditionResult = VALUE_UTILS::ToDouble( argValues[0] );
|
||||
if( !conditionResult )
|
||||
return MakeError<Value>( conditionResult.GetError() );
|
||||
|
||||
const auto condition = conditionResult.GetValue() != 0.0;
|
||||
return MakeValue<Value>( condition ? argValues[1] : argValues[2] );
|
||||
}
|
||||
|
||||
// Mathematical functions (return numbers) - convert args to doubles first
|
||||
std::vector<double> numArgs;
|
||||
for( const auto& val : argValues )
|
||||
{
|
||||
auto numResult = VALUE_UTILS::ToDouble( val );
|
||||
if( !numResult )
|
||||
return MakeError<Value>( numResult.GetError() );
|
||||
|
||||
numArgs.push_back( numResult.GetValue() );
|
||||
}
|
||||
|
||||
// Mathematical function implementations
|
||||
if( name == "abs" && argc == 1 )
|
||||
return MakeValue<Value>( std::abs( numArgs[0] ) );
|
||||
else if( name == "sum" && argc >= 1 )
|
||||
return MakeValue<Value>( std::accumulate( numArgs.begin(), numArgs.end(), 0.0 ) );
|
||||
else if( name == "round" && argc >= 1 )
|
||||
{
|
||||
const auto value = numArgs[0];
|
||||
const auto precision = argc > 1 ? static_cast<int>( numArgs[1] ) : 0;
|
||||
const auto multiplier = std::pow( 10.0, precision );
|
||||
return MakeValue<Value>( std::round( value * multiplier ) / multiplier );
|
||||
}
|
||||
else if( name == "sqrt" && argc == 1 )
|
||||
{
|
||||
if( numArgs[0] < 0 )
|
||||
return MakeError<Value>( "Square root of negative number" );
|
||||
|
||||
return MakeValue<Value>( std::sqrt( numArgs[0] ) );
|
||||
}
|
||||
else if( name == "pow" && argc == 2 )
|
||||
return MakeValue<Value>( std::pow( numArgs[0], numArgs[1] ) );
|
||||
else if( name == "floor" && argc == 1 )
|
||||
return MakeValue<Value>( std::floor( numArgs[0] ) );
|
||||
else if( name == "ceil" && argc == 1 )
|
||||
return MakeValue<Value>( std::ceil( numArgs[0] ) );
|
||||
else if( name == "min" && argc >= 1 )
|
||||
return MakeValue<Value>( *std::min_element( numArgs.begin(), numArgs.end() ) );
|
||||
else if( name == "max" && argc >= 1 )
|
||||
return MakeValue<Value>( *std::max_element( numArgs.begin(), numArgs.end() ) );
|
||||
else if( name == "avg" && argc >= 1 )
|
||||
{
|
||||
const auto sum = std::accumulate( numArgs.begin(), numArgs.end(), 0.0 );
|
||||
return MakeValue<Value>( sum / static_cast<double>( argc ) );
|
||||
}
|
||||
|
||||
return MakeError<Value>( fmt::format( "Unknown function: {} with {} arguments", name, argc ) );
|
||||
}
|
||||
|
||||
auto DOC_PROCESSOR::Process( const DOC& aDoc, VariableCallback aVariableCallback )
|
||||
-> std::pair<std::string, bool>
|
||||
{
|
||||
std::string result;
|
||||
auto localErrors = ERROR_COLLECTOR{};
|
||||
EVAL_VISITOR evaluator{ std::move( aVariableCallback ), localErrors };
|
||||
bool hadErrors = aDoc.HasErrors();
|
||||
|
||||
for( const auto& node : aDoc.GetNodes() )
|
||||
{
|
||||
switch( node->type )
|
||||
{
|
||||
case NodeType::Text:
|
||||
result += std::get<std::string>( node->data );
|
||||
break;
|
||||
|
||||
case NodeType::Calc:
|
||||
{
|
||||
const auto& calcData = std::get<BIN_OP_DATA>( node->data );
|
||||
auto evalResult = calcData.left->Accept( evaluator );
|
||||
|
||||
if( evalResult )
|
||||
result += VALUE_UTILS::ToString( evalResult.GetValue() );
|
||||
else
|
||||
{
|
||||
// Don't add error formatting to result - errors go to error vector only
|
||||
// The higher level will return original input unchanged if there are errors
|
||||
hadErrors = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
result += "[Unknown node type]";
|
||||
hadErrors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { std::move( result ), hadErrors || localErrors.HasErrors() };
|
||||
}
|
||||
|
||||
auto DOC_PROCESSOR::ProcessWithDetails( const DOC& aDoc, VariableCallback aVariableCallback )
|
||||
-> std::tuple<std::string, std::vector<std::string>, bool>
|
||||
{
|
||||
auto [result, hadErrors] = Process( aDoc, std::move( aVariableCallback ) );
|
||||
auto allErrors = aDoc.GetErrors();
|
||||
|
||||
return { std::move( result ), std::move( allErrors ), hadErrors };
|
||||
}
|
||||
|
||||
} // namespace calc_parser
|
2067
common/text_eval/text_eval_wrapper.cpp
Normal file
2067
common/text_eval/text_eval_wrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -953,6 +953,19 @@ TOOL_ACTION ACTIONS::unpinLibrary( TOOL_ACTION_ARGS()
|
||||
.FriendlyName( _( "Unpin Library" ) )
|
||||
.Tooltip( _( "No longer keep the library at the top of the list" ) ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::showLibraryFieldsTable( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Control.showLibraryFieldsTable" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Library Fields Table" ) )
|
||||
.Icon( BITMAPS::table ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::showRelatedLibraryFieldsTable( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Control.showRelatedLibraryFieldsTable" )
|
||||
.Scope( AS_GLOBAL )
|
||||
.FriendlyName( _( "Library Fields Table of Related Symbols" ) )
|
||||
.Tooltip( _( "Edit a table of fields of all symbols related to the selected symbol" ) )
|
||||
.Icon( BITMAPS::table ) );
|
||||
|
||||
TOOL_ACTION ACTIONS::showLibraryTree( TOOL_ACTION_ARGS()
|
||||
.Name( "common.Control.showLibraryTree" )
|
||||
.Scope( AS_GLOBAL )
|
||||
|
@ -30,6 +30,8 @@
|
||||
#include <trigo.h>
|
||||
|
||||
#include <geometry/geometry_utils.h>
|
||||
#include <math/vector2d.h>
|
||||
#include <math/util.h>
|
||||
|
||||
|
||||
void EC_VERTICAL::Apply( EDIT_POINT& aHandle, const GRID_HELPER& aGrid )
|
||||
@ -146,26 +148,24 @@ void EC_CIRCLE::Apply( EDIT_POINT& aHandle, const GRID_HELPER& aGrid )
|
||||
EC_CONVERGING::EC_CONVERGING( EDIT_LINE& aLine, EDIT_POINTS& aPoints ) :
|
||||
EDIT_CONSTRAINT<EDIT_LINE>( aLine ),
|
||||
m_colinearConstraint( nullptr ),
|
||||
m_editPoints( aPoints )
|
||||
m_editPoints( aPoints ),
|
||||
m_prevOrigin( aPoints.Previous( aLine.GetOrigin(), false ) ),
|
||||
m_nextEnd( aPoints.Next( aLine.GetEnd(), false ) )
|
||||
{
|
||||
// Dragged segment endings
|
||||
EDIT_POINT& origin = aLine.GetOrigin();
|
||||
EDIT_POINT& end = aLine.GetEnd();
|
||||
|
||||
// Previous and next points, to make constraining lines (adjacent to the dragged line)
|
||||
EDIT_POINT& prevOrigin = *aPoints.Previous( origin, false );
|
||||
EDIT_POINT& nextEnd = *aPoints.Next( end, false );
|
||||
|
||||
// Constraints for segments adjacent to the dragged one
|
||||
m_originSideConstraint = std::make_unique<EC_LINE>( origin, prevOrigin );
|
||||
m_endSideConstraint = std::make_unique<EC_LINE>( end, nextEnd );
|
||||
m_originSideConstraint = std::make_unique<EC_LINE>( origin, *m_prevOrigin );
|
||||
m_endSideConstraint = std::make_unique<EC_LINE>( end, *m_nextEnd );
|
||||
|
||||
// Store the current vector of the line
|
||||
m_draggedVector = end.GetPosition() - origin.GetPosition();
|
||||
|
||||
// Check for colinearity
|
||||
SEG originSide( origin.GetPosition(), prevOrigin.GetPosition() );
|
||||
SEG endSide( end.GetPosition(), nextEnd.GetPosition() );
|
||||
SEG originSide( origin.GetPosition(), m_prevOrigin->GetPosition() );
|
||||
SEG endSide( end.GetPosition(), m_nextEnd->GetPosition() );
|
||||
SEG dragged( origin.GetPosition(), end.GetPosition() );
|
||||
|
||||
// Used to align lines that are almost collinear
|
||||
@ -178,6 +178,13 @@ EC_CONVERGING::EC_CONVERGING( EDIT_LINE& aLine, EDIT_POINTS& aPoints ) :
|
||||
m_colinearConstraint = m_originSideConstraint.get();
|
||||
else if( m_endCollinear )
|
||||
m_colinearConstraint = m_endSideConstraint.get();
|
||||
|
||||
if( OPT_VECTOR2I intersect = originSide.IntersectLines( endSide ) )
|
||||
m_convergencePoint = *intersect;
|
||||
else
|
||||
m_convergencePoint = aLine.GetPosition();
|
||||
|
||||
m_midVector = aLine.GetPosition() - m_convergencePoint;
|
||||
}
|
||||
|
||||
|
||||
@ -189,6 +196,21 @@ EC_CONVERGING::~EC_CONVERGING()
|
||||
|
||||
void EC_CONVERGING::Apply( EDIT_LINE& aHandle, const GRID_HELPER& aGrid )
|
||||
{
|
||||
VECTOR2I handlePos = aHandle.GetPosition();
|
||||
VECTOR2I convToHandle = handlePos - m_convergencePoint;
|
||||
double t = 0.0;
|
||||
|
||||
if( m_midVector.SquaredEuclideanNorm() )
|
||||
t = double( convToHandle.Dot( m_midVector ) )
|
||||
/ double( m_midVector.SquaredEuclideanNorm() );
|
||||
|
||||
if( t < 0.0 )
|
||||
t = 0.0;
|
||||
|
||||
VECTOR2D newCenterD = VECTOR2D( m_convergencePoint ) + VECTOR2D( m_midVector ) * t;
|
||||
VECTOR2I newCenter( KiROUND( newCenterD.x ), KiROUND( newCenterD.y ) );
|
||||
aHandle.SetPosition( newCenter );
|
||||
|
||||
// The dragged segment endpoints
|
||||
EDIT_POINT& origin = aHandle.GetOrigin();
|
||||
EDIT_POINT& end = aHandle.GetEnd();
|
||||
@ -209,12 +231,9 @@ void EC_CONVERGING::Apply( EDIT_LINE& aHandle, const GRID_HELPER& aGrid )
|
||||
m_originSideConstraint->Apply( aGrid );
|
||||
m_endSideConstraint->Apply( aGrid );
|
||||
|
||||
EDIT_POINT& prevOrigin = *m_editPoints.Previous( origin, false );
|
||||
EDIT_POINT& nextEnd = *m_editPoints.Next( end, false );
|
||||
|
||||
// Two segments adjacent to the dragged segment
|
||||
SEG originSide = SEG( origin.GetPosition(), prevOrigin.GetPosition() );
|
||||
SEG endSide = SEG( end.GetPosition(), nextEnd.GetPosition() );
|
||||
SEG originSide = SEG( origin.GetPosition(), m_prevOrigin->GetPosition() );
|
||||
SEG endSide = SEG( end.GetPosition(), m_nextEnd->GetPosition() );
|
||||
|
||||
// First intersection point (dragged segment against origin side)
|
||||
if( OPT_VECTOR2I originIntersect = dragged.IntersectLines( originSide ) )
|
||||
@ -226,8 +245,8 @@ void EC_CONVERGING::Apply( EDIT_LINE& aHandle, const GRID_HELPER& aGrid )
|
||||
|
||||
// Check if adjacent segments intersect (did we dragged the line to the point that it may
|
||||
// create a selfintersecting polygon?)
|
||||
originSide = SEG( origin.GetPosition(), prevOrigin.GetPosition() );
|
||||
endSide = SEG( end.GetPosition(), nextEnd.GetPosition() );
|
||||
originSide = SEG( origin.GetPosition(), m_prevOrigin->GetPosition() );
|
||||
endSide = SEG( end.GetPosition(), m_nextEnd->GetPosition() );
|
||||
|
||||
if( OPT_VECTOR2I originEndIntersect = endSide.Intersect( originSide ) )
|
||||
{
|
||||
|
@ -27,6 +27,10 @@
|
||||
#include <gal/color4d.h>
|
||||
#include <gal/painter.h>
|
||||
#include <math/util.h> // for KiROUND
|
||||
#include <geometry/seg.h>
|
||||
#include <wx/string.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "tool/edit_points.h"
|
||||
|
||||
|
||||
@ -328,4 +332,5 @@ void EDIT_POINTS::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const
|
||||
gal->DrawLine( line.GetOrigin().GetPosition(), line.GetEnd().GetPosition() );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -218,35 +218,38 @@ void GRID_HELPER::SetAuxAxes( bool aEnable, const VECTOR2I& aOrigin )
|
||||
|
||||
VECTOR2I GRID_HELPER::AlignGrid( const VECTOR2I& aPoint ) const
|
||||
{
|
||||
return computeNearest( aPoint, GetGrid() );
|
||||
return computeNearest( aPoint, GetGrid(), GetOrigin() );
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I GRID_HELPER::AlignGrid( const VECTOR2I& aPoint, const VECTOR2D& aGrid ) const
|
||||
VECTOR2I GRID_HELPER::AlignGrid( const VECTOR2I& aPoint, const VECTOR2D& aGrid,
|
||||
const VECTOR2D& aOffset ) const
|
||||
{
|
||||
return computeNearest( aPoint, aGrid );
|
||||
return computeNearest( aPoint, aGrid, aOffset );
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I GRID_HELPER::computeNearest( const VECTOR2I& aPoint, const VECTOR2I& aGrid ) const
|
||||
VECTOR2I GRID_HELPER::computeNearest( const VECTOR2I& aPoint, const VECTOR2I& aGrid,
|
||||
const VECTOR2I& aOffset ) const
|
||||
{
|
||||
return VECTOR2I( KiROUND( static_cast<double>( aPoint.x ) / aGrid.x ) * aGrid.x,
|
||||
KiROUND( static_cast<double>( aPoint.y ) / aGrid.y ) * aGrid.y );
|
||||
return VECTOR2I( KiROUND( (double) ( aPoint.x - aOffset.x ) / aGrid.x ) * aGrid.x + aOffset.x,
|
||||
KiROUND( (double) ( aPoint.y - aOffset.y ) / aGrid.y ) * aGrid.y + aOffset.y );
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I GRID_HELPER::Align( const VECTOR2I& aPoint ) const
|
||||
{
|
||||
return Align( aPoint, GetGrid() );
|
||||
return Align( aPoint, GetGrid(), GetOrigin() );
|
||||
}
|
||||
|
||||
|
||||
VECTOR2I GRID_HELPER::Align( const VECTOR2I& aPoint, const VECTOR2D& aGrid ) const
|
||||
VECTOR2I GRID_HELPER::Align( const VECTOR2I& aPoint, const VECTOR2D& aGrid,
|
||||
const VECTOR2D& aOffset ) const
|
||||
{
|
||||
if( !canUseGrid() )
|
||||
return aPoint;
|
||||
|
||||
VECTOR2I nearest = AlignGrid( aPoint, aGrid );
|
||||
VECTOR2I nearest = AlignGrid( aPoint, aGrid, aOffset );
|
||||
|
||||
if( !m_auxAxis )
|
||||
return nearest;
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "tool/point_editor_behavior.h"
|
||||
|
||||
#include <advanced_config.h>
|
||||
#include <commit.h>
|
||||
#include <geometry/shape_poly_set.h>
|
||||
|
||||
|
||||
@ -101,6 +102,11 @@ void POLYGON_POINT_EDIT_BEHAVIOR::UpdateOutlineFromPoints( SHAPE_POLY_SET& aOu
|
||||
}
|
||||
|
||||
|
||||
void POLYGON_POINT_EDIT_BEHAVIOR::FinalizeItem( EDIT_POINTS& aPoints, COMMIT& aCommit )
|
||||
{
|
||||
m_polygon.RemoveNullSegments();
|
||||
}
|
||||
|
||||
void EDA_SEGMENT_POINT_EDIT_BEHAVIOR::MakePoints( EDIT_POINTS& aPoints )
|
||||
{
|
||||
aPoints.AddPoint( m_segment.GetStart() );
|
||||
|
@ -158,6 +158,43 @@ TOOL_DISPATCHER::~TOOL_DISPATCHER()
|
||||
delete st;
|
||||
}
|
||||
|
||||
int TOOL_DISPATCHER::decodeModifiers( const wxKeyboardState* aState )
|
||||
{
|
||||
int mods = 0;
|
||||
int wxmods = aState->GetModifiers();
|
||||
|
||||
// Returns the state of key modifiers (Alt, Ctrl and so on). Be carefull:
|
||||
// the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
|
||||
// So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
|
||||
#if CAN_USE_ALTGR_KEY
|
||||
if( wxmods & wxMOD_ALTGR )
|
||||
mods |= MD_ALTGR;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if( wxmods & wxMOD_CONTROL )
|
||||
mods |= MD_CTRL;
|
||||
|
||||
if( wxmods & wxMOD_ALT )
|
||||
mods |= MD_ALT;
|
||||
}
|
||||
|
||||
if( wxmods & wxMOD_SHIFT )
|
||||
mods |= MD_SHIFT;
|
||||
|
||||
#ifdef wxMOD_META
|
||||
if( wxmods & wxMOD_META )
|
||||
mods |= MD_META;
|
||||
#endif
|
||||
|
||||
#ifdef wxMOD_WIN
|
||||
if( wxmods & wxMOD_WIN )
|
||||
mods |= MD_SUPER;
|
||||
#endif
|
||||
|
||||
return mods;
|
||||
}
|
||||
|
||||
|
||||
void TOOL_DISPATCHER::ResetState()
|
||||
{
|
||||
@ -297,7 +334,19 @@ static bool isKeyModifierOnly( int aKeyCode )
|
||||
{
|
||||
static std::vector<enum wxKeyCode> special_keys =
|
||||
{
|
||||
WXK_CONTROL, WXK_RAW_CONTROL, WXK_SHIFT, WXK_ALT
|
||||
WXK_CONTROL, WXK_RAW_CONTROL, WXK_SHIFT, WXK_ALT,
|
||||
#ifdef WXK_WINDOWS_LEFT
|
||||
WXK_WINDOWS_LEFT, WXK_WINDOWS_RIGHT,
|
||||
#endif
|
||||
#ifdef WXK_MENU
|
||||
WXK_MENU,
|
||||
#endif
|
||||
#ifdef WXK_COMMAND
|
||||
WXK_COMMAND,
|
||||
#endif
|
||||
#ifdef WXK_META
|
||||
WXK_META,
|
||||
#endif
|
||||
};
|
||||
|
||||
return alg::contains( special_keys, aKeyCode );
|
||||
@ -515,7 +564,7 @@ void TOOL_DISPATCHER::DispatchWxEvent( wxEvent& aEvent )
|
||||
if( !evt && me->GetWheelRotation() != 0 )
|
||||
{
|
||||
const unsigned modBits =
|
||||
static_cast<unsigned>( mods ) & ( MD_CTRL | MD_ALT | MD_SHIFT );
|
||||
static_cast<unsigned>( mods ) & MD_MODIFIER_MASK;
|
||||
const bool shouldHandle = std::popcount( modBits ) > 1;
|
||||
|
||||
if( shouldHandle )
|
||||
|
@ -149,6 +149,9 @@ const std::string TOOL_EVENT::Format() const
|
||||
{ MD_SHIFT, "shift" },
|
||||
{ MD_CTRL, "ctrl" },
|
||||
{ MD_ALT, "alt" },
|
||||
{ MD_SUPER, "super" },
|
||||
{ MD_META, "meta" },
|
||||
{ MD_ALTGR, "altgr" },
|
||||
{ 0, "" }
|
||||
};
|
||||
|
||||
|
@ -59,6 +59,7 @@ const wxChar* const traceUiProfile = wxT( "KICAD_UI_PROFILE" );
|
||||
const wxChar* const traceGit = wxT( "KICAD_GIT" );
|
||||
const wxChar* const traceEagleIo = wxT( "KICAD_EAGLE_IO" );
|
||||
const wxChar* const traceDesignBlocks = wxT( "KICAD_DESIGN_BLOCK" );
|
||||
const wxChar* const traceLibFieldTable = wxT( "KICAD_LIB_FIELD_TABLE" );
|
||||
|
||||
|
||||
wxString dump( const wxArrayString& aArray )
|
||||
|
@ -174,6 +174,10 @@ void VIEW_GROUP::ViewDraw( int aLayer, VIEW* aView ) const
|
||||
{
|
||||
draw = aView->IsLayerVisible( layer - LAYER_CLEARANCE_START );
|
||||
}
|
||||
else if( IsPointsLayer( layer ) )
|
||||
{
|
||||
draw = aView->IsLayerVisible( layer - LAYER_POINT_START );
|
||||
}
|
||||
|
||||
if( isSelection )
|
||||
{
|
||||
|
@ -284,7 +284,7 @@ void BITMAP_BUTTON::OnDPIChanged( wxDPIChangedEvent& aEvent )
|
||||
invalidateBestSize();
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
// Don't skip, otherwise the button gets too big
|
||||
}
|
||||
|
||||
|
||||
|
@ -19,54 +19,225 @@
|
||||
|
||||
#include <widgets/font_choice.h>
|
||||
#include <kiplatform/ui.h>
|
||||
#include <wx/fontenum.h>
|
||||
#include <font/fontconfig.h>
|
||||
#include <pgm_base.h>
|
||||
|
||||
#include <wx/dc.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/fontenum.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/textctrl.h>
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <cwctype>
|
||||
|
||||
// The "official" name of the building Kicad stroke font (always existing)
|
||||
#include <font/kicad_font_name.h>
|
||||
|
||||
|
||||
FONT_CHOICE::FONT_CHOICE( wxWindow* aParent, int aId, wxPoint aPosition, wxSize aSize,
|
||||
int nChoices, wxString* aChoices, int aStyle ) :
|
||||
wxChoice( aParent, aId, aPosition, aSize, nChoices, aChoices, aStyle )
|
||||
class FONT_LIST_MANAGER : public wxEvtHandler
|
||||
{
|
||||
m_systemFontCount = wxChoice::GetCount();
|
||||
public:
|
||||
static FONT_LIST_MANAGER& Get();
|
||||
wxArrayString GetFonts() const;
|
||||
void Register( FONT_CHOICE* aCtrl );
|
||||
void Unregister( FONT_CHOICE* aCtrl );
|
||||
|
||||
private:
|
||||
FONT_LIST_MANAGER();
|
||||
~FONT_LIST_MANAGER();
|
||||
void Poll();
|
||||
void UpdateFonts();
|
||||
|
||||
std::thread m_thread;
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
wxArrayString m_fonts;
|
||||
std::vector<FONT_CHOICE*> m_controls;
|
||||
std::atomic<bool> m_quit;
|
||||
};
|
||||
|
||||
FONT_LIST_MANAGER& FONT_LIST_MANAGER::Get()
|
||||
{
|
||||
static FONT_LIST_MANAGER mgr;
|
||||
return mgr;
|
||||
}
|
||||
|
||||
wxArrayString FONT_LIST_MANAGER::GetFonts() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_mutex );
|
||||
return m_fonts;
|
||||
}
|
||||
|
||||
void FONT_LIST_MANAGER::Register( FONT_CHOICE* aCtrl )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_mutex );
|
||||
m_controls.push_back( aCtrl );
|
||||
}
|
||||
|
||||
void FONT_LIST_MANAGER::Unregister( FONT_CHOICE* aCtrl )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_mutex );
|
||||
auto it = std::find( m_controls.begin(), m_controls.end(), aCtrl );
|
||||
|
||||
if( it != m_controls.end() )
|
||||
m_controls.erase( it );
|
||||
}
|
||||
|
||||
FONT_LIST_MANAGER::FONT_LIST_MANAGER()
|
||||
{
|
||||
m_quit = false;
|
||||
UpdateFonts();
|
||||
|
||||
// It appears that the polling mechanism does not work correctly
|
||||
// for mingw (hangs on exit)
|
||||
#ifndef __MINGW32__
|
||||
m_thread = std::thread( &FONT_LIST_MANAGER::Poll, this );
|
||||
#endif
|
||||
}
|
||||
|
||||
FONT_LIST_MANAGER::~FONT_LIST_MANAGER()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_mutex );
|
||||
m_quit = true;
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
m_cv.notify_one();
|
||||
|
||||
if( m_thread.joinable() )
|
||||
m_thread.join();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FONT_LIST_MANAGER::Poll()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock( m_mutex );
|
||||
|
||||
while( !m_quit )
|
||||
{
|
||||
// N.B. wait_for will unlock the mutex while waiting but lock it before continuing
|
||||
// so we need to relock before continuing in the loop
|
||||
m_cv.wait_for( lock, std::chrono::seconds( 30 ), [&] { return m_quit.load(); } );
|
||||
|
||||
if( !m_quit )
|
||||
{
|
||||
lock.unlock();
|
||||
UpdateFonts();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FONT_LIST_MANAGER::UpdateFonts()
|
||||
{
|
||||
std::vector<std::string> fontNames;
|
||||
Fontconfig()->ListFonts( fontNames, std::string( Pgm().GetLanguageTag().utf8_str() ) );
|
||||
|
||||
wxArrayString menuList;
|
||||
|
||||
// The initial list of fonts has on top 1 or 2 options
|
||||
// only "KiCad Font" (KICAD_FONT_NAME)
|
||||
// "Default Font" and "KiCad Font" (KICAD_FONT_NAME)
|
||||
// "KiCad Font" is also a keyword, and cannot be translated.
|
||||
// So rebuilt the starting list
|
||||
wxChoice::Clear();
|
||||
|
||||
if( m_systemFontCount > 1 )
|
||||
Append( _( "Default Font" ) );
|
||||
|
||||
Append( KICAD_FONT_NAME );
|
||||
m_systemFontCount = wxChoice::GetCount();
|
||||
|
||||
for( const std::string& name : fontNames )
|
||||
menuList.Add( wxString( name ) );
|
||||
|
||||
menuList.Sort();
|
||||
|
||||
Freeze();
|
||||
Append( menuList );
|
||||
KIPLATFORM::UI::LargeChoiceBoxHack( this );
|
||||
Thaw();
|
||||
// Check if fonts changed and update controls
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_mutex );
|
||||
|
||||
if( menuList == m_fonts )
|
||||
return;
|
||||
|
||||
m_fonts = menuList;
|
||||
}
|
||||
|
||||
CallAfter( [this]() {
|
||||
std::vector<FONT_CHOICE*> controlsCopy;
|
||||
|
||||
// Copy controls list under lock protection
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( m_mutex );
|
||||
controlsCopy = m_controls;
|
||||
}
|
||||
|
||||
// Update controls without holding lock
|
||||
for( FONT_CHOICE* ctrl : controlsCopy )
|
||||
{
|
||||
if( ctrl && !ctrl->IsShownOnScreen() )
|
||||
ctrl->RefreshFonts();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
FONT_CHOICE::FONT_CHOICE( wxWindow* aParent, int aId, wxPoint aPosition, wxSize aSize,
|
||||
int nChoices, wxString* aChoices, int aStyle ) :
|
||||
wxOwnerDrawnComboBox( aParent, aId, wxEmptyString, aPosition, aSize, 0, nullptr, aStyle )
|
||||
{
|
||||
m_systemFontCount = nChoices;
|
||||
m_notFound = wxS( " " ) + _( "<not found>" );
|
||||
m_isFiltered = false;
|
||||
m_lastText = wxEmptyString;
|
||||
m_originalSelection = wxEmptyString;
|
||||
|
||||
FONT_LIST_MANAGER::Get().Register( this );
|
||||
RefreshFonts();
|
||||
|
||||
// Bind only essential events to restore functionality
|
||||
Bind( wxEVT_KEY_DOWN, &FONT_CHOICE::OnKeyDown, this );
|
||||
Bind( wxEVT_CHAR_HOOK, &FONT_CHOICE::OnCharHook, this );
|
||||
Bind( wxEVT_COMMAND_TEXT_UPDATED, &FONT_CHOICE::OnTextCtrl, this );
|
||||
Bind( wxEVT_COMBOBOX_DROPDOWN, &FONT_CHOICE::OnDropDown, this );
|
||||
Bind( wxEVT_COMBOBOX_CLOSEUP, &FONT_CHOICE::OnCloseUp, this );
|
||||
Bind( wxEVT_SET_FOCUS, &FONT_CHOICE::OnSetFocus, this );
|
||||
Bind( wxEVT_KILL_FOCUS, &FONT_CHOICE::OnKillFocus, this );
|
||||
}
|
||||
|
||||
|
||||
FONT_CHOICE::~FONT_CHOICE()
|
||||
{
|
||||
FONT_LIST_MANAGER::Get().Unregister( this );
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::RefreshFonts()
|
||||
{
|
||||
wxArrayString menuList = FONT_LIST_MANAGER::Get().GetFonts();
|
||||
|
||||
wxString selection = GetValue();
|
||||
|
||||
// Store the full font list for filtering
|
||||
m_fullFontList.Clear();
|
||||
|
||||
if( m_systemFontCount > 1 )
|
||||
m_fullFontList.Add( _( "Default Font" ) );
|
||||
|
||||
m_fullFontList.Add( KICAD_FONT_NAME );
|
||||
|
||||
for( const wxString& font : menuList )
|
||||
m_fullFontList.Add( font );
|
||||
|
||||
Freeze();
|
||||
Clear();
|
||||
|
||||
if( m_systemFontCount > 1 )
|
||||
Append( _( "Default Font" ) );
|
||||
|
||||
Append( KICAD_FONT_NAME );
|
||||
m_systemFontCount = GetCount();
|
||||
|
||||
Append( menuList );
|
||||
|
||||
if( !selection.IsEmpty() )
|
||||
SetStringSelection( selection );
|
||||
|
||||
m_isFiltered = false;
|
||||
Thaw();
|
||||
}
|
||||
|
||||
|
||||
@ -86,9 +257,6 @@ void FONT_CHOICE::SetFontSelection( KIFONT::FONT* aFont, bool aSilentMode )
|
||||
SetSelection( GetCount() - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
if( !aSilentMode )
|
||||
SendSelectionChangedEvent( wxEVT_CHOICE );
|
||||
}
|
||||
|
||||
|
||||
@ -118,8 +286,602 @@ KIFONT::FONT* FONT_CHOICE::GetFontSelection( bool aBold, bool aItalic, bool aFor
|
||||
}
|
||||
else
|
||||
{
|
||||
return KIFONT::FONT::GetFont( GetStringSelection(), aBold, aItalic, nullptr,
|
||||
return KIFONT::FONT::GetFont( GetValue(), aBold, aItalic, nullptr,
|
||||
aForDrawingSheet );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnDrawItem( wxDC& dc, const wxRect& rect, int item, int flags ) const
|
||||
{
|
||||
if( item == wxNOT_FOUND )
|
||||
return;
|
||||
|
||||
wxString name = GetString( item );
|
||||
|
||||
dc.SetFont( wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ) );
|
||||
dc.DrawText( name, rect.x + 2, rect.y + 2 );
|
||||
|
||||
if( item >= m_systemFontCount )
|
||||
{
|
||||
wxCoord w, h;
|
||||
dc.GetTextExtent( name, &w, &h );
|
||||
wxFont sampleFont( wxFontInfo( dc.GetFont().GetPointSize() ).FaceName( name ) );
|
||||
dc.SetFont( sampleFont );
|
||||
dc.SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
|
||||
dc.DrawText( wxS( "AaBbCcDd123456" ), rect.x + w + 15, rect.y + 2 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxString FONT_CHOICE::GetStringSelection() const
|
||||
{
|
||||
return GetValue();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnKeyDown( wxKeyEvent& aEvent )
|
||||
{
|
||||
int keyCode = aEvent.GetKeyCode();
|
||||
|
||||
if( keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER || keyCode == WXK_ESCAPE )
|
||||
{
|
||||
if( IsPopupShown() )
|
||||
{
|
||||
// Accept current text and close popup
|
||||
Dismiss();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if( keyCode == WXK_BACK && !IsPopupShown() )
|
||||
{
|
||||
// Handle backspace when popup is not shown
|
||||
// This allows normal character-by-character deletion instead of selecting all text
|
||||
|
||||
wxString currentText = GetValue();
|
||||
long selStart, selEnd;
|
||||
GetSelection( &selStart, &selEnd );
|
||||
|
||||
if( selStart != selEnd )
|
||||
{
|
||||
// There's a selection - delete the selected text
|
||||
wxString newText = currentText.Left( selStart ) + currentText.Mid( selEnd );
|
||||
m_lastText = newText; // Prevent recursive calls
|
||||
ChangeValue( newText );
|
||||
SetInsertionPoint( selStart );
|
||||
return; // Don't skip this event
|
||||
}
|
||||
else if( selStart > 0 )
|
||||
{
|
||||
// No selection - delete character before cursor
|
||||
wxString newText = currentText.Left( selStart - 1 ) + currentText.Mid( selStart );
|
||||
m_lastText = newText; // Prevent recursive calls
|
||||
ChangeValue( newText );
|
||||
SetInsertionPoint( selStart - 1 );
|
||||
return; // Don't skip this event
|
||||
}
|
||||
// If at beginning of text, let default behavior handle it
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnCharHook( wxKeyEvent& aEvent )
|
||||
{
|
||||
int keyCode = aEvent.GetUnicodeKey();
|
||||
wchar_t wc = static_cast<wchar_t>( keyCode );
|
||||
|
||||
// When popup is not shown, let normal text processing handle printable characters
|
||||
// The OnTextCtrl method will handle filtering and autocomplete
|
||||
if( !IsPopupShown() )
|
||||
{
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
if( std::iswprint( wc ) && !std::iswcntrl( wc ) )
|
||||
{
|
||||
// Get current text and check if there's a selection
|
||||
wxString currentText = GetValue();
|
||||
long selStart, selEnd;
|
||||
GetSelection( &selStart, &selEnd );
|
||||
|
||||
wxChar newChar = (wxChar)keyCode;
|
||||
wxString newText;
|
||||
|
||||
if( selStart != selEnd )
|
||||
{
|
||||
// There's a selection - replace it with the new character
|
||||
newText = currentText.Left( selStart ) + newChar + currentText.Mid( selEnd );
|
||||
}
|
||||
else
|
||||
{
|
||||
// No selection - append to current insertion point
|
||||
long insertionPoint = GetInsertionPoint();
|
||||
newText = currentText.Left( insertionPoint ) + newChar + currentText.Mid( insertionPoint );
|
||||
}
|
||||
|
||||
// Update the text control
|
||||
m_lastText = newText; // Prevent recursive calls
|
||||
ChangeValue( newText );
|
||||
SetInsertionPoint( selStart + 1 ); // Position cursor after new character
|
||||
|
||||
// Filter the font list based on new text (will handle trimming internally)
|
||||
FilterFontList( newText );
|
||||
|
||||
// Try autocomplete
|
||||
DoAutoComplete( newText );
|
||||
|
||||
return; // Don't skip this event
|
||||
}
|
||||
|
||||
switch (keyCode)
|
||||
{
|
||||
case WXK_BACK:
|
||||
{
|
||||
wxString currentText = GetValue();
|
||||
long selStart, selEnd;
|
||||
GetSelection( &selStart, &selEnd );
|
||||
|
||||
wxString newText;
|
||||
long newInsertionPoint;
|
||||
|
||||
if( selStart != selEnd )
|
||||
{
|
||||
// There's a selection - delete the selected text
|
||||
newText = currentText.Left( selStart ) + currentText.Mid( selEnd );
|
||||
newInsertionPoint = selStart;
|
||||
}
|
||||
else if( selStart > 0 )
|
||||
{
|
||||
// No selection - delete character before cursor
|
||||
newText = currentText.Left( selStart - 1 ) + currentText.Mid( selStart );
|
||||
newInsertionPoint = selStart - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // At beginning, can't delete
|
||||
}
|
||||
|
||||
m_lastText = newText; // Prevent recursive calls
|
||||
ChangeValue( newText );
|
||||
SetInsertionPoint( newInsertionPoint );
|
||||
|
||||
// Check if trimmed text is empty
|
||||
wxString trimmedNewText = newText;
|
||||
trimmedNewText.Trim().Trim( false );
|
||||
|
||||
if( trimmedNewText.IsEmpty() )
|
||||
{
|
||||
RestoreFullFontList();
|
||||
}
|
||||
else
|
||||
{
|
||||
FilterFontList( newText );
|
||||
// Don't call DoAutoComplete for backspace to avoid the loop
|
||||
}
|
||||
|
||||
return; // Don't skip this event
|
||||
}
|
||||
case WXK_RETURN:
|
||||
case WXK_NUMPAD_ENTER:
|
||||
{
|
||||
Dismiss();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case WXK_ESCAPE:
|
||||
{
|
||||
// Restore to original selection or default font if original doesn't exist
|
||||
if( !m_originalSelection.IsEmpty() && FindBestMatch( m_originalSelection ) != wxNOT_FOUND )
|
||||
{
|
||||
SetStringSelection( m_originalSelection );
|
||||
m_lastText = m_originalSelection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Original font doesn't exist anymore, select default font
|
||||
wxString defaultFont = GetDefaultFontName();
|
||||
SetStringSelection( defaultFont );
|
||||
m_lastText = defaultFont;
|
||||
}
|
||||
|
||||
// Restore full font list if filtered
|
||||
if( m_isFiltered )
|
||||
{
|
||||
RestoreFullFontList();
|
||||
}
|
||||
|
||||
// Only dismiss if popup is actually shown
|
||||
if( IsPopupShown() )
|
||||
{
|
||||
Dismiss();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnTextCtrl( wxCommandEvent& aEvent )
|
||||
{
|
||||
wxString currentText = GetValue();
|
||||
|
||||
// Avoid recursive calls
|
||||
if( currentText == m_lastText )
|
||||
{
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastText = currentText;
|
||||
|
||||
// If popup is shown, OnCharHook handles the text input, so just skip
|
||||
if( IsPopupShown() )
|
||||
{
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Trim whitespace for processing
|
||||
wxString trimmedText = currentText;
|
||||
trimmedText.Trim().Trim(false);
|
||||
|
||||
// If text is empty or all whitespace, restore full list
|
||||
if( trimmedText.IsEmpty() )
|
||||
{
|
||||
RestoreFullFontList();
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter the font list based on the text input
|
||||
FilterFontList( currentText );
|
||||
|
||||
// Try to find a match for autocomplete (only when popup is not shown)
|
||||
int bestMatch = FindBestMatch( trimmedText );
|
||||
|
||||
if( bestMatch != wxNOT_FOUND )
|
||||
{
|
||||
DoAutoComplete( trimmedText );
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnDropDown( wxCommandEvent& aEvent )
|
||||
{
|
||||
// Store the original selection when dropdown opens
|
||||
m_originalSelection = GetValue();
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnCloseUp( wxCommandEvent& aEvent )
|
||||
{
|
||||
// When dropdown closes, we should only restore the full font list
|
||||
// but NOT change the current text value unless explicitly selected
|
||||
// The OnKillFocus handler will handle text validation when focus is lost
|
||||
|
||||
// Reset to full font list if filtered
|
||||
if( m_isFiltered )
|
||||
{
|
||||
RestoreFullFontList();
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnSetFocus( wxFocusEvent& aEvent )
|
||||
{
|
||||
// When the control gains focus, select all text so user can quickly replace it
|
||||
// Only do this if we're not already showing the popup (which would indicate
|
||||
// the user is actively interacting with the dropdown)
|
||||
if( !GetValue().IsEmpty() && !IsPopupShown() )
|
||||
{
|
||||
// Use CallAfter to ensure the focus event is fully processed first
|
||||
CallAfter( [this]() {
|
||||
if( HasFocus() && !IsPopupShown() )
|
||||
SelectAll();
|
||||
} );
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::OnKillFocus( wxFocusEvent& aEvent )
|
||||
{
|
||||
// When losing focus, deselect text and validate/correct the font name
|
||||
|
||||
// First, deselect any selected text
|
||||
if( GetInsertionPoint() != GetLastPosition() )
|
||||
{
|
||||
SetInsertionPointEnd();
|
||||
}
|
||||
|
||||
// Get current text and trim whitespace
|
||||
wxString currentText = GetValue();
|
||||
currentText.Trim().Trim(false);
|
||||
|
||||
// If text is empty, set to default font
|
||||
if( currentText.IsEmpty() )
|
||||
{
|
||||
wxString defaultFont = GetDefaultFontName();
|
||||
SetStringSelection( defaultFont );
|
||||
m_lastText = defaultFont;
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to find exact match first
|
||||
if( FindBestMatch( currentText ) != wxNOT_FOUND )
|
||||
{
|
||||
// Exact match found, keep the current text but ensure it's properly set
|
||||
SetStringSelection( currentText );
|
||||
m_lastText = currentText;
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// No exact match, try to find best partial match
|
||||
wxString partialMatch = FindBestPartialMatch( currentText );
|
||||
|
||||
if( !partialMatch.IsEmpty() )
|
||||
{
|
||||
SetStringSelection( partialMatch );
|
||||
m_lastText = partialMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No decent partial match, fall back to default font
|
||||
wxString defaultFont = GetDefaultFontName();
|
||||
SetStringSelection( defaultFont );
|
||||
m_lastText = defaultFont;
|
||||
}
|
||||
|
||||
// Ensure we restore full font list if it was filtered
|
||||
if( m_isFiltered )
|
||||
{
|
||||
RestoreFullFontList();
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::DoAutoComplete( const wxString& aText )
|
||||
{
|
||||
if( aText.IsEmpty() )
|
||||
return;
|
||||
|
||||
// Find the best matching font
|
||||
int bestMatch = FindBestMatch( aText );
|
||||
|
||||
if( bestMatch == wxNOT_FOUND )
|
||||
return;
|
||||
|
||||
|
||||
wxString matchText = GetString( bestMatch );
|
||||
|
||||
// Only do autocomplete if the match is longer than what we typed
|
||||
if( matchText.Length() > aText.Length() && matchText.Lower().StartsWith( aText.Lower() ) )
|
||||
{
|
||||
// Set the text with the autocompleted portion selected
|
||||
m_lastText = matchText; // Update to prevent recursive calls
|
||||
ChangeValue( matchText );
|
||||
SetInsertionPoint( aText.Length() );
|
||||
SetSelection( aText.Length(), matchText.Length() );
|
||||
|
||||
if( IsPopupShown() )
|
||||
{
|
||||
SetSelection( bestMatch );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::FilterFontList( const wxString& aFilter )
|
||||
{
|
||||
// Trim whitespace from filter
|
||||
wxString trimmedFilter = aFilter;
|
||||
trimmedFilter.Trim().Trim(false);
|
||||
|
||||
if( trimmedFilter.IsEmpty() )
|
||||
{
|
||||
RestoreFullFontList();
|
||||
return;
|
||||
}
|
||||
|
||||
wxArrayString filteredList;
|
||||
|
||||
// Add system fonts first
|
||||
for( int i = 0; i < m_systemFontCount; i++ )
|
||||
{
|
||||
wxString fontName = m_fullFontList[i];
|
||||
|
||||
if( fontName.Lower().StartsWith( trimmedFilter.Lower() ) )
|
||||
filteredList.Add( fontName );
|
||||
}
|
||||
|
||||
// Add matching fonts from the full list
|
||||
for( size_t i = m_systemFontCount; i < m_fullFontList.GetCount(); i++ )
|
||||
{
|
||||
wxString fontName = m_fullFontList[i];
|
||||
|
||||
if( fontName.Lower().StartsWith( trimmedFilter.Lower() ) )
|
||||
filteredList.Add( fontName );
|
||||
}
|
||||
|
||||
// Preserve the current text value
|
||||
wxString currentText = GetValue();
|
||||
|
||||
// Check if we had items before and now have none - this indicates we need to force refresh
|
||||
bool hadItemsBefore = GetCount() > 0;
|
||||
bool haveItemsNow = filteredList.GetCount() > 0;
|
||||
bool needsPopupRefresh = hadItemsBefore && !haveItemsNow && IsPopupShown();
|
||||
|
||||
// Update the combo box with filtered list (even if empty)
|
||||
Freeze();
|
||||
Clear();
|
||||
|
||||
if( haveItemsNow )
|
||||
{
|
||||
Append( filteredList );
|
||||
}
|
||||
// If no matches, leave the dropdown empty
|
||||
|
||||
m_isFiltered = true;
|
||||
|
||||
// Restore the text value after filtering
|
||||
if( !currentText.IsEmpty() )
|
||||
{
|
||||
ChangeValue( currentText );
|
||||
SetInsertionPointEnd();
|
||||
}
|
||||
|
||||
Thaw();
|
||||
|
||||
// Handle popup display
|
||||
if( needsPopupRefresh )
|
||||
{
|
||||
// We had items before but now have none - dismiss the popup
|
||||
Dismiss();
|
||||
}
|
||||
else if( !IsPopupShown() && haveItemsNow )
|
||||
{
|
||||
// Only show popup if we have items to display and control has focus
|
||||
// This prevents popup from showing during programmatic text changes
|
||||
if( HasFocus() )
|
||||
{
|
||||
Popup();
|
||||
}
|
||||
}
|
||||
else if( IsPopupShown() && !haveItemsNow )
|
||||
{
|
||||
// If popup is shown but we have no items, dismiss it
|
||||
Dismiss();
|
||||
}
|
||||
|
||||
// Force a refresh to ensure the popup displays correctly only if it's shown and has items
|
||||
if( IsPopupShown() && haveItemsNow )
|
||||
{
|
||||
Update();
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FONT_CHOICE::RestoreFullFontList()
|
||||
{
|
||||
if( !m_isFiltered )
|
||||
return;
|
||||
|
||||
wxString selection = GetValue();
|
||||
|
||||
Freeze();
|
||||
Clear();
|
||||
Append( m_fullFontList );
|
||||
m_isFiltered = false;
|
||||
|
||||
if( !selection.IsEmpty() )
|
||||
{
|
||||
ChangeValue( selection );
|
||||
SetInsertionPointEnd();
|
||||
}
|
||||
|
||||
Thaw();
|
||||
}
|
||||
|
||||
|
||||
int FONT_CHOICE::FindBestMatch( const wxString& aText )
|
||||
{
|
||||
if( aText.IsEmpty() )
|
||||
return wxNOT_FOUND;
|
||||
|
||||
// Trim whitespace from search text
|
||||
wxString trimmedText = aText;
|
||||
trimmedText.Trim().Trim(false);
|
||||
|
||||
if( trimmedText.IsEmpty() )
|
||||
return wxNOT_FOUND;
|
||||
|
||||
wxString lowerText = trimmedText.Lower();
|
||||
|
||||
// Search in the full font list to find matches, then map to current list
|
||||
for( size_t i = 0; i < m_fullFontList.GetCount(); i++ )
|
||||
{
|
||||
wxString itemText = m_fullFontList[i].Lower();
|
||||
|
||||
if( itemText.StartsWith( lowerText ) )
|
||||
{
|
||||
// Find this font in the current displayed list
|
||||
wxString fullFontName = m_fullFontList[i];
|
||||
|
||||
for( unsigned int j = 0; j < GetCount(); j++ )
|
||||
{
|
||||
if( GetString( j ) == fullFontName )
|
||||
return j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wxNOT_FOUND;
|
||||
}
|
||||
|
||||
|
||||
wxString FONT_CHOICE::FindBestPartialMatch( const wxString& aText )
|
||||
{
|
||||
if( aText.IsEmpty() )
|
||||
return wxEmptyString;
|
||||
|
||||
// Trim whitespace from search text
|
||||
wxString trimmedText = aText;
|
||||
trimmedText.Trim().Trim(false);
|
||||
|
||||
if( trimmedText.IsEmpty() )
|
||||
return wxEmptyString;
|
||||
|
||||
wxString testText = trimmedText;
|
||||
|
||||
// Try progressively shorter versions of the text by removing characters from the end
|
||||
// Don't go below a minimum length of 2 characters for meaningful partial matches
|
||||
while( testText.Length() >= 2 )
|
||||
{
|
||||
wxString lowerTestText = testText.Lower();
|
||||
|
||||
// Search in the full font list for a match
|
||||
for( size_t i = 0; i < m_fullFontList.GetCount(); i++ )
|
||||
{
|
||||
wxString itemText = m_fullFontList[i].Lower();
|
||||
|
||||
if( itemText.StartsWith( lowerTestText ) )
|
||||
{
|
||||
// Found a match, return the full font name
|
||||
return m_fullFontList[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the last character and try again
|
||||
testText = testText.Left( testText.Length() - 1 );
|
||||
}
|
||||
|
||||
// No decent partial match found (need at least 2 characters for a meaningful match)
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
|
||||
wxString FONT_CHOICE::GetDefaultFontName() const
|
||||
{
|
||||
// Return KiCad font name as the default
|
||||
return KICAD_FONT_NAME;
|
||||
}
|
47
common/widgets/grid_striped_renderer.cpp
Normal file
47
common/widgets/grid_striped_renderer.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
#include <widgets/grid_striped_renderer.h>
|
||||
|
||||
void STRIPED_CELL_RENDERER::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc,
|
||||
const wxRect& rect, int row, int col, bool isSelected)
|
||||
{
|
||||
// First draw the striped background for empty cells
|
||||
wxString cellValue = grid.GetCellValue(row, col);
|
||||
if (cellValue.IsEmpty())
|
||||
{
|
||||
DrawStripedBackground(dc, rect, isSelected);
|
||||
}
|
||||
|
||||
// Then draw the text content using the parent class
|
||||
wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
|
||||
}
|
||||
|
||||
void STRIPED_CELL_RENDERER::drawStripedBackground(wxDC& dc, const wxRect& rect, bool isSelected) const
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
// For selected cells, use the selection color
|
||||
dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)));
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.DrawRectangle(rect);
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw horizontal stripes
|
||||
const int stripeHeight = 3;
|
||||
wxColour color1 = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
wxColour color2(240, 240, 240); // Light gray
|
||||
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
|
||||
bool useColor1 = true;
|
||||
for (int y = rect.GetTop(); y < rect.GetBottom(); y += stripeHeight)
|
||||
{
|
||||
wxColour currentColor = useColor1 ? color1 : color2;
|
||||
dc.SetBrush(wxBrush(currentColor));
|
||||
|
||||
int stripeBottom = wxMin(y + stripeHeight, rect.GetBottom());
|
||||
dc.DrawRectangle(rect.GetLeft(), y, rect.GetWidth(), stripeBottom - y);
|
||||
|
||||
useColor1 = !useColor1;
|
||||
}
|
||||
}
|
@ -640,15 +640,36 @@ void LIB_TREE::onQueryCharHook( wxKeyEvent& aKeyStroke )
|
||||
{
|
||||
int hotkey = aKeyStroke.GetKeyCode();
|
||||
|
||||
if( aKeyStroke.GetModifiers() & wxMOD_CONTROL )
|
||||
int mods = aKeyStroke.GetModifiers();
|
||||
|
||||
// the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
|
||||
// So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
|
||||
#if CAN_USE_ALTGR_KEY
|
||||
if( wxmods & wxMOD_ALTGR )
|
||||
mods |= MD_ALTGR;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if( mods & wxMOD_CONTROL )
|
||||
hotkey += MD_CTRL;
|
||||
|
||||
if( aKeyStroke.GetModifiers() & wxMOD_ALT )
|
||||
if( mods & wxMOD_ALT )
|
||||
hotkey += MD_ALT;
|
||||
}
|
||||
|
||||
if( aKeyStroke.GetModifiers() & wxMOD_SHIFT )
|
||||
if( mods & wxMOD_SHIFT )
|
||||
hotkey += MD_SHIFT;
|
||||
|
||||
#ifdef wxMOD_META
|
||||
if( mods & wxMOD_META )
|
||||
hotkey += MD_META;
|
||||
#endif
|
||||
|
||||
#ifdef wxMOD_WIN
|
||||
if( mods & wxMOD_WIN )
|
||||
hotkey += MD_SUPER;
|
||||
#endif
|
||||
|
||||
if( hotkey == ACTIONS::expandAll.GetHotKey()
|
||||
|| hotkey == ACTIONS::expandAll.GetHotKeyAlt() )
|
||||
{
|
||||
@ -860,14 +881,31 @@ void LIB_TREE::onTreeCharHook( wxKeyEvent& aKeyStroke )
|
||||
{
|
||||
int hotkey = aKeyStroke.GetKeyCode();
|
||||
|
||||
if( aKeyStroke.ShiftDown() )
|
||||
hotkey |= MD_SHIFT;
|
||||
int mods = aKeyStroke.GetModifiers();
|
||||
|
||||
if( aKeyStroke.AltDown() )
|
||||
if( mods & wxMOD_ALTGR )
|
||||
hotkey |= MD_ALTGR;
|
||||
else
|
||||
{
|
||||
if( mods & wxMOD_ALT )
|
||||
hotkey |= MD_ALT;
|
||||
|
||||
if( aKeyStroke.ControlDown() )
|
||||
if( mods & wxMOD_CONTROL )
|
||||
hotkey |= MD_CTRL;
|
||||
}
|
||||
|
||||
if( mods & wxMOD_SHIFT )
|
||||
hotkey |= MD_SHIFT;
|
||||
|
||||
#ifdef wxMOD_META
|
||||
if( mods & wxMOD_META )
|
||||
hotkey |= MD_META;
|
||||
#endif
|
||||
|
||||
#ifdef wxMOD_WIN
|
||||
if( mods & wxMOD_WIN )
|
||||
hotkey |= MD_SUPER;
|
||||
#endif
|
||||
|
||||
if( tool->GetManager()->GetActionManager()->RunHotKey( hotkey ) )
|
||||
aKeyStroke.Skip( false );
|
||||
|
@ -235,7 +235,11 @@ void UNIT_BINDER::onComboBox( wxCommandEvent& aEvent )
|
||||
const wxString value = combo->GetStringSelection();
|
||||
const long long int conv = ValueFromString( *m_iuScale, m_units, value, m_dataType );
|
||||
|
||||
CallAfter(
|
||||
[this, conv]
|
||||
{
|
||||
SetValue( conv );
|
||||
} );
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
@ -670,7 +670,7 @@ long WIDGET_HOTKEY_LIST::MapKeypressToKeycode( const wxKeyEvent& aEvent )
|
||||
{
|
||||
long key = aEvent.GetKeyCode();
|
||||
bool is_tab = aEvent.IsKeyInCategory( WXK_CATEGORY_TAB );
|
||||
|
||||
printf("key %lX mod %X\n", key, aEvent.GetModifiers());
|
||||
if( key == WXK_ESCAPE )
|
||||
{
|
||||
return 0;
|
||||
@ -693,14 +693,35 @@ long WIDGET_HOTKEY_LIST::MapKeypressToKeycode( const wxKeyEvent& aEvent )
|
||||
*/
|
||||
bool keyIsLetter = key >= 'A' && key <= 'Z';
|
||||
|
||||
if( aEvent.ShiftDown() && ( keyIsLetter || key > 256 || key == 9 || key == 32 ) )
|
||||
int mods = aEvent.GetModifiers();
|
||||
|
||||
if( ( mods & wxMOD_SHIFT ) && ( keyIsLetter || key > 256 || key == 9 || key == 32 ) )
|
||||
key |= MD_SHIFT;
|
||||
|
||||
if( aEvent.ControlDown() )
|
||||
// the flag wxMOD_ALTGR is defined in wxWidgets as wxMOD_CONTROL|wxMOD_ALT
|
||||
// So AltGr key cannot used as modifier key because it is the same as Alt key + Ctrl key.
|
||||
#if CAN_USE_ALTGR_KEY
|
||||
if( wxmods & wxMOD_ALTGR )
|
||||
mods |= MD_ALTGR;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if( mods & wxMOD_CONTROL )
|
||||
key |= MD_CTRL;
|
||||
|
||||
if( aEvent.AltDown() )
|
||||
if( mods & wxMOD_ALT )
|
||||
key |= MD_ALT;
|
||||
}
|
||||
|
||||
#ifdef wxMOD_META
|
||||
if( mods & wxMOD_META )
|
||||
key |= MD_META;
|
||||
#endif
|
||||
|
||||
#ifdef wxMOD_WIN
|
||||
if( mods & wxMOD_WIN )
|
||||
key |= MD_SUPER;
|
||||
#endif
|
||||
|
||||
return key;
|
||||
}
|
||||
|
@ -76,8 +76,7 @@ wxGridCellAttr* WX_GRID_TABLE_BASE::enhanceAttr( wxGridCellAttr* aInputAttr, int
|
||||
|
||||
void WX_GRID::CellEditorSetMargins( wxTextEntryBase* aEntry )
|
||||
{
|
||||
// This is consistent with wxGridCellTextEditor. But works differently across platforms or
|
||||
// course.
|
||||
// This is consistent with wxGridCellTextEditor. But works differently across platforms of course.
|
||||
aEntry->SetMargins( 0, 0 );
|
||||
}
|
||||
|
||||
@ -202,13 +201,12 @@ private:
|
||||
};
|
||||
|
||||
|
||||
WX_GRID::WX_GRID( wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size,
|
||||
long style, const wxString& name ) :
|
||||
WX_GRID::WX_GRID( wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style,
|
||||
const wxString& name ) :
|
||||
wxGrid( parent, id, pos, size, style, name ),
|
||||
m_weOwnTable( false )
|
||||
{
|
||||
// Grids with comboboxes need a bit of extra height; other grids look better if they're
|
||||
// consistent.
|
||||
// Grids with comboboxes need a bit of extra height; other grids look better if they're consistent.
|
||||
SetDefaultRowSize( GetDefaultRowSize() + FromDIP( 4 ) );
|
||||
|
||||
SetDefaultCellOverflow( false );
|
||||
@ -241,11 +239,7 @@ void WX_GRID::onDPIChanged(wxDPIChangedEvent& aEvt)
|
||||
wxGrid::SetColLabelSize( wxGRID_AUTOSIZE );
|
||||
} );
|
||||
|
||||
/// This terrible hack is a way to avoid the incredibly disruptive resizing of grids that
|
||||
/// happens on Macs when moving a window between monitors of different DPIs.
|
||||
#ifndef __WXMAC__
|
||||
aEvt.Skip();
|
||||
#endif
|
||||
// Don't skip, otherwise the grid gets too big
|
||||
}
|
||||
|
||||
|
||||
@ -422,8 +416,7 @@ void WX_GRID::onCellEditorHidden( wxGridEvent& aEvent )
|
||||
}
|
||||
else
|
||||
{
|
||||
val = unitsProvider->OptionalValueFromString( m_eval->Result(),
|
||||
cellDataType );
|
||||
val = unitsProvider->OptionalValueFromString( m_eval->Result(), cellDataType );
|
||||
}
|
||||
|
||||
evalValue = unitsProvider->StringFromOptionalValue( val, true, cellDataType );
|
||||
@ -518,8 +511,7 @@ void WX_GRID::DrawCornerLabel( wxDC& dc )
|
||||
|
||||
static WX_GRID_CORNER_HEADER_RENDERER rend;
|
||||
|
||||
// It is reported that we need to erase the background to avoid display
|
||||
// artifacts, see #12055.
|
||||
// It is reported that we need to erase the background to avoid display artifacts; see #12055.
|
||||
{
|
||||
wxDCBrushChanger setBrush( dc, m_colLabelWin->GetBackgroundColour() );
|
||||
wxDCPenChanger setPen( dc, m_colLabelWin->GetBackgroundColour() );
|
||||
@ -542,8 +534,7 @@ void WX_GRID::DrawColLabel( wxDC& dc, int col )
|
||||
|
||||
static WX_GRID_COLUMN_HEADER_RENDERER rend;
|
||||
|
||||
// It is reported that we need to erase the background to avoid display
|
||||
// artifacts, see #12055.
|
||||
// It is reported that we need to erase the background to avoid display artifacts; see #12055.
|
||||
{
|
||||
wxDCBrushChanger setBrush( dc, m_colLabelWin->GetBackgroundColour() );
|
||||
wxDCPenChanger setPen( dc, m_colLabelWin->GetBackgroundColour() );
|
||||
@ -578,8 +569,7 @@ void WX_GRID::DrawRowLabel( wxDC& dc, int row )
|
||||
|
||||
static WX_GRID_ROW_HEADER_RENDERER rend;
|
||||
|
||||
// It is reported that we need to erase the background to avoid display
|
||||
// artifacts, see #12055.
|
||||
// It is reported that we need to erase the background to avoid display artifacts; see #12055.
|
||||
{
|
||||
wxDCBrushChanger setBrush( dc, m_colLabelWin->GetBackgroundColour() );
|
||||
wxDCPenChanger setPen( dc, m_colLabelWin->GetBackgroundColour() );
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <eda_base_frame.h>
|
||||
|
||||
#ifdef __WXMSW__
|
||||
@ -240,7 +241,8 @@ void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
|
||||
// do we actually still need this for wx3.3?
|
||||
#if wxCHECK_VERSION( 3, 3, 0 )
|
||||
wxSizerItem* outerSizer = sizer->GetItem( (size_t) 0 );
|
||||
wxSizerItem* text = nullptr;
|
||||
wxSizerItem* textSizer = nullptr;
|
||||
|
||||
if (outerSizer->IsSizer())
|
||||
{
|
||||
wxBoxSizer* innerSizer1 = dynamic_cast<wxBoxSizer*>( outerSizer->GetSizer() );
|
||||
@ -248,15 +250,15 @@ void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
|
||||
dynamic_cast<wxBoxSizer*>( innerSizer1->GetItem((size_t)0)->GetSizer() );
|
||||
|
||||
if( innerSizer2 )
|
||||
text = innerSizer2->GetItem( 1 );
|
||||
textSizer = innerSizer2->GetItem( 1 );
|
||||
}
|
||||
#else
|
||||
wxSizerItem* text = sizer->GetItem( 1 );
|
||||
wxSizerItem* textSizer = sizer->GetItem( 1 );
|
||||
#endif
|
||||
|
||||
if( text )
|
||||
if( textSizer )
|
||||
{
|
||||
if( auto textCtrl = dynamic_cast<wxStaticText*>( text->GetWindow() ) )
|
||||
if( wxStaticText* textCtrl = dynamic_cast<wxStaticText*>( textSizer->GetWindow() ) )
|
||||
textCtrl->SetLabelText( m_message );
|
||||
}
|
||||
|
||||
@ -273,9 +275,9 @@ void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
|
||||
if( barWidth != parentWidth )
|
||||
SetSize( parentWidth, GetSize().GetHeight() );
|
||||
|
||||
if( text )
|
||||
if( textSizer )
|
||||
{
|
||||
if( auto textCtrl = dynamic_cast<wxStaticText*>( text->GetWindow() ) )
|
||||
if( wxStaticText* textCtrl = dynamic_cast<wxStaticText*>( textSizer->GetWindow() ) )
|
||||
{
|
||||
// Re-wrap the text (this is done automatically later but we need it now)
|
||||
// And count how many lines we need. If we have embedded newlines, then
|
||||
@ -285,9 +287,15 @@ void WX_INFOBAR::onSize( wxSizeEvent& aEvent )
|
||||
// than the height of the icon for the bar.
|
||||
textCtrl->Wrap( -1 );
|
||||
wxString wrapped_text = textCtrl->GetLabel();
|
||||
int height = ( wrapped_text.Freq( '\n' ) + 1 ) * text->GetMinSize().GetHeight();
|
||||
int margins = text->GetMinSize().GetHeight() - 1;
|
||||
int line_count = wrapped_text.Freq( '\n' ) + 1;
|
||||
int txt_h, txt_v;
|
||||
wxWindowDC dc( textCtrl );
|
||||
dc.GetTextExtent( wxT( "Xp" ), &txt_h, &txt_v );
|
||||
|
||||
int height = txt_v * line_count;
|
||||
int margins = txt_v - 1;
|
||||
SetMinSize( wxSize( GetSize().GetWidth(), height + margins ) );
|
||||
|
||||
textCtrl->Wrap( -1 );
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/ffile.h>
|
||||
|
||||
|
||||
CVPCB_MAINFRAME::CVPCB_MAINFRAME( KIWAY* aKiway, wxWindow* aParent ) :
|
||||
@ -796,7 +797,12 @@ void CVPCB_MAINFRAME::DisplayStatus()
|
||||
msg.Empty();
|
||||
|
||||
if( symbol )
|
||||
msg = wxString::Format( wxT( "%i" ), symbol->GetPinCount() );
|
||||
{
|
||||
int pc = symbol->GetPinCount();
|
||||
wxLogTrace( "CVPCB_PINCOUNT", wxT( "DisplayStatus: selected '%s' pinCount=%d" ),
|
||||
symbol->GetReference(), pc );
|
||||
msg = wxString::Format( wxT( "%i" ), pc );
|
||||
}
|
||||
|
||||
if( !filters.IsEmpty() )
|
||||
filters += wxT( ", " );
|
||||
@ -957,6 +963,12 @@ int CVPCB_MAINFRAME::readSchematicNetlist( const std::string& aNetlist )
|
||||
|
||||
m_netlist.Clear();
|
||||
|
||||
// Trace basic payload characteristics to verify libparts are present and visible here
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxT( "readSchematicNetlist: payload size=%zu has_libparts=%d has_libpart=%d" ),
|
||||
aNetlist.size(), aNetlist.find( "(libparts" ) != std::string::npos,
|
||||
aNetlist.find( "(libpart" ) != std::string::npos );
|
||||
|
||||
try
|
||||
{
|
||||
netlistReader.LoadNetlist();
|
||||
|
@ -132,7 +132,13 @@ void FOOTPRINTS_LISTBOX::SetFootprints( FOOTPRINT_LIST& aList, const wxString& a
|
||||
filter.FilterByFootprintFilters( aComponent->GetFootprintFilters() );
|
||||
|
||||
if( aFilterType & FILTERING_BY_PIN_COUNT && aComponent )
|
||||
filter.FilterByPinCount( aComponent->GetPinCount() );
|
||||
{
|
||||
int pc = aComponent->GetPinCount();
|
||||
wxLogTrace( "CVPCB_PINCOUNT",
|
||||
wxT( "FOOTPRINTS_LISTBOX::SetFootprints: ref='%s' pinCount filter=%d" ),
|
||||
aComponent->GetReference(), pc );
|
||||
filter.FilterByPinCount( pc );
|
||||
}
|
||||
|
||||
if( aFilterType & FILTERING_BY_LIBRARY )
|
||||
filter.FilterByLibrary( aLibName );
|
||||
|
@ -108,6 +108,7 @@
|
||||
"solder_mask_bridge": "warning",
|
||||
"starved_thermal": "warning",
|
||||
"text_height": "warning",
|
||||
"text_on_edge_cuts": "error",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "ignore",
|
||||
"too_many_vias": "ignore",
|
||||
@ -246,6 +247,15 @@
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"component_class_settings": {
|
||||
"assignments": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"sheet_component_classes": {
|
||||
"enabled": false
|
||||
}
|
||||
},
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
|
@ -122,6 +122,8 @@ set( EESCHEMA_DLGS
|
||||
dialogs/dialog_label_properties_base.cpp
|
||||
dialogs/dialog_lib_edit_pin_table.cpp
|
||||
dialogs/dialog_lib_edit_pin_table_base.cpp
|
||||
dialogs/dialog_lib_fields_table_base.cpp
|
||||
dialogs/dialog_lib_fields_table.cpp
|
||||
dialogs/dialog_lib_new_symbol.cpp
|
||||
dialogs/dialog_lib_new_symbol_base.cpp
|
||||
dialogs/dialog_lib_symbol_properties.cpp
|
||||
@ -365,6 +367,7 @@ set( EESCHEMA_SRCS
|
||||
generate_alias_info.cpp
|
||||
gfx_import_utils.cpp
|
||||
junction_helpers.cpp
|
||||
lib_fields_data_model.cpp
|
||||
lib_symbol.cpp
|
||||
libarch.cpp
|
||||
menubar.cpp
|
||||
|
@ -1614,10 +1614,9 @@ void CONNECTION_GRAPH::resolveAllDrivers()
|
||||
|
||||
thread_pool& tp = GetKiCadThreadPool();
|
||||
|
||||
auto results = tp.parallelize_loop( dirty_graphs.size(),
|
||||
[&]( const int a, const int b)
|
||||
auto results = tp.submit_loop( 0, dirty_graphs.size(),
|
||||
[&]( const int ii )
|
||||
{
|
||||
for( int ii = a; ii < b; ++ii )
|
||||
update_lambda( dirty_graphs[ii] );
|
||||
});
|
||||
results.wait();
|
||||
@ -2257,10 +2256,9 @@ void CONNECTION_GRAPH::buildConnectionGraph( std::function<void( SCH_ITEM* )>* a
|
||||
|
||||
thread_pool& tp = GetKiCadThreadPool();
|
||||
|
||||
auto results = tp.parallelize_loop( m_driver_subgraphs.size(),
|
||||
[&]( const int a, const int b)
|
||||
auto results = tp.submit_loop( 0, m_driver_subgraphs.size(),
|
||||
[&]( const int ii )
|
||||
{
|
||||
for( int ii = a; ii < b; ++ii )
|
||||
m_driver_subgraphs[ii]->UpdateItemConnections();
|
||||
});
|
||||
|
||||
@ -2464,10 +2462,9 @@ void CONNECTION_GRAPH::buildConnectionGraph( std::function<void( SCH_ITEM* )>* a
|
||||
return 1;
|
||||
};
|
||||
|
||||
auto results2 = tp.parallelize_loop( m_driver_subgraphs.size(),
|
||||
[&]( const int a, const int b)
|
||||
auto results2 = tp.submit_loop( 0, m_driver_subgraphs.size(),
|
||||
[&]( const int ii )
|
||||
{
|
||||
for( int ii = a; ii < b; ++ii )
|
||||
updateItemConnectionsTask( m_driver_subgraphs[ii] );
|
||||
} );
|
||||
results2.wait();
|
||||
|
@ -931,6 +931,26 @@ void SCH_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
|
||||
items );
|
||||
|
||||
m_syncingPcbToSchSelection = false;
|
||||
|
||||
if( eeconfig()->m_CrossProbing.flash_selection )
|
||||
{
|
||||
wxLogTrace( "CROSS_PROBE_FLASH", "MAIL_SELECTION(_FORCE): flash enabled, items=%zu", items.size() );
|
||||
if( items.empty() )
|
||||
{
|
||||
wxLogTrace( "CROSS_PROBE_FLASH", "MAIL_SELECTION(_FORCE): nothing to flash" );
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<SCH_ITEM*> itemPtrs;
|
||||
std::copy( items.begin(), items.end(), std::back_inserter( itemPtrs ) );
|
||||
|
||||
StartCrossProbeFlash( itemPtrs );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wxLogTrace( "CROSS_PROBE_FLASH", "MAIL_SELECTION(_FORCE): flash disabled" );
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include <id.h>
|
||||
#include <confirm.h>
|
||||
#include <widgets/wx_html_report_box.h>
|
||||
#include <widgets/std_bitmap_button.h>
|
||||
#include <dialogs/dialog_text_entry.h>
|
||||
#include <string_utils.h>
|
||||
#include <kiplatform/ui.h>
|
||||
@ -76,15 +77,15 @@ DIALOG_ERC::DIALOG_ERC( SCH_EDIT_FRAME* parent ) :
|
||||
m_running( false ),
|
||||
m_ercRun( false ),
|
||||
m_centerMarkerOnIdle( nullptr ),
|
||||
m_severities( 0 )
|
||||
m_crossprobe( true ),
|
||||
m_scroll_on_crossprobe( true )
|
||||
{
|
||||
m_currentSchematic = &parent->Schematic();
|
||||
|
||||
SetName( DIALOG_ERC_WINDOW_NAME ); // Set a window name to be able to find it
|
||||
KIPLATFORM::UI::SetFloatLevel( this );
|
||||
|
||||
if( EESCHEMA_SETTINGS* cfg = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" ) )
|
||||
m_severities = cfg->m_Appearance.erc_severities;
|
||||
m_bMenu->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
|
||||
|
||||
m_messages->SetImmediateMode();
|
||||
|
||||
@ -92,7 +93,7 @@ DIALOG_ERC::DIALOG_ERC( SCH_EDIT_FRAME* parent ) :
|
||||
|
||||
m_markerTreeModel = new ERC_TREE_MODEL( parent, m_markerDataView );
|
||||
m_markerDataView->AssociateModel( m_markerTreeModel );
|
||||
m_markerTreeModel->Update( m_markerProvider, m_severities );
|
||||
m_markerTreeModel->Update( m_markerProvider, getSeverities() );
|
||||
|
||||
m_ignoredList->InsertColumn( 0, wxEmptyString, wxLIST_FORMAT_LEFT, DEFAULT_SINGLE_COL_WIDTH );
|
||||
|
||||
@ -129,8 +130,11 @@ DIALOG_ERC::DIALOG_ERC( SCH_EDIT_FRAME* parent ) :
|
||||
|
||||
SetFocus();
|
||||
|
||||
syncCheckboxes();
|
||||
updateDisplayedCounts();
|
||||
if( EESCHEMA_SETTINGS* cfg = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" ) )
|
||||
{
|
||||
m_crossprobe = cfg->m_ERCDialog.crossprobe;
|
||||
m_scroll_on_crossprobe = cfg->m_ERCDialog.scroll_on_crossprobe;
|
||||
}
|
||||
|
||||
// Now all widgets have the size fixed, call FinishDialogSettings
|
||||
finishDialogSettings();
|
||||
@ -148,7 +152,10 @@ DIALOG_ERC::~DIALOG_ERC()
|
||||
g_lastERCIgnored.push_back( { m_ignoredList->GetItemText( ii ), m_ignoredList->GetItemData( ii ) } );
|
||||
|
||||
if( EESCHEMA_SETTINGS* cfg = GetAppSettings<EESCHEMA_SETTINGS>( "eeschema" ) )
|
||||
cfg->m_Appearance.erc_severities = m_severities;
|
||||
{
|
||||
cfg->m_ERCDialog.crossprobe = m_crossprobe;
|
||||
cfg->m_ERCDialog.scroll_on_crossprobe = m_scroll_on_crossprobe;
|
||||
}
|
||||
|
||||
m_markerTreeModel->DecRef();
|
||||
}
|
||||
@ -189,6 +196,59 @@ void DIALOG_ERC::UpdateAnnotationWarning()
|
||||
}
|
||||
|
||||
|
||||
int DIALOG_ERC::getSeverities()
|
||||
{
|
||||
int severities = 0;
|
||||
|
||||
if( m_showErrors->GetValue() )
|
||||
severities |= RPT_SEVERITY_ERROR;
|
||||
|
||||
if( m_showWarnings->GetValue() )
|
||||
severities |= RPT_SEVERITY_WARNING;
|
||||
|
||||
if( m_showExclusions->GetValue() )
|
||||
severities |= RPT_SEVERITY_EXCLUSION;
|
||||
|
||||
return severities;
|
||||
}
|
||||
|
||||
|
||||
void DIALOG_ERC::OnMenu( wxCommandEvent& event )
|
||||
{
|
||||
// Build a pop menu:
|
||||
wxMenu menu;
|
||||
|
||||
menu.Append( 4206, _( "Cross-probe Selected Items" ),
|
||||
_( "Highlight corresponding items on canvas when selected in the ERC list" ),
|
||||
wxITEM_CHECK );
|
||||
menu.Check( 4206, m_crossprobe );
|
||||
|
||||
menu.Append( 4207, _( "Center on Cross-probe" ),
|
||||
_( "When cross-probing, scroll the canvas so that the item is visible" ),
|
||||
wxITEM_CHECK );
|
||||
menu.Check( 4207, m_scroll_on_crossprobe );
|
||||
|
||||
// menu_id is the selected submenu id from the popup menu or wxID_NONE
|
||||
int menu_id = m_bMenu->GetPopupMenuSelectionFromUser( menu );
|
||||
|
||||
if( menu_id == 0 || menu_id == 4206 )
|
||||
{
|
||||
m_crossprobe = !m_crossprobe;
|
||||
}
|
||||
else if( menu_id == 1 || menu_id == 4207 )
|
||||
{
|
||||
m_scroll_on_crossprobe = !m_scroll_on_crossprobe;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DIALOG_ERC::TransferDataToWindow()
|
||||
{
|
||||
UpdateData();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DIALOG_ERC::updateUI()
|
||||
{
|
||||
// If ERC checks ever get slow enough we'll want a progress indicator...
|
||||
@ -217,6 +277,13 @@ void DIALOG_ERC::Report( const wxString& aMessage )
|
||||
}
|
||||
|
||||
|
||||
void DIALOG_ERC::UpdateData()
|
||||
{
|
||||
m_markerTreeModel->Update( m_markerProvider, getSeverities() );
|
||||
updateDisplayedCounts();
|
||||
}
|
||||
|
||||
|
||||
void DIALOG_ERC::updateDisplayedCounts()
|
||||
{
|
||||
int numErrors = 0;
|
||||
@ -348,30 +415,14 @@ void DIALOG_ERC::OnCloseErcDialog( wxCloseEvent& aEvent )
|
||||
// Dialog is mode-less so let the parent know that it needs to be destroyed.
|
||||
if( !IsModal() && !IsQuasiModal() )
|
||||
{
|
||||
wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_ERC_DIALOG, wxID_ANY );
|
||||
|
||||
wxWindow* parent = GetParent();
|
||||
|
||||
if( parent )
|
||||
wxQueueEvent( parent, evt );
|
||||
if( wxWindow* parent = GetParent() )
|
||||
wxQueueEvent( parent, new wxCommandEvent( EDA_EVT_CLOSE_ERC_DIALOG, wxID_ANY ) );
|
||||
}
|
||||
|
||||
aEvent.Skip();
|
||||
}
|
||||
|
||||
|
||||
static int RPT_SEVERITY_ALL = RPT_SEVERITY_WARNING | RPT_SEVERITY_ERROR | RPT_SEVERITY_EXCLUSION;
|
||||
|
||||
|
||||
void DIALOG_ERC::syncCheckboxes()
|
||||
{
|
||||
m_showAll->SetValue( m_severities == RPT_SEVERITY_ALL );
|
||||
m_showErrors->SetValue( m_severities & RPT_SEVERITY_ERROR );
|
||||
m_showWarnings->SetValue( m_severities & RPT_SEVERITY_WARNING );
|
||||
m_showExclusions->SetValue( m_severities & RPT_SEVERITY_EXCLUSION );
|
||||
}
|
||||
|
||||
|
||||
void DIALOG_ERC::OnLinkClicked( wxHtmlLinkEvent& event )
|
||||
{
|
||||
m_parent->OnAnnotate();
|
||||
@ -446,8 +497,7 @@ void DIALOG_ERC::OnRunERCClick( wxCommandEvent& event )
|
||||
}
|
||||
|
||||
if( m_cancelled )
|
||||
// @spellingerror
|
||||
m_messages->Report( _( "-------- ERC canceled by user.<br><br>" ), RPT_SEVERITY_INFO );
|
||||
m_messages->Report( _( "-------- ERC cancelled by user.<br><br>" ), RPT_SEVERITY_INFO );
|
||||
else
|
||||
m_messages->Report( _( "Done.<br><br>" ), RPT_SEVERITY_INFO );
|
||||
|
||||
@ -508,7 +558,7 @@ void DIALOG_ERC::testErc()
|
||||
}
|
||||
|
||||
// Update marker list:
|
||||
m_markerTreeModel->Update( m_markerProvider, m_severities );
|
||||
m_markerTreeModel->Update( m_markerProvider, getSeverities() );
|
||||
|
||||
// Display new markers from the current screen:
|
||||
for( SCH_ITEM* marker : m_parent->GetScreen()->Items().OfType( SCH_MARKER_T ) )
|
||||
@ -523,6 +573,12 @@ void DIALOG_ERC::testErc()
|
||||
|
||||
void DIALOG_ERC::OnERCItemSelected( wxDataViewEvent& aEvent )
|
||||
{
|
||||
if( !m_crossprobe )
|
||||
{
|
||||
aEvent.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const KIID& itemID = RC_TREE_MODEL::ToUUID( aEvent.GetItem() );
|
||||
SCH_SHEET_PATH sheet;
|
||||
SCH_ITEM* item = m_parent->Schematic().ResolveItem( itemID, &sheet, true );
|
||||
@ -568,7 +624,7 @@ void DIALOG_ERC::OnERCItemSelected( wxDataViewEvent& aEvent )
|
||||
m_parent->RedrawScreen( m_parent->GetScreen()->m_ScrollCenter, false );
|
||||
}
|
||||
|
||||
m_parent->FocusOnItem( item );
|
||||
m_parent->FocusOnItem( item, m_scroll_on_crossprobe );
|
||||
redrawDrawPanel();
|
||||
}
|
||||
|
||||
@ -762,7 +818,7 @@ void DIALOG_ERC::OnERCItemRClick( wxDataViewEvent& aEvent )
|
||||
m_parent->GetCanvas()->GetView()->Update( marker );
|
||||
|
||||
// Update view
|
||||
if( m_severities & RPT_SEVERITY_EXCLUSION )
|
||||
if( getSeverities() & RPT_SEVERITY_EXCLUSION )
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->ValueChanged( node );
|
||||
else
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->DeleteCurrentItem( false );
|
||||
@ -788,7 +844,7 @@ void DIALOG_ERC::OnERCItemRClick( wxDataViewEvent& aEvent )
|
||||
}
|
||||
|
||||
// Rebuild model and view
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markerProvider, m_severities );
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markerProvider, getSeverities() );
|
||||
modified = true;
|
||||
break;
|
||||
|
||||
@ -804,7 +860,7 @@ void DIALOG_ERC::OnERCItemRClick( wxDataViewEvent& aEvent )
|
||||
}
|
||||
|
||||
// Rebuild model and view
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markerProvider, m_severities );
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markerProvider, getSeverities() );
|
||||
modified = true;
|
||||
break;
|
||||
|
||||
@ -830,7 +886,7 @@ void DIALOG_ERC::OnERCItemRClick( wxDataViewEvent& aEvent )
|
||||
ScreenList.DeleteMarkers( MARKER_BASE::MARKER_ERC, rcItem->GetErrorCode() );
|
||||
|
||||
// Rebuild model and view
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markerProvider, m_severities );
|
||||
static_cast<RC_TREE_MODEL*>( aEvent.GetModel() )->Update( m_markerProvider, getSeverities() );
|
||||
modified = true;
|
||||
}
|
||||
break;
|
||||
@ -956,7 +1012,7 @@ void DIALOG_ERC::ExcludeMarker( SCH_MARKER* aMarker )
|
||||
m_parent->GetCanvas()->GetView()->Update( marker );
|
||||
|
||||
// Update view
|
||||
if( m_severities & RPT_SEVERITY_EXCLUSION )
|
||||
if( getSeverities() & RPT_SEVERITY_EXCLUSION )
|
||||
m_markerTreeModel->ValueChanged( node );
|
||||
else
|
||||
m_markerTreeModel->DeleteCurrentItem( false );
|
||||
@ -976,28 +1032,14 @@ void DIALOG_ERC::OnEditViolationSeverities( wxHyperlinkEvent& aEvent )
|
||||
|
||||
void DIALOG_ERC::OnSeverity( wxCommandEvent& aEvent )
|
||||
{
|
||||
int flag = 0;
|
||||
|
||||
if( aEvent.GetEventObject() == m_showAll )
|
||||
flag = RPT_SEVERITY_ALL;
|
||||
else if( aEvent.GetEventObject() == m_showErrors )
|
||||
flag = RPT_SEVERITY_ERROR;
|
||||
else if( aEvent.GetEventObject() == m_showWarnings )
|
||||
flag = RPT_SEVERITY_WARNING;
|
||||
else if( aEvent.GetEventObject() == m_showExclusions )
|
||||
flag = RPT_SEVERITY_EXCLUSION;
|
||||
{
|
||||
m_showErrors->SetValue( true );
|
||||
m_showWarnings->SetValue( aEvent.IsChecked() );
|
||||
m_showExclusions->SetValue( aEvent.IsChecked() );
|
||||
}
|
||||
|
||||
if( aEvent.IsChecked() )
|
||||
m_severities |= flag;
|
||||
else if( aEvent.GetEventObject() == m_showAll )
|
||||
m_severities = RPT_SEVERITY_ERROR;
|
||||
else
|
||||
m_severities &= ~flag;
|
||||
|
||||
syncCheckboxes();
|
||||
|
||||
m_markerTreeModel->Update( m_markerProvider, m_severities );
|
||||
updateDisplayedCounts();
|
||||
UpdateData();
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,6 +49,8 @@ public:
|
||||
DIALOG_ERC( SCH_EDIT_FRAME* parent );
|
||||
~DIALOG_ERC();
|
||||
|
||||
bool TransferDataToWindow() override;
|
||||
|
||||
// PROGRESS_REPORTER_BASE calls
|
||||
bool updateUI() override;
|
||||
void AdvancePhase( const wxString& aMessage ) override;
|
||||
@ -66,10 +68,14 @@ public:
|
||||
*/
|
||||
void ExcludeMarker( SCH_MARKER* aMarker = nullptr );
|
||||
|
||||
void UpdateData();
|
||||
void UpdateAnnotationWarning();
|
||||
|
||||
private:
|
||||
int getSeverities();
|
||||
|
||||
// from DIALOG_ERC_BASE:
|
||||
void OnMenu( wxCommandEvent& aEvent ) override;
|
||||
void OnCloseErcDialog( wxCloseEvent& event ) override;
|
||||
void OnRunERCClick( wxCommandEvent& event ) override;
|
||||
void OnDeleteOneClick( wxCommandEvent& event ) override;
|
||||
@ -92,8 +98,6 @@ private:
|
||||
|
||||
void testErc();
|
||||
|
||||
bool writeReport( const wxString& aFullFileName );
|
||||
|
||||
void deleteAllMarkers( bool aIncludeExclusions );
|
||||
|
||||
void syncCheckboxes();
|
||||
@ -114,7 +118,8 @@ private:
|
||||
|
||||
const SCH_MARKER* m_centerMarkerOnIdle;
|
||||
|
||||
int m_severities;
|
||||
bool m_crossprobe;
|
||||
bool m_scroll_on_crossprobe;
|
||||
};
|
||||
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
// PLEASE DO *NOT* EDIT THIS FILE!
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "widgets/std_bitmap_button.h"
|
||||
#include "widgets/wx_html_report_box.h"
|
||||
#include "widgets/wx_infobar.h"
|
||||
|
||||
@ -29,6 +30,24 @@ DIALOG_ERC_BASE::DIALOG_ERC_BASE( wxWindow* parent, wxWindowID id, const wxStrin
|
||||
wxBoxSizer* bMainSizer;
|
||||
bMainSizer = new wxBoxSizer( wxVERTICAL );
|
||||
|
||||
wxGridBagSizer* gbSizerOptions;
|
||||
gbSizerOptions = new wxGridBagSizer( 0, 0 );
|
||||
gbSizerOptions->SetFlexibleDirection( wxBOTH );
|
||||
gbSizerOptions->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
|
||||
|
||||
m_bMenu = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBU_AUTODRAW|0 );
|
||||
m_bMenu->SetMinSize( wxSize( 30,30 ) );
|
||||
|
||||
gbSizerOptions->Add( m_bMenu, wxGBPosition( 0, 2 ), wxGBSpan( 2, 1 ), wxALIGN_CENTER_VERTICAL, 5 );
|
||||
|
||||
|
||||
gbSizerOptions->AddGrowableCol( 0 );
|
||||
gbSizerOptions->AddGrowableCol( 1 );
|
||||
gbSizerOptions->AddGrowableRow( 0 );
|
||||
gbSizerOptions->AddGrowableRow( 1 );
|
||||
|
||||
bMainSizer->Add( gbSizerOptions, 0, wxEXPAND|wxLEFT, 5 );
|
||||
|
||||
m_runningResultsBook = new wxSimplebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
|
||||
running = new wxPanel( m_runningResultsBook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
|
||||
wxBoxSizer* bSizer14;
|
||||
@ -195,6 +214,7 @@ DIALOG_ERC_BASE::DIALOG_ERC_BASE( wxWindow* parent, wxWindowID id, const wxStrin
|
||||
|
||||
// Connect Events
|
||||
this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DIALOG_ERC_BASE::OnCloseErcDialog ) );
|
||||
m_bMenu->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_ERC_BASE::OnMenu ), NULL, this );
|
||||
m_messages->Connect( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEventHandler( DIALOG_ERC_BASE::OnLinkClicked ), NULL, this );
|
||||
m_markerDataView->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, wxDataViewEventHandler( DIALOG_ERC_BASE::OnERCItemDClick ), NULL, this );
|
||||
m_markerDataView->Connect( wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU, wxDataViewEventHandler( DIALOG_ERC_BASE::OnERCItemRClick ), NULL, this );
|
||||
@ -216,6 +236,7 @@ DIALOG_ERC_BASE::~DIALOG_ERC_BASE()
|
||||
{
|
||||
// Disconnect Events
|
||||
this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DIALOG_ERC_BASE::OnCloseErcDialog ) );
|
||||
m_bMenu->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_ERC_BASE::OnMenu ), NULL, this );
|
||||
m_messages->Disconnect( wxEVT_COMMAND_HTML_LINK_CLICKED, wxHtmlLinkEventHandler( DIALOG_ERC_BASE::OnLinkClicked ), NULL, this );
|
||||
m_markerDataView->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, wxDataViewEventHandler( DIALOG_ERC_BASE::OnERCItemDClick ), NULL, this );
|
||||
m_markerDataView->Disconnect( wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU, wxDataViewEventHandler( DIALOG_ERC_BASE::OnERCItemRClick ), NULL, this );
|
||||
|
@ -14,7 +14,7 @@
|
||||
<property name="embedded_files_path">res</property>
|
||||
<property name="encoding">UTF-8</property>
|
||||
<property name="file">dialog_erc_base</property>
|
||||
<property name="first_id">1000</property>
|
||||
<property name="first_id">7100</property>
|
||||
<property name="internationalize">1</property>
|
||||
<property name="lua_skip_events">1</property>
|
||||
<property name="lua_ui_table">UI</property>
|
||||
@ -135,6 +135,101 @@
|
||||
<property name="name">bMainSizer</property>
|
||||
<property name="orient">wxVERTICAL</property>
|
||||
<property name="permission">none</property>
|
||||
<object class="sizeritem" expanded="true">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxEXPAND|wxLEFT</property>
|
||||
<property name="proportion">0</property>
|
||||
<object class="wxGridBagSizer" expanded="true">
|
||||
<property name="empty_cell_size"></property>
|
||||
<property name="flexible_direction">wxBOTH</property>
|
||||
<property name="growablecols">0,1</property>
|
||||
<property name="growablerows">0,1</property>
|
||||
<property name="hgap">0</property>
|
||||
<property name="minimum_size"></property>
|
||||
<property name="name">gbSizerOptions</property>
|
||||
<property name="non_flexible_grow_mode">wxFLEX_GROWMODE_SPECIFIED</property>
|
||||
<property name="permission">none</property>
|
||||
<property name="vgap">0</property>
|
||||
<object class="gbsizeritem" expanded="true">
|
||||
<property name="border">5</property>
|
||||
<property name="colspan">1</property>
|
||||
<property name="column">2</property>
|
||||
<property name="flag">wxALIGN_CENTER_VERTICAL</property>
|
||||
<property name="row">0</property>
|
||||
<property name="rowspan">2</property>
|
||||
<object class="wxBitmapButton" expanded="true">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
<property name="RightDockable">1</property>
|
||||
<property name="TopDockable">1</property>
|
||||
<property name="aui_layer">0</property>
|
||||
<property name="aui_name"></property>
|
||||
<property name="aui_position">0</property>
|
||||
<property name="aui_row">0</property>
|
||||
<property name="auth_needed">0</property>
|
||||
<property name="best_size"></property>
|
||||
<property name="bg"></property>
|
||||
<property name="bitmap"></property>
|
||||
<property name="caption"></property>
|
||||
<property name="caption_visible">1</property>
|
||||
<property name="center_pane">0</property>
|
||||
<property name="close_button">1</property>
|
||||
<property name="context_help"></property>
|
||||
<property name="context_menu">1</property>
|
||||
<property name="current"></property>
|
||||
<property name="default">0</property>
|
||||
<property name="default_pane">0</property>
|
||||
<property name="disabled"></property>
|
||||
<property name="dock">Dock</property>
|
||||
<property name="dock_fixed">0</property>
|
||||
<property name="docking">Left</property>
|
||||
<property name="drag_accept_files">0</property>
|
||||
<property name="enabled">1</property>
|
||||
<property name="fg"></property>
|
||||
<property name="floatable">1</property>
|
||||
<property name="focus"></property>
|
||||
<property name="font"></property>
|
||||
<property name="gripper">0</property>
|
||||
<property name="hidden">0</property>
|
||||
<property name="id">wxID_ANY</property>
|
||||
<property name="label">Refresh Grouping</property>
|
||||
<property name="margins"></property>
|
||||
<property name="markup">0</property>
|
||||
<property name="max_size"></property>
|
||||
<property name="maximize_button">0</property>
|
||||
<property name="maximum_size"></property>
|
||||
<property name="min_size"></property>
|
||||
<property name="minimize_button">0</property>
|
||||
<property name="minimum_size">30,30</property>
|
||||
<property name="moveable">1</property>
|
||||
<property name="name">m_bMenu</property>
|
||||
<property name="pane_border">1</property>
|
||||
<property name="pane_position"></property>
|
||||
<property name="pane_size"></property>
|
||||
<property name="permission">protected</property>
|
||||
<property name="pin_button">1</property>
|
||||
<property name="pos"></property>
|
||||
<property name="position"></property>
|
||||
<property name="pressed"></property>
|
||||
<property name="resize">Resizable</property>
|
||||
<property name="show">1</property>
|
||||
<property name="size"></property>
|
||||
<property name="style"></property>
|
||||
<property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
|
||||
<property name="toolbar_pane">0</property>
|
||||
<property name="tooltip"></property>
|
||||
<property name="validator_data_type"></property>
|
||||
<property name="validator_style">wxFILTER_NONE</property>
|
||||
<property name="validator_type">wxDefaultValidator</property>
|
||||
<property name="validator_variable"></property>
|
||||
<property name="window_extra_style"></property>
|
||||
<property name="window_name"></property>
|
||||
<property name="window_style"></property>
|
||||
<event name="OnButtonClick">OnMenu</event>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="sizeritem" expanded="true">
|
||||
<property name="border">5</property>
|
||||
<property name="flag">wxEXPAND|wxTOP|wxRIGHT</property>
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <wx/artprov.h>
|
||||
#include <wx/xrc/xmlres.h>
|
||||
#include <wx/intl.h>
|
||||
class STD_BITMAP_BUTTON;
|
||||
class WX_HTML_REPORT_BOX;
|
||||
class WX_INFOBAR;
|
||||
|
||||
@ -20,13 +21,16 @@ class WX_INFOBAR;
|
||||
#include <wx/colour.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/bmpbuttn.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/icon.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/icon.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/listctrl.h>
|
||||
@ -35,12 +39,11 @@ class WX_INFOBAR;
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <widgets/number_badge.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/dialog.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define ID_ERASE_DRC_MARKERS 1000
|
||||
#define ID_ERASE_DRC_MARKERS 7100
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// Class DIALOG_ERC_BASE
|
||||
@ -51,6 +54,7 @@ class DIALOG_ERC_BASE : public DIALOG_SHIM
|
||||
|
||||
protected:
|
||||
WX_INFOBAR* m_infoBar;
|
||||
STD_BITMAP_BUTTON* m_bMenu;
|
||||
wxSimplebook* m_runningResultsBook;
|
||||
wxPanel* running;
|
||||
wxNotebook* m_runningNotebook;
|
||||
@ -82,6 +86,7 @@ class DIALOG_ERC_BASE : public DIALOG_SHIM
|
||||
|
||||
// Virtual event handlers, override them in your derived class
|
||||
virtual void OnCloseErcDialog( wxCloseEvent& event ) { event.Skip(); }
|
||||
virtual void OnMenu( wxCommandEvent& event ) { event.Skip(); }
|
||||
virtual void OnLinkClicked( wxHtmlLinkEvent& event ) { event.Skip(); }
|
||||
virtual void OnERCItemDClick( wxDataViewEvent& event ) { event.Skip(); }
|
||||
virtual void OnERCItemRClick( wxDataViewEvent& event ) { event.Skip(); }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user