|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Jon Evans <jon@craftyjon.com> * 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 <tuple>
#include <api/api_handler_common.h>
#include <build_version.h>
#include <eda_shape.h>
#include <eda_text.h>
#include <gestfich.h>
#include <geometry/shape_compound.h>
#include <google/protobuf/empty.pb.h>
#include <paths.h>
#include <pgm_base.h>
#include <api/api_plugin.h>
#include <api/api_utils.h>
#include <project/net_settings.h>
#include <project/project_file.h>
#include <settings/settings_manager.h>
#include <wx/string.h>
using namespace kiapi::common::commands;using namespace kiapi::common::types;using google::protobuf::Empty;
API_HANDLER_COMMON::API_HANDLER_COMMON() : API_HANDLER(){ registerHandler<commands::GetVersion, GetVersionResponse>( &API_HANDLER_COMMON::handleGetVersion ); registerHandler<GetKiCadBinaryPath, PathResponse>( &API_HANDLER_COMMON::handleGetKiCadBinaryPath ); registerHandler<GetNetClasses, NetClassesResponse>( &API_HANDLER_COMMON::handleGetNetClasses ); registerHandler<SetNetClasses, Empty>( &API_HANDLER_COMMON::handleSetNetClasses ); registerHandler<Ping, Empty>( &API_HANDLER_COMMON::handlePing ); registerHandler<GetTextExtents, types::Box2>( &API_HANDLER_COMMON::handleGetTextExtents ); registerHandler<GetTextAsShapes, GetTextAsShapesResponse>( &API_HANDLER_COMMON::handleGetTextAsShapes ); registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>( &API_HANDLER_COMMON::handleExpandTextVariables ); registerHandler<GetPluginSettingsPath, StringResponse>( &API_HANDLER_COMMON::handleGetPluginSettingsPath ); registerHandler<GetTextVariables, project::TextVariables>( &API_HANDLER_COMMON::handleGetTextVariables ); registerHandler<SetTextVariables, Empty>( &API_HANDLER_COMMON::handleSetTextVariables );
}
HANDLER_RESULT<GetVersionResponse> API_HANDLER_COMMON::handleGetVersion( const HANDLER_CONTEXT<commands::GetVersion>& ){ GetVersionResponse reply;
reply.mutable_version()->set_full_version( GetBuildVersion().ToStdString() );
std::tuple<int, int, int> version = GetMajorMinorPatchTuple(); reply.mutable_version()->set_major( std::get<0>( version ) ); reply.mutable_version()->set_minor( std::get<1>( version ) ); reply.mutable_version()->set_patch( std::get<2>( version ) );
return reply;}
HANDLER_RESULT<PathResponse> API_HANDLER_COMMON::handleGetKiCadBinaryPath( const HANDLER_CONTEXT<GetKiCadBinaryPath>& aCtx ){ wxFileName fn( wxEmptyString, wxString::FromUTF8( aCtx.Request.binary_name() ) );#ifdef _WIN32
fn.SetExt( wxT( "exe" ) );#endif
wxString path = FindKicadFile( fn.GetFullName() ); PathResponse reply; reply.set_path( path.ToUTF8() ); return reply;}
HANDLER_RESULT<NetClassesResponse> API_HANDLER_COMMON::handleGetNetClasses( const HANDLER_CONTEXT<GetNetClasses>& aCtx ){ NetClassesResponse reply;
std::shared_ptr<NET_SETTINGS>& netSettings = Pgm().GetSettingsManager().Prj().GetProjectFile().m_NetSettings;
google::protobuf::Any any;
netSettings->GetDefaultNetclass()->Serialize( any ); any.UnpackTo( reply.add_net_classes() );
for( const auto& netClass : netSettings->GetNetclasses() | std::views::values ) { netClass->Serialize( any ); any.UnpackTo( reply.add_net_classes() ); }
return reply;}
HANDLER_RESULT<Empty> API_HANDLER_COMMON::handleSetNetClasses( const HANDLER_CONTEXT<SetNetClasses>& aCtx ){ std::shared_ptr<NET_SETTINGS>& netSettings = Pgm().GetSettingsManager().Prj().GetProjectFile().m_NetSettings;
if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE ) netSettings->ClearNetclasses();
auto netClasses = netSettings->GetNetclasses(); google::protobuf::Any any;
for( const auto& ncProto : aCtx.Request.net_classes() ) { any.PackFrom( ncProto ); wxString name = wxString::FromUTF8( ncProto.name() );
if( name == wxT( "Default" ) ) { netSettings->GetDefaultNetclass()->Deserialize( any ); } else { if( !netClasses.contains( name ) ) netClasses.insert( { name, std::make_shared<NETCLASS>( name, false ) } );
netClasses[name]->Deserialize( any ); } }
netSettings->SetNetclasses( netClasses );
return Empty();}
HANDLER_RESULT<Empty> API_HANDLER_COMMON::handlePing( const HANDLER_CONTEXT<Ping>& aCtx ){ return Empty();}
HANDLER_RESULT<types::Box2> API_HANDLER_COMMON::handleGetTextExtents( const HANDLER_CONTEXT<GetTextExtents>& aCtx ){ EDA_TEXT text( pcbIUScale ); google::protobuf::Any any; any.PackFrom( aCtx.Request.text() );
if( !text.Deserialize( any ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( "Could not decode text in GetTextExtents message" ); return tl::unexpected( e ); }
types::Box2 response;
BOX2I bbox = text.GetTextBox(); EDA_ANGLE angle = text.GetTextAngle();
if( !angle.IsZero() ) bbox = bbox.GetBoundingBoxRotated( text.GetTextPos(), text.GetTextAngle() );
response.mutable_position()->set_x_nm( bbox.GetPosition().x ); response.mutable_position()->set_y_nm( bbox.GetPosition().y ); response.mutable_size()->set_x_nm( bbox.GetSize().x ); response.mutable_size()->set_y_nm( bbox.GetSize().y );
return response;}
HANDLER_RESULT<GetTextAsShapesResponse> API_HANDLER_COMMON::handleGetTextAsShapes( const HANDLER_CONTEXT<GetTextAsShapes>& aCtx ){ GetTextAsShapesResponse reply;
for( const TextOrTextBox& textMsg : aCtx.Request.text() ) { Text dummyText; const Text* textPtr = &textMsg.text();
if( textMsg.has_textbox() ) { dummyText.set_text( textMsg.textbox().text() ); dummyText.mutable_attributes()->CopyFrom( textMsg.textbox().attributes() ); textPtr = &dummyText; }
EDA_TEXT text( pcbIUScale ); google::protobuf::Any any; any.PackFrom( *textPtr );
if( !text.Deserialize( any ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( "Could not decode text in GetTextAsShapes message" ); return tl::unexpected( e ); }
std::shared_ptr<SHAPE_COMPOUND> shapes = text.GetEffectiveTextShape( false );
TextWithShapes* entry = reply.add_text_with_shapes(); entry->mutable_text()->CopyFrom( textMsg );
for( SHAPE* subshape : shapes->Shapes() ) { EDA_SHAPE proxy( *subshape ); proxy.Serialize( any ); GraphicShape* shapeMsg = entry->mutable_shapes()->add_shapes(); any.UnpackTo( shapeMsg ); }
if( textMsg.has_textbox() ) { GraphicShape* border = entry->mutable_shapes()->add_shapes(); int width = textMsg.textbox().attributes().stroke_width().value_nm(); border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width ); VECTOR2I tl = UnpackVector2( textMsg.textbox().top_left() ); VECTOR2I br = UnpackVector2( textMsg.textbox().bottom_right() );
// top
PackVector2( *border->mutable_segment()->mutable_start(), tl ); PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( br.x, tl.y ) );
// right
border = entry->mutable_shapes()->add_shapes(); border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width ); PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( br.x, tl.y ) ); PackVector2( *border->mutable_segment()->mutable_end(), br );
// bottom
border = entry->mutable_shapes()->add_shapes(); border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width ); PackVector2( *border->mutable_segment()->mutable_start(), br ); PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( tl.x, br.y ) );
// left
border = entry->mutable_shapes()->add_shapes(); border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width ); PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( tl.x, br.y ) ); PackVector2( *border->mutable_segment()->mutable_end(), tl ); } }
return reply;}
HANDLER_RESULT<ExpandTextVariablesResponse> API_HANDLER_COMMON::handleExpandTextVariables( const HANDLER_CONTEXT<ExpandTextVariables>& aCtx ){ if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_UNHANDLED ); // No error message, this is a flag that the server should try a different handler
return tl::unexpected( e ); }
ExpandTextVariablesResponse reply; PROJECT& project = Pgm().GetSettingsManager().Prj();
for( const std::string& textMsg : aCtx.Request.text() ) { wxString result = ExpandTextVars( wxString::FromUTF8( textMsg ), &project ); reply.add_text( result.ToUTF8() ); }
return reply;}
HANDLER_RESULT<StringResponse> API_HANDLER_COMMON::handleGetPluginSettingsPath( const HANDLER_CONTEXT<GetPluginSettingsPath>& aCtx ){ wxString identifier = wxString::FromUTF8( aCtx.Request.identifier() );
if( identifier.IsEmpty() ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( "plugin identifier is missing" ); return tl::unexpected( e ); }
if( API_PLUGIN::IsValidIdentifier( identifier ) ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_BAD_REQUEST ); e.set_error_message( "plugin identifier is invalid" ); return tl::unexpected( e ); }
wxFileName path( PATHS::GetUserSettingsPath(), wxEmptyString ); path.AppendDir( "plugins" );
// Create the base plugins path if needed, but leave the specific plugin to create its own path
PATHS::EnsurePathExists( path.GetPath() );
path.AppendDir( identifier );
StringResponse reply; reply.set_response( path.GetPath() ); return reply;}
HANDLER_RESULT<project::TextVariables> API_HANDLER_COMMON::handleGetTextVariables( const HANDLER_CONTEXT<GetTextVariables>& aCtx ){ if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_UNHANDLED ); // No error message, this is a flag that the server should try a different handler
return tl::unexpected( e ); }
const PROJECT& project = Pgm().GetSettingsManager().Prj();
if( project.IsNullProject() ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_NOT_READY ); e.set_error_message( "no valid project is loaded, cannot get text variables" ); return tl::unexpected( e ); }
const std::map<wxString, wxString>& vars = project.GetTextVars();
project::TextVariables reply; auto map = reply.mutable_variables();
for( const auto& [key, value] : vars ) ( *map )[ std::string( key.ToUTF8() ) ] = value.ToUTF8();
return reply;}
HANDLER_RESULT<Empty> API_HANDLER_COMMON::handleSetTextVariables( const HANDLER_CONTEXT<SetTextVariables>& aCtx ){ if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_UNHANDLED ); // No error message, this is a flag that the server should try a different handler
return tl::unexpected( e ); }
PROJECT& project = Pgm().GetSettingsManager().Prj();
if( project.IsNullProject() ) { ApiResponseStatus e; e.set_status( ApiStatusCode::AS_NOT_READY ); e.set_error_message( "no valid project is loaded, cannot set text variables" ); return tl::unexpected( e ); }
const project::TextVariables& newVars = aCtx.Request.variables(); std::map<wxString, wxString>& vars = project.GetTextVars();
if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE ) vars.clear();
for( const auto& [key, value] : newVars.variables() ) vars[wxString::FromUTF8( key )] = wxString::FromUTF8( value );
Pgm().GetSettingsManager().SaveProject();
return Empty();}
|