26#include <magic_enum.hpp>
42#include <wx/filename.h>
44using namespace kiapi::common::commands;
45using kiapi::common::commands::DocumentModifiedState;
46using kiapi::common::types::DocumentType;
47using kiapi::common::types::ItemRequestStatus;
88 return std::make_unique<SCH_COMMIT>(
context()->GetToolManager(),
true );
92tl::expected<bool, ApiResponseStatus>
95 if( aDocument.type() != DocumentType::DOCTYPE_SYMBOL )
98 e.set_status( ApiStatusCode::AS_UNHANDLED );
99 return tl::unexpected( e );
104 if( aDocument.has_project() )
106 if( aDocument.project().name().compare( prj.
GetProjectName().ToUTF8() ) != 0 )
109 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
110 e.set_error_message( fmt::format(
"the requested project {} is not open", aDocument.project().name() ) );
111 return tl::unexpected( e );
114 if( aDocument.project().path().compare( prj.
GetProjectPath().ToUTF8() ) != 0 )
117 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
118 e.set_error_message( fmt::format(
"the requested project {} is not open at path {}",
119 aDocument.project().name(), aDocument.project().path() ) );
120 return tl::unexpected( e );
127 if( aDocument.lib_id().library_nickname().empty() )
132 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
133 e.set_error_message(
"no symbol is currently open" );
134 return tl::unexpected( e );
143 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
144 e.set_error_message(
"no symbol is currently open" );
145 return tl::unexpected( e );
151 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
152 e.set_error_message( fmt::format(
"the requested library is {} but the open library is {}",
153 aDocument.lib_id().library_nickname(),
155 return tl::unexpected( e );
161 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
162 e.set_error_message( fmt::format(
"the requested symbol is {} but the open symbol is {}",
163 aDocument.lib_id().entry_name(),
165 return tl::unexpected( e );
174 if( aCtx.
Request.type() != DocumentType::DOCTYPE_SYMBOL )
177 e.set_status( ApiStatusCode::AS_UNHANDLED );
178 return tl::unexpected( e );
181 GetOpenDocumentsResponse response;
183 if( !
context()->GetCurSymbol() )
186 types::DocumentSpecifier doc;
189 doc.set_type( DocumentType::DOCTYPE_SYMBOL );
201 response.mutable_documents()->Add( std::move( doc ) );
209 if( aCtx.
Request.type() != DocumentType::DOCTYPE_SYMBOL )
212 e.set_status( ApiStatusCode::AS_UNHANDLED );
213 return tl::unexpected( e );
221 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
222 e.set_error_message( fmt::format(
"could not parse library identifier {}.{}",
223 aCtx.
Request.identifier().library_nickname(),
224 aCtx.
Request.identifier().entry_name() ) );
225 return tl::unexpected( e );
228 if( !
context()->LoadSymbol( libId ) )
231 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
232 e.set_error_message( fmt::format(
"could not open symbol {}.{}",
233 aCtx.
Request.identifier().library_nickname(),
234 aCtx.
Request.identifier().entry_name() ) );
235 return tl::unexpected( e );
244 if(
context()->GetLoadedLibId().GetLibNickname().
empty() )
247 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
248 e.set_error_message(
"the open symbol has no library yet; use SaveCopyOfDocument with a "
250 return tl::unexpected( e );
254 return tl::unexpected( documentValidation.error() );
256 if( std::optional<ApiResponseStatus> busy =
checkForBusy() )
257 return tl::unexpected( *busy );
262 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
263 e.set_error_message(
"failed to save symbol (is the library read-only?)" );
264 return tl::unexpected( e );
276 if( !documentValidation )
277 return tl::unexpected( documentValidation.error() );
279 if( std::optional<ApiResponseStatus> busy =
checkForBusy() )
280 return tl::unexpected( *busy );
282 wxFileName libPath( wxString::FromUTF8( aCtx.
Request.path() ) );
284 if( !libPath.IsOk() || !libPath.IsDirWritable() )
287 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
288 e.set_error_message( fmt::format(
"save path '{}' could not be opened", libPath.GetFullPath().ToStdString() ) );
289 return tl::unexpected( e );
292 if( libPath.FileExists() && ( !libPath.IsFileWritable() || !aCtx.
Request.options().overwrite() ) )
295 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
297 fmt::format(
"save path '{}' exists and cannot be overwritten", libPath.GetFullPath().ToStdString() ) );
298 return tl::unexpected( e );
304 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
306 fmt::format(
"save path '{}' must have a kicad_sym extension", libPath.GetFullPath().ToStdString() ) );
307 return tl::unexpected( e );
310 if( !
context()->SaveSymbolCopy( libPath.GetFullPath(), aCtx.
Request.options().overwrite() ) )
313 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
314 e.set_error_message(
"failed to save symbol copy" );
315 return tl::unexpected( e );
326 return tl::unexpected( documentValidation.error() );
329 if( std::optional<ApiResponseStatus> busy =
checkForBusy() )
330 return tl::unexpected( *busy );
332 if( !
context()->RevertSymbol() )
335 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
336 e.set_error_message(
"failed to revert symbol" );
337 return tl::unexpected( e );
350 return tl::unexpected( valid.error() );
353 if( std::optional<ApiResponseStatus> busy =
checkForBusy() )
354 return tl::unexpected( *busy );
361 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
362 e.set_error_message(
"no symbol is currently loaded" );
363 return tl::unexpected( e );
366 GetItemsResponse response;
367 std::set<KICAD_T> typesRequested;
370 if( aCtx.
Request.types().empty() )
373 for(
KICAD_T type : requestedTypes )
378 typesRequested.insert( type );
381 if( typesRequested.empty() )
384 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
385 e.set_error_message(
"none of the requested types are valid for a Symbol object" );
386 return tl::unexpected( e );
389 auto emitItem = [&](
EDA_ITEM* item )
391 if( !item || !typesRequested.contains( item->Type() ) )
394 google::protobuf::Any
any;
395 item->Serialize(
any );
396 response.mutable_items()->Add( std::move(
any ) );
405 response.set_status( ItemRequestStatus::IRS_OK );
416 return tl::unexpected( valid.error() );
419 if( std::optional<ApiResponseStatus> busy =
checkForBusy() )
420 return tl::unexpected( *busy );
427 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
428 e.set_error_message(
"no symbol is currently loaded" );
429 return tl::unexpected( e );
432 GetItemsResponse response;
434 for(
const types::KIID& idProto : aCtx.
Request.items() )
436 KIID id( idProto.value() );
438 if(
id ==
symbol->m_Uuid )
440 google::protobuf::Any
any;
442 response.mutable_items()->Add( std::move(
any ) );
448 if( item.m_Uuid !=
id || !
s_allowedTypes.contains( item.Type() ) )
451 google::protobuf::Any
any;
452 item.Serialize(
any );
453 response.mutable_items()->Add( std::move(
any ) );
458 response.set_status( ItemRequestStatus::IRS_OK );
473 if(
symbol->m_Uuid == aId )
474 return std::optional<EDA_ITEM*>(
symbol );
478 if( item.m_Uuid == aId )
479 return std::optional<EDA_ITEM*>( &item );
487 bool aCreate,
const std::string& aClientName,
const types::ItemHeader& aHeader,
488 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
489 std::function<
void( ItemStatus, google::protobuf::Any )> aItemHandler )
495 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
497 e.set_status( ApiStatusCode::AS_UNHANDLED );
498 return tl::unexpected( e );
500 else if( !containerResult )
502 e.CopyFrom( containerResult.error() );
503 return tl::unexpected( e );
510 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
511 e.set_error_message(
"no symbol is currently loaded" );
512 return tl::unexpected( e );
515 if( containerResult->has_value() && **containerResult !=
symbol->m_Uuid )
517 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
518 e.set_error_message( fmt::format(
"the requested container {} is not the open symbol",
519 ( *containerResult )->AsString().ToStdString() ) );
520 return tl::unexpected( e );
526 for(
const google::protobuf::Any& anyItem : aItems )
533 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
534 status.set_error_message( fmt::format(
"Could not decode a valid type from {}", anyItem.type_url() ) );
535 aItemHandler( status, anyItem );
541 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
542 status.set_error_message( fmt::format(
"type {} is not supported by the symbol editor API handler",
543 magic_enum::enum_name( *type ) ) );
544 aItemHandler( status, anyItem );
550 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
551 status.set_error_message(
"whole-symbol create/update is not supported; "
552 "edit the symbol's child items instead" );
553 aItemHandler( status, anyItem );
559 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
560 status.set_error_message(
"fields must be edited through the parent symbol's properties" );
561 aItemHandler( status, anyItem );
569 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
570 status.set_error_message(
571 fmt::format(
"could not create an item of type {}", magic_enum::enum_name( *type ) ) );
572 aItemHandler( status, anyItem );
576 if( !created->Deserialize( anyItem ) )
578 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
579 status.set_error_message(
580 fmt::format(
"could not unpack {} from request", created->GetClass().ToStdString() ) );
581 aItemHandler( status, anyItem );
589 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
590 status.set_error_message(
"mandatory fields cannot be created or replaced directly" );
591 aItemHandler( status, anyItem );
600 if( item.m_Uuid == newItem->
m_Uuid )
602 existingItem = &item;
607 if( aCreate && existingItem )
609 status.set_code( ItemStatusCode::ISC_EXISTING );
610 status.set_error_message(
611 fmt::format(
"an item with UUID {} already exists", newItem->
m_Uuid.
AsStdString() ) );
612 aItemHandler( status, anyItem );
615 else if( !aCreate && !existingItem )
617 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
618 status.set_error_message(
619 fmt::format(
"an item with UUID {} does not exist", newItem->
m_Uuid.
AsStdString() ) );
620 aItemHandler( status, anyItem );
624 if( !aCreate && existingItem->
Type() != *type )
626 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
627 status.set_error_message(
628 fmt::format(
"item {} is not of the requested type", newItem->
m_Uuid.
AsStdString() ) );
629 aItemHandler( status, anyItem );
633 status.set_code( ItemStatusCode::ISC_OK );
634 google::protobuf::Any responseItem;
644 symbol->AddDrawItem( newItem,
false );
661 aItemHandler( status, responseItem );
666 pushCurrentCommit( aClientName, aCreate ?
_(
"Created items via API" ) :
_(
"Modified items via API" ) );
669 return ItemRequestStatus::IRS_OK;
674 const std::string& aClientName )
684 for(
auto& [
id, status] : aItemsToDelete )
686 if( status != ItemDeletionStatus::IDS_NONEXISTENT )
693 if( candidate.m_Uuid ==
id )
702 status = ItemDeletionStatus::IDS_IMMUTABLE;
708 status = ItemDeletionStatus::IDS_IMMUTABLE;
717 symbol->RemoveDrawItem( item );
718 status = ItemDeletionStatus::IDS_OK;
746 return tl::unexpected( documentValidation.error() );
748 GetDocumentModifiedStateResponse response;
749 response.set_state(
context()->IsContentModified() ? DocumentModifiedState::DMS_MODIFIED
750 : DocumentModifiedState::DMS_UNMODIFIED );
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
std::unique_ptr< EDA_ITEM > CreateItemForType(KICAD_T aType, EDA_ITEM *aContainer)
HANDLER_RESULT< bool > validateDocument(const DocumentSpecifier &aDocument)
HANDLER_RESULT< std::optional< KIID > > validateItemHeaderDocument(const kiapi::common::types::ItemHeader &aHeader)
If the header is valid, returns the item container.
API_HANDLER_EDITOR(EDA_BASE_FRAME *aFrame=nullptr)
static std::vector< KICAD_T > parseRequestedItemTypes(const google::protobuf::RepeatedField< int > &aTypes)
COMMIT * getCurrentCommit(const std::string &aClientName)
virtual void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage)
std::set< std::string > m_activeClients
virtual std::optional< ApiResponseStatus > checkForBusy()
Checks if the editor can accept commands.
HANDLER_RESULT< commands::GetItemsResponse > handleGetItems(const HANDLER_CONTEXT< commands::GetItems > &aCtx)
tl::expected< bool, ApiResponseStatus > validateDocumentInternal(const DocumentSpecifier &aDocument) const override
std::unique_ptr< COMMIT > createCommit() override
Override this to create an appropriate COMMIT subclass for the frame in question.
HANDLER_RESULT< Empty > handleSaveDocument(const HANDLER_CONTEXT< commands::SaveDocument > &aCtx)
SYMBOL_EDITOR_CONTEXT * context() const
static std::set< KICAD_T > s_allowedTypes
LIB_SYMBOL * symbol() const
HANDLER_RESULT< Empty > handleRevertDocument(const HANDLER_CONTEXT< commands::RevertDocument > &aCtx)
HANDLER_RESULT< commands::GetDocumentModifiedStateResponse > handleGetDocumentModifiedState(const HANDLER_CONTEXT< commands::GetDocumentModifiedState > &aCtx) override
void deleteItemsInternal(std::map< KIID, ItemDeletionStatus > &aItemsToDelete, const std::string &aClientName) override
void onModified() override
HANDLER_RESULT< Empty > handleOpenLibraryItem(const HANDLER_CONTEXT< commands::OpenLibraryItem > &aCtx)
HANDLER_RESULT< types::ItemRequestStatus > handleCreateUpdateItemsInternal(bool aCreate, const std::string &aClientName, const types::ItemHeader &aHeader, const google::protobuf::RepeatedPtrField< google::protobuf::Any > &aItems, std::function< void(commands::ItemStatus, google::protobuf::Any)> aItemHandler) override
API_HANDLER_SYMBOL(SYMBOL_EDIT_FRAME *aFrame)
std::shared_ptr< SYMBOL_EDITOR_CONTEXT > m_context
HANDLER_RESULT< commands::GetOpenDocumentsResponse > handleGetOpenDocuments(const HANDLER_CONTEXT< commands::GetOpenDocuments > &aCtx)
HANDLER_RESULT< Empty > handleSaveCopyOfDocument(const HANDLER_CONTEXT< commands::SaveCopyOfDocument > &aCtx)
void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage) override
SYMBOL_EDIT_FRAME * frame() const
std::optional< EDA_ITEM * > getItemFromDocument(const DocumentSpecifier &aDocument, const KIID &aId) override
HANDLER_RESULT< commands::GetItemsResponse > handleGetItemsById(const HANDLER_CONTEXT< commands::GetItemsById > &aCtx)
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
A base class for most all the KiCad significant classes used in schematics and boards.
KICAD_T Type() const
Returns the type of object.
virtual void SetParent(EDA_ITEM *aParent)
void Update(const KIGFX::VIEW_ITEM *aItem, int aUpdateFlags) const override
For dynamic VIEWs, inform the associated VIEW that the graphical representation of this item has chan...
virtual void Add(VIEW_ITEM *aItem, int aDrawPriority=-1)
Add a VIEW_ITEM to the view.
virtual void Remove(VIEW_ITEM *aItem)
Remove a VIEW_ITEM from the view.
std::string AsStdString() const
A logical library item identifier and consists of various portions much like a URI.
bool IsValid() const
Check if this LID_ID is valid.
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
const wxString GetUniStringLibNickname() const
const UTF8 & GetLibItemName() const
Define a library symbol object.
const LIB_ID & GetLibId() const override
Container for project specific data.
virtual const wxString GetProjectPath() const
Return the full path of the project.
virtual const wxString GetProjectName() const
Return the short name of the project.
virtual bool IsNullProject() const
Check if this project is a null project (i.e.
SCH_DRAW_PANEL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
Commit for the schematic and symbol editors.
KIGFX::SCH_VIEW * GetView() const override
Return a pointer to the KIGFX::VIEW instance used in the panel.
Base class for any item which can be embedded within the SCHEMATIC container class,...
void SwapItemData(SCH_ITEM *aImage)
Swap data between aItem and aImage.
virtual void Serialize(google::protobuf::Any &aContainer) const
Serializes this object to the given Any message.
virtual LIB_ID GetLoadedLibId() const =0
virtual LIB_SYMBOL * GetCurSymbol() const =0
virtual SCH_SCREEN * GetScreen() const =0
virtual void SetContentModified(bool aModified=true)=0
virtual PROJECT & Prj() const =0
The symbol library editor main window.
void OnModify() override
Must be called after a schematic change in order to set the "modify" flag of the current symbol.
A type-safe container of any type.
static bool empty(const wxTextEntryBase *aCtrl)
static const std::string KiCadSymbolLibFileExtension
@ GEOMETRY
Position or shape has changed.
KICOMMON_API std::optional< KICAD_T > TypeNameFromAny(const google::protobuf::Any &aMessage)
KICOMMON_API LIB_ID UnpackLibId(const types::LibraryIdentifier &aId)
RequestMessageType Request
std::shared_ptr< SYMBOL_EDITOR_CONTEXT > CreateSymbolEditorFrameContext(SYMBOL_EDIT_FRAME *aFrame)
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition of file extensions used in Kicad.