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.

445 lines
13 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <base_units.h>
  24. #include <pgm_base.h>
  25. #include <sch_edit_frame.h>
  26. #include <plotters/plotter.h>
  27. #include <widgets/msgpanel.h>
  28. #include <bitmaps.h>
  29. #include <string_utils.h>
  30. #include <schematic.h>
  31. #include <settings/color_settings.h>
  32. #include <sch_painter.h>
  33. #include <wx/log.h>
  34. #include <dialogs/html_message_box.h>
  35. #include <project/project_file.h>
  36. #include <project/net_settings.h>
  37. #include <core/kicad_algo.h>
  38. #include <trigo.h>
  39. #include <sch_textbox.h>
  40. #include <tools/sch_navigate_tool.h>
  41. using KIGFX::SCH_RENDER_SETTINGS;
  42. SCH_TEXTBOX::SCH_TEXTBOX( int aLineWidth, FILL_T aFillType, const wxString& text ) :
  43. SCH_SHAPE( SHAPE_T::RECT, aLineWidth, aFillType, SCH_TEXTBOX_T ),
  44. EDA_TEXT( schIUScale, text )
  45. {
  46. m_layer = LAYER_NOTES;
  47. SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  48. SetVertJustify( GR_TEXT_V_ALIGN_TOP );
  49. SetMultilineAllowed( true );
  50. }
  51. SCH_TEXTBOX::SCH_TEXTBOX( const SCH_TEXTBOX& aText ) :
  52. SCH_SHAPE( aText ),
  53. EDA_TEXT( aText )
  54. { }
  55. int SCH_TEXTBOX::GetTextMargin() const
  56. {
  57. return KiROUND( GetTextSize().y * 0.8 );
  58. }
  59. void SCH_TEXTBOX::MirrorHorizontally( int aCenter )
  60. {
  61. // Text is NOT really mirrored; it just has its justification flipped
  62. if( GetTextAngle() == ANGLE_HORIZONTAL )
  63. {
  64. switch( GetHorizJustify() )
  65. {
  66. case GR_TEXT_H_ALIGN_LEFT: SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
  67. case GR_TEXT_H_ALIGN_CENTER: break;
  68. case GR_TEXT_H_ALIGN_RIGHT: SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
  69. }
  70. }
  71. }
  72. void SCH_TEXTBOX::MirrorVertically( int aCenter )
  73. {
  74. // Text is NOT really mirrored; it just has its justification flipped
  75. if( GetTextAngle() == ANGLE_VERTICAL )
  76. {
  77. switch( GetHorizJustify() )
  78. {
  79. case GR_TEXT_H_ALIGN_LEFT: SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
  80. case GR_TEXT_H_ALIGN_CENTER: break;
  81. case GR_TEXT_H_ALIGN_RIGHT: SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
  82. }
  83. }
  84. }
  85. void SCH_TEXTBOX::Rotate( const VECTOR2I& aCenter )
  86. {
  87. SCH_SHAPE::Rotate( aCenter );
  88. SetTextAngle( GetTextAngle() == ANGLE_VERTICAL ? ANGLE_HORIZONTAL : ANGLE_VERTICAL );
  89. }
  90. void SCH_TEXTBOX::Rotate90( bool aClockwise )
  91. {
  92. SetTextAngle( GetTextAngle() == ANGLE_VERTICAL ? ANGLE_HORIZONTAL : ANGLE_VERTICAL );
  93. }
  94. VECTOR2I SCH_TEXTBOX::GetDrawPos() const
  95. {
  96. int margin = GetTextMargin();
  97. BOX2I bbox( m_start, m_end - m_start );
  98. bbox.Normalize();
  99. VECTOR2I pos( bbox.GetLeft() + margin, bbox.GetBottom() - margin );
  100. if( GetTextAngle() == ANGLE_VERTICAL )
  101. {
  102. switch( GetHorizJustify() )
  103. {
  104. case GR_TEXT_H_ALIGN_LEFT:
  105. pos.y = bbox.GetBottom() - margin;
  106. break;
  107. case GR_TEXT_H_ALIGN_CENTER:
  108. pos.y = ( bbox.GetTop() + bbox.GetBottom() ) / 2;
  109. break;
  110. case GR_TEXT_H_ALIGN_RIGHT:
  111. pos.y = bbox.GetTop() + margin;
  112. break;
  113. }
  114. switch( GetVertJustify() )
  115. {
  116. case GR_TEXT_V_ALIGN_TOP:
  117. pos.x = bbox.GetLeft() + margin;
  118. break;
  119. case GR_TEXT_V_ALIGN_CENTER:
  120. pos.x = ( bbox.GetLeft() + bbox.GetRight() ) / 2;
  121. break;
  122. case GR_TEXT_V_ALIGN_BOTTOM:
  123. pos.x = bbox.GetRight() - margin;
  124. break;
  125. }
  126. }
  127. else
  128. {
  129. switch( GetHorizJustify() )
  130. {
  131. case GR_TEXT_H_ALIGN_LEFT:
  132. pos.x = bbox.GetLeft() + margin;
  133. break;
  134. case GR_TEXT_H_ALIGN_CENTER:
  135. pos.x = ( bbox.GetLeft() + bbox.GetRight() ) / 2;
  136. break;
  137. case GR_TEXT_H_ALIGN_RIGHT:
  138. pos.x = bbox.GetRight() - margin;
  139. break;
  140. }
  141. switch( GetVertJustify() )
  142. {
  143. case GR_TEXT_V_ALIGN_TOP:
  144. pos.y = bbox.GetTop() + margin;
  145. break;
  146. case GR_TEXT_V_ALIGN_CENTER:
  147. pos.y = ( bbox.GetTop() + bbox.GetBottom() ) / 2;
  148. break;
  149. case GR_TEXT_V_ALIGN_BOTTOM:
  150. pos.y = bbox.GetBottom() - margin;
  151. break;
  152. }
  153. }
  154. return pos;
  155. }
  156. void SCH_TEXTBOX::SwapData( SCH_ITEM* aItem )
  157. {
  158. SCH_TEXTBOX* item = static_cast<SCH_TEXTBOX*>( aItem );
  159. std::swap( m_layer, item->m_layer );
  160. SwapText( *item );
  161. SwapAttributes( *item );
  162. SCH_SHAPE::SwapData( aItem );
  163. }
  164. bool SCH_TEXTBOX::operator<( const SCH_ITEM& aItem ) const
  165. {
  166. if( Type() != aItem.Type() )
  167. return Type() < aItem.Type();
  168. auto other = static_cast<const SCH_TEXTBOX*>( &aItem );
  169. if( GetLayer() != other->GetLayer() )
  170. return GetLayer() < other->GetLayer();
  171. if( GetPosition().x != other->GetPosition().x )
  172. return GetPosition().x < other->GetPosition().x;
  173. if( GetPosition().y != other->GetPosition().y )
  174. return GetPosition().y < other->GetPosition().y;
  175. return GetText() < other->GetText();
  176. }
  177. KIFONT::FONT* SCH_TEXTBOX::getDrawFont() const
  178. {
  179. KIFONT::FONT* font = EDA_TEXT::GetFont();
  180. if( !font )
  181. font = KIFONT::FONT::GetFont( GetDefaultFont(), IsBold(), IsItalic() );
  182. return font;
  183. }
  184. void SCH_TEXTBOX::Print( const RENDER_SETTINGS* aSettings, const VECTOR2I& aOffset )
  185. {
  186. wxDC* DC = aSettings->GetPrintDC();
  187. int penWidth = GetPenWidth();
  188. bool blackAndWhiteMode = GetGRForceBlackPenState();
  189. VECTOR2I pt1 = GetStart();
  190. VECTOR2I pt2 = GetEnd();
  191. COLOR4D color = GetStroke().GetColor();
  192. PLOT_DASH_TYPE lineStyle = GetStroke().GetPlotStyle();
  193. if( GetFillMode() == FILL_T::FILLED_WITH_COLOR && !blackAndWhiteMode )
  194. GRFilledRect( DC, pt1, pt2, 0, GetFillColor(), GetFillColor() );
  195. if( penWidth > 0 )
  196. {
  197. penWidth = std::max( penWidth, aSettings->GetMinPenWidth() );
  198. if( blackAndWhiteMode || color == COLOR4D::UNSPECIFIED )
  199. color = aSettings->GetLayerColor( m_layer );
  200. if( lineStyle == PLOT_DASH_TYPE::DEFAULT )
  201. lineStyle = PLOT_DASH_TYPE::SOLID;
  202. if( lineStyle == PLOT_DASH_TYPE::SOLID )
  203. {
  204. GRRect( DC, pt1, pt2, penWidth, color );
  205. }
  206. else
  207. {
  208. std::vector<SHAPE*> shapes = MakeEffectiveShapes( true );
  209. for( SHAPE* shape : shapes )
  210. {
  211. STROKE_PARAMS::Stroke( shape, lineStyle, penWidth, aSettings,
  212. [&]( const VECTOR2I& a, const VECTOR2I& b )
  213. {
  214. GRLine( DC, a.x, a.y, b.x, b.y, penWidth, color );
  215. } );
  216. }
  217. for( SHAPE* shape : shapes )
  218. delete shape;
  219. }
  220. }
  221. color = GetTextColor();
  222. if( blackAndWhiteMode || color == COLOR4D::UNSPECIFIED )
  223. color = aSettings->GetLayerColor( m_layer );
  224. EDA_TEXT::Print( aSettings, aOffset, color );
  225. }
  226. wxString SCH_TEXTBOX::GetShownText( int aDepth, bool aAllowExtraText ) const
  227. {
  228. std::function<bool( wxString* )> textResolver =
  229. [&]( wxString* token ) -> bool
  230. {
  231. if( SCH_SHEET* sheet = Schematic()->CurrentSheet().Last() )
  232. {
  233. if( sheet->ResolveTextVar( token, aDepth + 1 ) )
  234. return true;
  235. }
  236. return false;
  237. };
  238. wxString text = EDA_TEXT::GetShownText();
  239. if( HasTextVars() )
  240. {
  241. if( aDepth < 10 )
  242. text = ExpandTextVars( text, &textResolver );
  243. }
  244. KIFONT::FONT* font = GetFont();
  245. if( !font )
  246. font = KIFONT::FONT::GetFont( GetDefaultFont(), IsBold(), IsItalic() );
  247. VECTOR2D size = GetEnd() - GetStart();
  248. int colWidth = GetTextAngle() == ANGLE_HORIZONTAL ? size.x : size.y;
  249. colWidth = abs( colWidth ) - GetTextMargin() * 2;
  250. font->LinebreakText( text, colWidth, GetTextSize(), GetTextThickness(), IsBold(), IsItalic() );
  251. return text;
  252. }
  253. bool SCH_TEXTBOX::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  254. {
  255. BOX2I rect = GetBoundingBox();
  256. rect.Inflate( aAccuracy );
  257. return rect.Contains( aPosition );
  258. }
  259. bool SCH_TEXTBOX::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  260. {
  261. BOX2I rect = aRect;
  262. rect.Inflate( aAccuracy );
  263. if( aContained )
  264. return rect.Contains( GetBoundingBox() );
  265. return rect.Intersects( GetBoundingBox() );
  266. }
  267. void SCH_TEXTBOX::DoHypertextAction( EDA_DRAW_FRAME* aFrame ) const
  268. {
  269. wxCHECK_MSG( IsHypertext(), /* void */,
  270. "Calling a hypertext menu on a SCH_TEXTBOX with no hyperlink?" );
  271. SCH_NAVIGATE_TOOL* navTool = aFrame->GetToolManager()->GetTool<SCH_NAVIGATE_TOOL>();
  272. navTool->HypertextCommand( m_hyperlink );
  273. }
  274. wxString SCH_TEXTBOX::GetItemDescription( UNITS_PROVIDER* aUnitsProvider ) const
  275. {
  276. return wxString::Format( _( "Graphic Text Box" ) );
  277. }
  278. BITMAPS SCH_TEXTBOX::GetMenuImage() const
  279. {
  280. return BITMAPS::add_textbox;
  281. }
  282. void SCH_TEXTBOX::Plot( PLOTTER* aPlotter, bool aBackground ) const
  283. {
  284. if( aBackground )
  285. {
  286. SCH_SHAPE::Plot( aPlotter, aBackground );
  287. return;
  288. }
  289. RENDER_SETTINGS* settings = aPlotter->RenderSettings();
  290. int penWidth = GetPenWidth();
  291. COLOR4D color = GetStroke().GetColor();
  292. PLOT_DASH_TYPE lineStyle = GetStroke().GetPlotStyle();
  293. if( penWidth > 0 )
  294. {
  295. penWidth = std::max( penWidth, settings->GetMinPenWidth() );
  296. if( !aPlotter->GetColorMode() || color == COLOR4D::UNSPECIFIED )
  297. color = settings->GetLayerColor( m_layer );
  298. if( lineStyle == PLOT_DASH_TYPE::DEFAULT )
  299. lineStyle = PLOT_DASH_TYPE::SOLID;
  300. aPlotter->SetColor( color );
  301. aPlotter->SetDash( penWidth, lineStyle );
  302. aPlotter->Rect( m_start, m_end, FILL_T::NO_FILL, penWidth );
  303. aPlotter->SetDash( penWidth, PLOT_DASH_TYPE::SOLID );
  304. }
  305. KIFONT::FONT* font = GetFont();
  306. if( !font )
  307. font = KIFONT::FONT::GetFont( settings->GetDefaultFont(), IsBold(), IsItalic() );
  308. color = GetTextColor();
  309. if( !aPlotter->GetColorMode() || color == COLOR4D::UNSPECIFIED )
  310. color = settings->GetLayerColor( m_layer );
  311. penWidth = GetEffectiveTextPenWidth( settings->GetDefaultPenWidth() );
  312. penWidth = std::max( penWidth, settings->GetMinPenWidth() );
  313. aPlotter->SetCurrentLineWidth( penWidth );
  314. std::vector<VECTOR2I> positions;
  315. wxArrayString strings_list;
  316. wxStringSplit( GetShownText(), strings_list, '\n' );
  317. positions.reserve( strings_list.Count() );
  318. GetLinePositions( positions, (int) strings_list.Count() );
  319. for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
  320. {
  321. aPlotter->Text( positions[ii], color, strings_list.Item( ii ), GetTextAngle(),
  322. GetTextSize(), GetHorizJustify(), GetVertJustify(), penWidth, IsItalic(),
  323. IsBold(), false, font );
  324. }
  325. if( HasHyperlink() )
  326. aPlotter->HyperlinkBox( GetBoundingBox(), GetHyperlink() );
  327. }
  328. void SCH_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  329. {
  330. // Don't use GetShownText() here; we want to show the user the variable references
  331. aList.emplace_back( _( "Text Box" ), KIUI::EllipsizeStatusText( aFrame, GetText() ) );
  332. aList.emplace_back( _( "Font" ), GetFont() ? GetFont()->GetName() : _( "Default" ) );
  333. wxString textStyle[] = { _( "Normal" ), _( "Italic" ), _( "Bold" ), _( "Bold Italic" ) };
  334. int style = IsBold() && IsItalic() ? 3 : IsBold() ? 2 : IsItalic() ? 1 : 0;
  335. aList.emplace_back( _( "Style" ), textStyle[style] );
  336. aList.emplace_back( _( "Text Size" ), aFrame->MessageTextFromValue( GetTextWidth() ) );
  337. aList.emplace_back( _( "Box Width" ),
  338. aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
  339. aList.emplace_back( _( "Box Height" ),
  340. aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
  341. m_stroke.GetMsgPanelInfo( aFrame, aList );
  342. }