You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

473 lines
15 KiB

5 years ago
5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 KiCad Developers, see AUTHORS.TXT for contributors.
  5. * Copyright (C) 2017-2022 KiCad Developers, see AUTHORS.txt for contributors.
  6. * @author Kristoffer Ödmark
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <wx/clipbrd.h>
  26. #include <wx/log.h>
  27. #include <board.h>
  28. #include <ignore.h>
  29. #include <pad.h>
  30. #include <pcb_group.h>
  31. #include <pcb_shape.h>
  32. #include <pcb_text.h>
  33. #include <pcb_textbox.h>
  34. #include <fp_text.h>
  35. #include <fp_textbox.h>
  36. #include <zone.h>
  37. #include <locale_io.h>
  38. #include <netinfo.h>
  39. #include <plugins/kicad/pcb_parser.h>
  40. #include <plugins/kicad/pcb_plugin.h>
  41. #include <kicad_clipboard.h>
  42. #include "confirm.h"
  43. CLIPBOARD_IO::CLIPBOARD_IO():
  44. PCB_PLUGIN(CTL_FOR_CLIPBOARD ),
  45. m_formatter()
  46. {
  47. m_out = &m_formatter;
  48. }
  49. CLIPBOARD_IO::~CLIPBOARD_IO()
  50. {
  51. }
  52. void CLIPBOARD_IO::SetBoard( BOARD* aBoard )
  53. {
  54. m_board = aBoard;
  55. }
  56. void CLIPBOARD_IO::SaveSelection( const PCB_SELECTION& aSelected, bool isFootprintEditor )
  57. {
  58. VECTOR2I refPoint( 0, 0 );
  59. // dont even start if the selection is empty
  60. if( aSelected.Empty() )
  61. return;
  62. if( aSelected.HasReferencePoint() )
  63. refPoint = aSelected.GetReferencePoint();
  64. // Prepare net mapping that assures that net codes saved in a file are consecutive integers
  65. m_mapping->SetBoard( m_board );
  66. if( aSelected.Size() == 1 && aSelected.Front()->Type() == PCB_FOOTPRINT_T )
  67. {
  68. // make the footprint safe to transfer to other pcbs
  69. const FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aSelected.Front() );
  70. // Do not modify existing board
  71. FOOTPRINT newFootprint( *footprint );
  72. for( PAD* pad : newFootprint.Pads() )
  73. pad->SetNetCode( 0 );
  74. // locked means "locked in place"; copied items therefore can't be locked
  75. newFootprint.SetLocked( false );
  76. // locate the reference point at (0, 0) in the copied items
  77. newFootprint.Move( VECTOR2I( -refPoint.x, -refPoint.y ) );
  78. Format( static_cast<BOARD_ITEM*>( &newFootprint ) );
  79. }
  80. else if( isFootprintEditor )
  81. {
  82. FOOTPRINT partialFootprint( m_board );
  83. // Useful to copy the selection to the board editor (if any), and provides
  84. // a dummy lib id.
  85. // Perhaps not a good Id, but better than a empty id
  86. KIID dummy;
  87. LIB_ID id( "clipboard", dummy.AsString() );
  88. partialFootprint.SetFPID( id );
  89. for( const EDA_ITEM* item : aSelected )
  90. {
  91. const PCB_GROUP* group = dynamic_cast<const PCB_GROUP*>( item );
  92. BOARD_ITEM* clone;
  93. if( const FP_TEXT* text = dyn_cast<const FP_TEXT*>( item ) )
  94. {
  95. if( text->GetType() != FP_TEXT::TEXT_is_DIVERS )
  96. continue;
  97. }
  98. if( group )
  99. clone = static_cast<BOARD_ITEM*>( group->DeepClone() );
  100. else
  101. clone = static_cast<BOARD_ITEM*>( item->Clone() );
  102. // If it is only a footprint, clear the nets from the pads
  103. if( PAD* pad = dyn_cast<PAD*>( clone ) )
  104. pad->SetNetCode( 0 );
  105. // Add the pad to the new footprint before moving to ensure the local coords are
  106. // correct
  107. partialFootprint.Add( clone );
  108. // A list of not added items, when adding items to the footprint
  109. // some FP_TEXT (reference and value) cannot be added to the footprint
  110. std::vector<BOARD_ITEM*> skipped_items;
  111. if( group )
  112. {
  113. static_cast<PCB_GROUP*>( clone )->RunOnDescendants(
  114. [&]( BOARD_ITEM* descendant )
  115. {
  116. // One cannot add a text reference or value to a given footprint:
  117. // only one is allowed. So add only FP_TEXT::TEXT_is_DIVERS
  118. bool can_add = true;
  119. if( const FP_TEXT* text = dyn_cast<const FP_TEXT*>( descendant ) )
  120. {
  121. if( text->GetType() != FP_TEXT::TEXT_is_DIVERS )
  122. can_add = false;
  123. }
  124. if( can_add )
  125. partialFootprint.Add( descendant );
  126. else
  127. skipped_items.push_back( descendant );
  128. } );
  129. }
  130. // locate the reference point at (0, 0) in the copied items
  131. clone->Move( -refPoint );
  132. // Now delete items, duplicated but not added:
  133. for( BOARD_ITEM* skp_item : skipped_items )
  134. delete skp_item;
  135. }
  136. // Set the new relative internal local coordinates of copied items
  137. FOOTPRINT* editedFootprint = m_board->Footprints().front();
  138. VECTOR2I moveVector = partialFootprint.GetPosition() + editedFootprint->GetPosition();
  139. partialFootprint.MoveAnchorPosition( moveVector );
  140. Format( &partialFootprint, 0 );
  141. }
  142. else
  143. {
  144. // we will fake being a .kicad_pcb to get the full parser kicking
  145. // This means we also need layers and nets
  146. LOCALE_IO io;
  147. m_formatter.Print( 0, "(kicad_pcb (version %d) (generator pcbnew)\n",
  148. SEXPR_BOARD_FILE_VERSION );
  149. m_formatter.Print( 0, "\n" );
  150. formatBoardLayers( m_board );
  151. formatNetInformation( m_board );
  152. m_formatter.Print( 0, "\n" );
  153. for( EDA_ITEM* i : aSelected )
  154. {
  155. BOARD_ITEM* item = static_cast<BOARD_ITEM*>( i );
  156. BOARD_ITEM* copy = nullptr;
  157. if( item->Type() == PCB_FP_SHAPE_T )
  158. {
  159. // Convert to PCB_SHAPE_T
  160. copy = (BOARD_ITEM*) reinterpret_cast<PCB_SHAPE*>( item )->Clone();
  161. copy->SetLayer( item->GetLayer() );
  162. }
  163. else if( item->Type() == PCB_FP_TEXT_T )
  164. {
  165. // Convert to PCB_TEXT_T
  166. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( item->GetParent() );
  167. FP_TEXT* fp_text = static_cast<FP_TEXT*>( item );
  168. PCB_TEXT* pcb_text = new PCB_TEXT( m_board );
  169. if( fp_text->GetText() == wxT( "${VALUE}" ) )
  170. pcb_text->SetText( footprint->GetValue() );
  171. else if( fp_text->GetText() == wxT( "${REFERENCE}" ) )
  172. pcb_text->SetText( footprint->GetReference() );
  173. else
  174. pcb_text->CopyText( *fp_text );
  175. pcb_text->SetAttributes( *fp_text );
  176. pcb_text->SetLayer( fp_text->GetLayer() );
  177. copy = pcb_text;
  178. }
  179. else if( item->Type() == PCB_FP_TEXTBOX_T )
  180. {
  181. // Convert to PCB_TEXTBOX_T
  182. FP_TEXTBOX* fp_textbox = static_cast<FP_TEXTBOX*>( item );
  183. PCB_TEXTBOX* pcb_textbox = new PCB_TEXTBOX( m_board );
  184. pcb_textbox->CopyText( *fp_textbox );
  185. pcb_textbox->SetAttributes( *fp_textbox );
  186. pcb_textbox->SetLayer( fp_textbox->GetLayer() );
  187. copy = pcb_textbox;
  188. }
  189. else if( item->Type() == PCB_PAD_T )
  190. {
  191. // Create a parent to own the copied pad
  192. FOOTPRINT* footprint = new FOOTPRINT( m_board );
  193. PAD* pad = (PAD*) item->Clone();
  194. footprint->SetPosition( pad->GetPosition() );
  195. pad->SetPos0( VECTOR2I() );
  196. footprint->Add( pad );
  197. copy = footprint;
  198. }
  199. else if( item->Type() == PCB_FP_ZONE_T )
  200. {
  201. // Convert to PCB_ZONE_T
  202. ZONE* zone = new ZONE( m_board );
  203. zone->InitDataFromSrcInCopyCtor( *static_cast<ZONE*>( item ) );
  204. copy = zone;
  205. }
  206. else if( item->Type() == PCB_GROUP_T )
  207. {
  208. copy = static_cast<PCB_GROUP*>( item )->DeepClone();
  209. }
  210. else
  211. {
  212. copy = static_cast<BOARD_ITEM*>( item->Clone() );
  213. }
  214. auto prepItem = [&]( BOARD_ITEM* aItem )
  215. {
  216. aItem->SetLocked( false );
  217. };
  218. if( copy )
  219. {
  220. prepItem( copy );
  221. // locate the reference point at (0, 0) in the copied items
  222. copy->Move( -refPoint );
  223. Format( copy, 1 );
  224. if( copy->Type() == PCB_GROUP_T )
  225. {
  226. static_cast<PCB_GROUP*>( copy )->RunOnDescendants( prepItem );
  227. static_cast<PCB_GROUP*>( copy )->RunOnDescendants( [&]( BOARD_ITEM* titem )
  228. {
  229. Format( titem, 1 );
  230. } );
  231. }
  232. delete copy;
  233. }
  234. }
  235. m_formatter.Print( 0, "\n)" );
  236. }
  237. // These are placed at the end to minimize the open time of the clipboard
  238. wxLogNull doNotLog; // disable logging of failed clipboard actions
  239. auto clipboard = wxTheClipboard;
  240. wxClipboardLocker clipboardLock( clipboard );
  241. if( !clipboardLock || !clipboard->IsOpened() )
  242. return;
  243. clipboard->SetData( new wxTextDataObject( wxString( m_formatter.GetString().c_str(),
  244. wxConvUTF8 ) ) );
  245. clipboard->Flush();
  246. #ifndef __WXOSX__
  247. // This section exists to return the clipboard data, ensuring it has fully
  248. // been processed by the system clipboard. This appears to be needed for
  249. // extremely large clipboard copies on asynchronous linux clipboard managers
  250. // such as KDE's Klipper. However, a read back of the data on OSX before the
  251. // clipboard is closed seems to cause an ASAN error (heap-buffer-overflow)
  252. // since it uses the cached version of the clipboard data and not the system
  253. // clipboard data.
  254. if( clipboard->IsSupported( wxDF_TEXT ) || clipboard->IsSupported( wxDF_UNICODETEXT ) )
  255. {
  256. wxTextDataObject data;
  257. clipboard->GetData( data );
  258. ignore_unused( data.GetText() );
  259. }
  260. #endif
  261. }
  262. BOARD_ITEM* CLIPBOARD_IO::Parse()
  263. {
  264. BOARD_ITEM* item;
  265. wxString result;
  266. wxLogNull doNotLog; // disable logging of failed clipboard actions
  267. auto clipboard = wxTheClipboard;
  268. wxClipboardLocker clipboardLock( clipboard );
  269. if( !clipboardLock )
  270. return nullptr;
  271. if( clipboard->IsSupported( wxDF_TEXT ) || clipboard->IsSupported( wxDF_UNICODETEXT ) )
  272. {
  273. wxTextDataObject data;
  274. clipboard->GetData( data );
  275. result = data.GetText();
  276. }
  277. try
  278. {
  279. item = PCB_PLUGIN::Parse( result );
  280. }
  281. catch (...)
  282. {
  283. item = nullptr;
  284. }
  285. return item;
  286. }
  287. void CLIPBOARD_IO::Save( const wxString& aFileName, BOARD* aBoard,
  288. const PROPERTIES* aProperties )
  289. {
  290. init( aProperties );
  291. m_board = aBoard; // after init()
  292. // Prepare net mapping that assures that net codes saved in a file are consecutive integers
  293. m_mapping->SetBoard( aBoard );
  294. STRING_FORMATTER formatter;
  295. m_out = &formatter;
  296. m_out->Print( 0, "(kicad_pcb (version %d) (generator pcbnew)\n", SEXPR_BOARD_FILE_VERSION );
  297. Format( aBoard, 1 );
  298. m_out->Print( 0, ")\n" );
  299. wxLogNull doNotLog; // disable logging of failed clipboard actions
  300. auto clipboard = wxTheClipboard;
  301. wxClipboardLocker clipboardLock( clipboard );
  302. if( !clipboardLock )
  303. return;
  304. clipboard->SetData( new wxTextDataObject(
  305. wxString( m_formatter.GetString().c_str(), wxConvUTF8 ) ) );
  306. clipboard->Flush();
  307. // This section exists to return the clipboard data, ensuring it has fully
  308. // been processed by the system clipboard. This appears to be needed for
  309. // extremely large clipboard copies on asynchronous linux clipboard managers
  310. // such as KDE's Klipper
  311. if( clipboard->IsSupported( wxDF_TEXT ) || clipboard->IsSupported( wxDF_UNICODETEXT ) )
  312. {
  313. wxTextDataObject data;
  314. clipboard->GetData( data );
  315. ignore_unused( data.GetText() );
  316. }
  317. }
  318. BOARD* CLIPBOARD_IO::Load( const wxString& aFileName, BOARD* aAppendToMe,
  319. const PROPERTIES* aProperties, PROJECT* aProject,
  320. PROGRESS_REPORTER* aProgressReporter )
  321. {
  322. std::string result;
  323. wxLogNull doNotLog; // disable logging of failed clipboard actions
  324. auto clipboard = wxTheClipboard;
  325. wxClipboardLocker clipboardLock( clipboard );
  326. if( !clipboardLock )
  327. return nullptr;
  328. if( clipboard->IsSupported( wxDF_TEXT ) || clipboard->IsSupported( wxDF_UNICODETEXT ) )
  329. {
  330. wxTextDataObject data;
  331. clipboard->GetData( data );
  332. result = data.GetText().mb_str();
  333. }
  334. std::function<bool( wxString, int, wxString, wxString )> queryUser =
  335. [&]( wxString aTitle, int aIcon, wxString aMessage, wxString aAction ) -> bool
  336. {
  337. KIDIALOG dlg( nullptr, aMessage, aTitle, wxOK | wxCANCEL | aIcon );
  338. if( !aAction.IsEmpty() )
  339. dlg.SetOKLabel( aAction );
  340. dlg.DoNotShowCheckbox( aMessage, 0 );
  341. return dlg.ShowModal() == wxID_OK;
  342. };
  343. STRING_LINE_READER reader( result, wxT( "clipboard" ) );
  344. PCB_PARSER parser( &reader, aAppendToMe, &queryUser );
  345. init( aProperties );
  346. BOARD_ITEM* item;
  347. BOARD* board;
  348. try
  349. {
  350. item = parser.Parse();
  351. }
  352. catch( const FUTURE_FORMAT_ERROR& )
  353. {
  354. // Don't wrap a FUTURE_FORMAT_ERROR in another
  355. throw;
  356. }
  357. catch( const PARSE_ERROR& parse_error )
  358. {
  359. if( parser.IsTooRecent() )
  360. throw FUTURE_FORMAT_ERROR( parse_error, parser.GetRequiredVersion() );
  361. else
  362. throw;
  363. }
  364. if( item->Type() != PCB_T )
  365. {
  366. // The parser loaded something that was valid, but wasn't a board.
  367. THROW_PARSE_ERROR( _( "Clipboard content is not KiCad compatible" ), parser.CurSource(),
  368. parser.CurLine(), parser.CurLineNumber(), parser.CurOffset() );
  369. }
  370. else
  371. {
  372. board = dynamic_cast<BOARD*>( item );
  373. }
  374. // Give the filename to the board if it's new
  375. if( board && !aAppendToMe )
  376. board->SetFileName( aFileName );
  377. return board;
  378. }