KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_editor.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 Jon Evans <[email protected]>
5 * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
22#include <api/api_utils.h>
23#include <eda_base_frame.h>
24#include <eda_item.h>
25#include <wx/wx.h>
26
27using namespace kiapi::common::commands;
28
29
32 m_frame( aFrame )
33{
34 registerHandler<BeginCommit, BeginCommitResponse>( &API_HANDLER_EDITOR::handleBeginCommit );
35 registerHandler<EndCommit, EndCommitResponse>( &API_HANDLER_EDITOR::handleEndCommit );
36 registerHandler<CreateItems, CreateItemsResponse>( &API_HANDLER_EDITOR::handleCreateItems );
37 registerHandler<UpdateItems, UpdateItemsResponse>( &API_HANDLER_EDITOR::handleUpdateItems );
38 registerHandler<DeleteItems, DeleteItemsResponse>( &API_HANDLER_EDITOR::handleDeleteItems );
39 registerHandler<HitTest, HitTestResponse>( &API_HANDLER_EDITOR::handleHitTest );
40}
41
42
44 const HANDLER_CONTEXT& aCtx )
45{
46 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
47 return tl::unexpected( *busy );
48
49 if( m_commits.count( aCtx.ClientName ) )
50 {
51 ApiResponseStatus e;
52 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
53 e.set_error_message( fmt::format( "the client {} already has a commit in progress",
54 aCtx.ClientName ) );
55 return tl::unexpected( e );
56 }
57
58 wxASSERT( !m_activeClients.count( aCtx.ClientName ) );
59
60 BeginCommitResponse response;
61
62 KIID id;
63 m_commits[aCtx.ClientName] = std::make_pair( id, createCommit() );
64 response.mutable_id()->set_value( id.AsStdString() );
65
66 m_activeClients.insert( aCtx.ClientName );
67
68 return response;
69}
70
71
73 const HANDLER_CONTEXT& aCtx )
74{
75 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
76 return tl::unexpected( *busy );
77
78 if( !m_commits.count( aCtx.ClientName ) )
79 {
80 ApiResponseStatus e;
81 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
82 e.set_error_message( fmt::format( "the client {} does not has a commit in progress",
83 aCtx.ClientName ) );
84 return tl::unexpected( e );
85 }
86
87 wxASSERT( m_activeClients.count( aCtx.ClientName ) );
88
89 const std::pair<KIID, std::unique_ptr<COMMIT>>& pair = m_commits.at( aCtx.ClientName );
90 const KIID& id = pair.first;
91 const std::unique_ptr<COMMIT>& commit = pair.second;
92
93 EndCommitResponse response;
94
95 // Do not check IDs with drop; it is a safety net in case the id was lost on the client side
96 switch( aMsg.action() )
97 {
98 case kiapi::common::commands::CMA_DROP:
99 {
100 commit->Revert();
101 m_commits.erase( aCtx.ClientName );
102 m_activeClients.erase( aCtx.ClientName );
103 break;
104 }
105
106 case kiapi::common::commands::CMA_COMMIT:
107 {
108 if( aMsg.id().value().compare( id.AsStdString() ) != 0 )
109 {
110 ApiResponseStatus e;
111 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
112 e.set_error_message( fmt::format( "the id {} does not match the commit in progress",
113 aMsg.id().value() ) );
114 return tl::unexpected( e );
115 }
116
117 pushCurrentCommit( aCtx, wxString( aMsg.message().c_str(), wxConvUTF8 ) );
118 break;
119 }
120
121 default:
122 break;
123 }
124
125 return response;
126}
127
128
130{
131 if( !m_commits.count( aCtx.ClientName ) )
132 {
133 KIID id;
134 m_commits[aCtx.ClientName] = std::make_pair( id, createCommit() );
135 }
136
137 return m_commits.at( aCtx.ClientName ).second.get();
138}
139
140
141void API_HANDLER_EDITOR::pushCurrentCommit( const HANDLER_CONTEXT& aCtx, const wxString& aMessage )
142{
143 auto it = m_commits.find( aCtx.ClientName );
144
145 if( it == m_commits.end() )
146 return;
147
148 it->second.second->Push( aMessage.IsEmpty() ? m_defaultCommitMessage : aMessage );
149 m_commits.erase( it );
150 m_activeClients.erase( aCtx.ClientName );
151}
152
153
155 const kiapi::common::types::DocumentSpecifier& aDocument )
156{
157 if( !validateDocumentInternal( aDocument ) )
158 {
159 ApiResponseStatus e;
160 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
161 e.set_error_message( fmt::format( "the requested document {} is not open",
162 aDocument.board_filename() ) );
163 return tl::unexpected( e );
164 }
165
166 return true;
167}
168
169
171 const kiapi::common::types::ItemHeader& aHeader )
172{
173 if( !aHeader.has_document() || aHeader.document().type() != thisDocumentType() )
174 {
175 ApiResponseStatus e;
176 e.set_status( ApiStatusCode::AS_UNHANDLED );
177 // No error message, this is a flag that the server should try a different handler
178 return tl::unexpected( e );
179 }
180
181 HANDLER_RESULT<bool> documentValidation = validateDocument( aHeader.document() );
182
183 if( !documentValidation )
184 return tl::unexpected( documentValidation.error() );
185
186 if( !validateDocumentInternal( aHeader.document() ) )
187 {
188 ApiResponseStatus e;
189 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
190 e.set_error_message( fmt::format( "the requested document {} is not open",
191 aHeader.document().board_filename() ) );
192 return tl::unexpected( e );
193 }
194
195 if( aHeader.has_container() )
196 {
197 return KIID( aHeader.container().value() );
198 }
199
200 // Valid header, but no container provided
201 return std::nullopt;
202}
203
204
205std::optional<ApiResponseStatus> API_HANDLER_EDITOR::checkForBusy()
206{
208 {
209 ApiResponseStatus e;
210 e.set_status( ApiStatusCode::AS_BUSY );
211 e.set_error_message( "KiCad is busy and cannot respond to API requests right now" );
212 return e;
213 }
214
215 return std::nullopt;
216}
217
218
220 const HANDLER_CONTEXT& aCtx )
221{
222 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
223 return tl::unexpected( *busy );
224
225 CreateItemsResponse response;
226
228 aMsg.header(), aMsg.items(),
229 [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem )
230 {
231 ItemCreationResult itemResult;
232 itemResult.mutable_status()->CopyFrom( aStatus );
233 itemResult.mutable_item()->CopyFrom( aItem );
234 response.mutable_created_items()->Add( std::move( itemResult ) );
235 } );
236
237 if( !result.has_value() )
238 return tl::unexpected( result.error() );
239
240 response.set_status( *result );
241 return response;
242}
243
244
246 const HANDLER_CONTEXT& aCtx )
247{
248 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
249 return tl::unexpected( *busy );
250
251 UpdateItemsResponse response;
252
254 aMsg.header(), aMsg.items(),
255 [&]( const ItemStatus& aStatus, const google::protobuf::Any& aItem )
256 {
257 ItemUpdateResult itemResult;
258 itemResult.mutable_status()->CopyFrom( aStatus );
259 itemResult.mutable_item()->CopyFrom( aItem );
260 response.mutable_updated_items()->Add( std::move( itemResult ) );
261 } );
262
263 if( !result.has_value() )
264 return tl::unexpected( result.error() );
265
266 response.set_status( *result );
267 return response;
268}
269
270
272 const HANDLER_CONTEXT& aCtx )
273{
274 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
275 return tl::unexpected( *busy );
276
277 if( !validateItemHeaderDocument( aMsg.header() ) )
278 {
279 ApiResponseStatus e;
280 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
281 e.set_status( ApiStatusCode::AS_UNHANDLED );
282 return tl::unexpected( e );
283 }
284
285 std::map<KIID, ItemDeletionStatus> itemsToDelete;
286
287 for( const kiapi::common::types::KIID& kiidBuf : aMsg.item_ids() )
288 {
289 if( !kiidBuf.value().empty() )
290 {
291 KIID kiid( kiidBuf.value() );
292 itemsToDelete[kiid] = ItemDeletionStatus::IDS_NONEXISTENT;
293 }
294 }
295
296 if( itemsToDelete.empty() )
297 {
298 ApiResponseStatus e;
299 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
300 e.set_error_message( "no valid items to delete were given" );
301 return tl::unexpected( e );
302 }
303
304 deleteItemsInternal( itemsToDelete, aCtx );
305
306 DeleteItemsResponse response;
307
308 for( const auto& [id, status] : itemsToDelete )
309 {
310 ItemDeletionResult result;
311 result.mutable_id()->set_value( id.AsStdString() );
312 result.set_status( status );
313 }
314
315 response.set_status( kiapi::common::types::ItemRequestStatus::IRS_OK );
316 return response;
317}
318
319
321 const HANDLER_CONTEXT& aCtx )
322{
323 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
324 return tl::unexpected( *busy );
325
326 if( !validateItemHeaderDocument( aMsg.header() ) )
327 {
328 ApiResponseStatus e;
329 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
330 e.set_status( ApiStatusCode::AS_UNHANDLED );
331 return tl::unexpected( e );
332 }
333
334 HitTestResponse response;
335
336 std::optional<EDA_ITEM*> item = getItemFromDocument( aMsg.header().document(),
337 KIID( aMsg.id().value() ) );
338
339 if( !item )
340 {
341 ApiResponseStatus e;
342 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
343 e.set_error_message( "the requested item ID is not present in the given document" );
344 return tl::unexpected( e );
345 }
346
347 if( ( *item )->HitTest( kiapi::common::UnpackVector2( aMsg.position() ), aMsg.tolerance() ) )
348 response.set_result( HitTestResult::HTR_HIT );
349 else
350 response.set_result( HitTestResult::HTR_NO_HIT );
351
352 return response;
353}
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition: api_handler.h:45
HANDLER_RESULT< commands::HitTestResponse > handleHitTest(commands::HitTest &aMsg, const HANDLER_CONTEXT &aCtx)
virtual void pushCurrentCommit(const HANDLER_CONTEXT &aCtx, const wxString &aMessage)
virtual HANDLER_RESULT< ItemRequestStatus > handleCreateUpdateItemsInternal(bool aCreate, const HANDLER_CONTEXT &aCtx, const types::ItemHeader &aHeader, const google::protobuf::RepeatedPtrField< google::protobuf::Any > &aItems, std::function< void(commands::ItemStatus, google::protobuf::Any)> aItemHandler)=0
virtual kiapi::common::types::DocumentType thisDocumentType() const =0
Override this to specify which document type this editor handles.
HANDLER_RESULT< commands::BeginCommitResponse > handleBeginCommit(commands::BeginCommit &aMsg, const HANDLER_CONTEXT &aCtx)
virtual std::unique_ptr< COMMIT > createCommit()=0
Override this to create an appropriate COMMIT subclass for the frame in question.
HANDLER_RESULT< bool > validateDocument(const DocumentSpecifier &aDocument)
HANDLER_RESULT< commands::UpdateItemsResponse > handleUpdateItems(commands::UpdateItems &aMsg, const HANDLER_CONTEXT &aCtx)
virtual std::optional< EDA_ITEM * > getItemFromDocument(const DocumentSpecifier &aDocument, const KIID &aId)=0
HANDLER_RESULT< commands::EndCommitResponse > handleEndCommit(commands::EndCommit &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< std::optional< KIID > > validateItemHeaderDocument(const kiapi::common::types::ItemHeader &aHeader)
If the header is valid, returns the item container.
COMMIT * getCurrentCommit(const HANDLER_CONTEXT &aCtx)
API_HANDLER_EDITOR(EDA_BASE_FRAME *aFrame=nullptr)
HANDLER_RESULT< commands::DeleteItemsResponse > handleDeleteItems(commands::DeleteItems &aMsg, const HANDLER_CONTEXT &aCtx)
std::set< std::string > m_activeClients
std::map< std::string, std::pair< KIID, std::unique_ptr< COMMIT > > > m_commits
virtual void deleteItemsInternal(std::map< KIID, ItemDeletionStatus > &aItemsToDelete, const HANDLER_CONTEXT &aCtx)=0
virtual std::optional< ApiResponseStatus > checkForBusy()
Checks if the editor can accept commands.
HANDLER_RESULT< commands::CreateItemsResponse > handleCreateItems(commands::CreateItems &aMsg, const HANDLER_CONTEXT &aCtx)
EDA_BASE_FRAME * m_frame
virtual bool validateDocumentInternal(const DocumentSpecifier &aDocument) const =0
static const wxString m_defaultCommitMessage
Definition: api_handler.h:131
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition: commit.h:74
The base frame for deriving all KiCad main window classes.
virtual bool CanAcceptApiCommands()
Checks if this frame is ready to accept API commands.
Definition: kiid.h:49
Base window classes and related definitions.
VECTOR2I UnpackVector2(const types::Vector2 &aInput)
Definition: api_utils.cpp:75
std::string ClientName
Definition: api_handler.h:50