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.

494 lines
16 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 <dialogs/html_message_box.h>
  34. #include <project/project_file.h>
  35. #include <project/net_settings.h>
  36. #include <core/kicad_algo.h>
  37. #include <trigo.h>
  38. #include <lib_textbox.h>
  39. using KIGFX::SCH_RENDER_SETTINGS;
  40. LIB_TEXTBOX::LIB_TEXTBOX( LIB_SYMBOL* aParent, int aLineWidth, FILL_T aFillType,
  41. const wxString& text ) :
  42. LIB_SHAPE( aParent, SHAPE_T::RECT, aLineWidth, aFillType, LIB_TEXTBOX_T ),
  43. EDA_TEXT( schIUScale, text )
  44. {
  45. SetTextSize( wxSize( schIUScale.MilsToIU( DEFAULT_TEXT_SIZE ), schIUScale.MilsToIU( DEFAULT_TEXT_SIZE ) ) );
  46. SetHorizJustify( GR_TEXT_H_ALIGN_LEFT );
  47. SetVertJustify( GR_TEXT_V_ALIGN_TOP );
  48. SetMultilineAllowed( true );
  49. }
  50. LIB_TEXTBOX::LIB_TEXTBOX( const LIB_TEXTBOX& aText ) :
  51. LIB_SHAPE( aText ),
  52. EDA_TEXT( aText )
  53. { }
  54. int LIB_TEXTBOX::GetTextMargin() const
  55. {
  56. return KiROUND( GetTextSize().y * 0.8 );
  57. }
  58. void LIB_TEXTBOX::MirrorHorizontally( const VECTOR2I& center )
  59. {
  60. // Text is NOT really mirrored; it just has its justification flipped
  61. if( GetTextAngle() == ANGLE_HORIZONTAL )
  62. {
  63. switch( GetHorizJustify() )
  64. {
  65. case GR_TEXT_H_ALIGN_LEFT: SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
  66. case GR_TEXT_H_ALIGN_CENTER: break;
  67. case GR_TEXT_H_ALIGN_RIGHT: SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
  68. }
  69. }
  70. }
  71. void LIB_TEXTBOX::MirrorVertically( const VECTOR2I& center )
  72. {
  73. // Text is NOT really mirrored; it just has its justification flipped
  74. if( GetTextAngle() == ANGLE_VERTICAL )
  75. {
  76. switch( GetHorizJustify() )
  77. {
  78. case GR_TEXT_H_ALIGN_LEFT: SetHorizJustify( GR_TEXT_H_ALIGN_RIGHT ); break;
  79. case GR_TEXT_H_ALIGN_CENTER: break;
  80. case GR_TEXT_H_ALIGN_RIGHT: SetHorizJustify( GR_TEXT_H_ALIGN_LEFT ); break;
  81. }
  82. }
  83. }
  84. void LIB_TEXTBOX::Rotate( const VECTOR2I& aCenter, bool aRotateCCW )
  85. {
  86. LIB_SHAPE::Rotate( aCenter, aRotateCCW );
  87. SetTextAngle( GetTextAngle() == ANGLE_VERTICAL ? ANGLE_HORIZONTAL : ANGLE_VERTICAL );
  88. }
  89. VECTOR2I LIB_TEXTBOX::GetDrawPos() const
  90. {
  91. int margin = GetTextMargin();
  92. BOX2I bbox( VECTOR2I( std::min( m_start.x, m_end.x ), std::min( -m_start.y, -m_end.y ) ),
  93. VECTOR2I( abs( m_end.x - m_start.x ), abs( m_end.y - m_start.y ) ) );
  94. VECTOR2I pos( bbox.GetLeft() + margin, bbox.GetBottom() - margin );
  95. if( GetTextAngle() == ANGLE_VERTICAL )
  96. {
  97. switch( GetHorizJustify() )
  98. {
  99. case GR_TEXT_H_ALIGN_LEFT:
  100. pos.y = bbox.GetBottom() - margin;
  101. break;
  102. case GR_TEXT_H_ALIGN_CENTER:
  103. pos.y = ( bbox.GetTop() + bbox.GetBottom() ) / 2;
  104. break;
  105. case GR_TEXT_H_ALIGN_RIGHT:
  106. pos.y = bbox.GetTop() + margin;
  107. break;
  108. }
  109. switch( GetVertJustify() )
  110. {
  111. case GR_TEXT_V_ALIGN_TOP:
  112. pos.x = bbox.GetLeft() + margin;
  113. break;
  114. case GR_TEXT_V_ALIGN_CENTER:
  115. pos.x = ( bbox.GetLeft() + bbox.GetRight() ) / 2;
  116. break;
  117. case GR_TEXT_V_ALIGN_BOTTOM:
  118. pos.x = bbox.GetRight() - margin;
  119. break;
  120. }
  121. }
  122. else
  123. {
  124. switch( GetHorizJustify() )
  125. {
  126. case GR_TEXT_H_ALIGN_LEFT:
  127. pos.x = bbox.GetLeft() + margin;
  128. break;
  129. case GR_TEXT_H_ALIGN_CENTER:
  130. pos.x = ( bbox.GetLeft() + bbox.GetRight() ) / 2;
  131. break;
  132. case GR_TEXT_H_ALIGN_RIGHT:
  133. pos.x = bbox.GetRight() - margin;
  134. break;
  135. }
  136. switch( GetVertJustify() )
  137. {
  138. case GR_TEXT_V_ALIGN_TOP:
  139. pos.y = bbox.GetTop() + margin;
  140. break;
  141. case GR_TEXT_V_ALIGN_CENTER:
  142. pos.y = ( bbox.GetTop() + bbox.GetBottom() ) / 2;
  143. break;
  144. case GR_TEXT_V_ALIGN_BOTTOM:
  145. pos.y = bbox.GetBottom() - margin;
  146. break;
  147. }
  148. }
  149. return pos;
  150. }
  151. int LIB_TEXTBOX::compare( const LIB_ITEM& aOther, int aCompareFlags ) const
  152. {
  153. wxASSERT( aOther.Type() == LIB_TEXTBOX_T );
  154. int retv = LIB_ITEM::compare( aOther, aCompareFlags );
  155. if( retv )
  156. return retv;
  157. const LIB_TEXTBOX* tmp = static_cast<const LIB_TEXTBOX*>( &aOther );
  158. int result = GetText().CmpNoCase( tmp->GetText() );
  159. if( result != 0 )
  160. return result;
  161. if( GetTextWidth() != tmp->GetTextWidth() )
  162. return GetTextWidth() - tmp->GetTextWidth();
  163. if( GetTextHeight() != tmp->GetTextHeight() )
  164. return GetTextHeight() - tmp->GetTextHeight();
  165. if( IsBold() != tmp->IsBold() )
  166. return IsBold() - tmp->IsBold();
  167. if( IsItalic() != tmp->IsItalic() )
  168. return IsItalic() - tmp->IsItalic();
  169. if( GetHorizJustify() != tmp->GetHorizJustify() )
  170. return GetHorizJustify() - tmp->GetHorizJustify();
  171. if( GetTextAngle().AsTenthsOfADegree() != tmp->GetTextAngle().AsTenthsOfADegree() )
  172. return GetTextAngle().AsTenthsOfADegree() - tmp->GetTextAngle().AsTenthsOfADegree();
  173. return EDA_SHAPE::Compare( &static_cast<const LIB_SHAPE&>( aOther ) );
  174. }
  175. KIFONT::FONT* LIB_TEXTBOX::getDrawFont() const
  176. {
  177. KIFONT::FONT* font = EDA_TEXT::GetFont();
  178. if( !font )
  179. font = KIFONT::FONT::GetFont( GetDefaultFont(), IsBold(), IsItalic() );
  180. return font;
  181. }
  182. void LIB_TEXTBOX::print( const RENDER_SETTINGS* aSettings, const VECTOR2I& aOffset, void* aData,
  183. const TRANSFORM& aTransform, bool aDimmed )
  184. {
  185. if( IsPrivate() )
  186. return;
  187. bool forceNoFill = static_cast<bool>( aData );
  188. bool blackAndWhiteMode = GetGRForceBlackPenState();
  189. int penWidth = GetEffectivePenWidth( aSettings );
  190. COLOR4D color = GetStroke().GetColor();
  191. PLOT_DASH_TYPE lineStyle = GetStroke().GetPlotStyle();
  192. wxDC* DC = aSettings->GetPrintDC();
  193. VECTOR2I pt1 = aTransform.TransformCoordinate( m_start ) + aOffset;
  194. VECTOR2I pt2 = aTransform.TransformCoordinate( m_end ) + aOffset;
  195. if( !forceNoFill && GetFillMode() == FILL_T::FILLED_WITH_COLOR && !blackAndWhiteMode )
  196. GRFilledRect( DC, pt1, pt2, penWidth, GetFillColor(), GetFillColor() );
  197. if( penWidth > 0 )
  198. {
  199. penWidth = std::max( penWidth, aSettings->GetMinPenWidth() );
  200. if( blackAndWhiteMode || color == COLOR4D::UNSPECIFIED )
  201. color = aSettings->GetLayerColor( LAYER_DEVICE );
  202. COLOR4D bg = aSettings->GetBackgroundColor();
  203. if( bg == COLOR4D::UNSPECIFIED || GetGRForceBlackPenState() )
  204. bg = COLOR4D::WHITE;
  205. if( aDimmed )
  206. color = color.Mix( bg, 0.5f );
  207. if( lineStyle == PLOT_DASH_TYPE::DEFAULT )
  208. lineStyle = PLOT_DASH_TYPE::SOLID;
  209. if( lineStyle <= PLOT_DASH_TYPE::FIRST_TYPE )
  210. {
  211. GRRect( DC, pt1, pt2, penWidth, color );
  212. }
  213. else
  214. {
  215. std::vector<SHAPE*> shapes = MakeEffectiveShapes( true );
  216. for( SHAPE* shape : shapes )
  217. {
  218. STROKE_PARAMS::Stroke( shape, lineStyle, penWidth, aSettings,
  219. [&]( const VECTOR2I& a, const VECTOR2I& b )
  220. {
  221. VECTOR2I pts = aTransform.TransformCoordinate( a ) + aOffset;
  222. VECTOR2I pte = aTransform.TransformCoordinate( b ) + aOffset;
  223. GRLine( DC, pts.x, pts.y, pte.x, pte.y, penWidth, color );
  224. } );
  225. }
  226. for( SHAPE* shape : shapes )
  227. delete shape;
  228. }
  229. }
  230. LIB_TEXTBOX text( *this );
  231. color = GetTextColor();
  232. if( blackAndWhiteMode || color == COLOR4D::UNSPECIFIED )
  233. color = aSettings->GetLayerColor( LAYER_DEVICE );
  234. COLOR4D bg = aSettings->GetBackgroundColor();
  235. if( bg == COLOR4D::UNSPECIFIED || GetGRForceBlackPenState() )
  236. bg = COLOR4D::WHITE;
  237. if( aDimmed )
  238. color = color.Mix( bg, 0.5f );
  239. penWidth = std::max( GetEffectiveTextPenWidth(), aSettings->GetMinPenWidth() );
  240. if( aTransform.y1 )
  241. {
  242. text.SetTextAngle( text.GetTextAngle() == ANGLE_HORIZONTAL ? ANGLE_VERTICAL
  243. : ANGLE_HORIZONTAL );
  244. }
  245. KIFONT::FONT* font = GetFont();
  246. if( !font )
  247. font = KIFONT::FONT::GetFont( aSettings->GetDefaultFont(), IsBold(), IsItalic() );
  248. // NB: GetDrawPos() will want Symbol Editor (upside-down) coordinates
  249. text.SetStart( VECTOR2I( pt1.x, -pt1.y ) );
  250. text.SetEnd( VECTOR2I( pt2.x, -pt2.y ) );
  251. GRPrintText( DC, text.GetDrawPos(), color, text.GetShownText(), text.GetTextAngle(),
  252. text.GetTextSize(), text.GetHorizJustify(), text.GetVertJustify(), penWidth,
  253. text.IsItalic(), text.IsBold(), font );
  254. }
  255. wxString LIB_TEXTBOX::GetShownText( int aDepth, bool aAllowExtraText ) const
  256. {
  257. wxString text = EDA_TEXT::GetShownText();
  258. KIFONT::FONT* font = GetFont();
  259. VECTOR2D size = GetEnd() - GetStart();
  260. int colWidth = GetTextAngle() == ANGLE_HORIZONTAL ? size.x : size.y;
  261. if( !font )
  262. font = KIFONT::FONT::GetFont( GetDefaultFont(), IsBold(), IsItalic() );
  263. colWidth = abs( colWidth ) - GetTextMargin() * 2;
  264. font->LinebreakText( text, colWidth, GetTextSize(), GetTextThickness(), IsBold(), IsItalic() );
  265. return text;
  266. }
  267. bool LIB_TEXTBOX::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  268. {
  269. if( aAccuracy < schIUScale.MilsToIU( MINIMUM_SELECTION_DISTANCE ) )
  270. aAccuracy = schIUScale.MilsToIU( MINIMUM_SELECTION_DISTANCE );
  271. BOX2I rect = GetBoundingBox();
  272. rect.Inflate( aAccuracy );
  273. return rect.Contains( aPosition );
  274. }
  275. bool LIB_TEXTBOX::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  276. {
  277. BOX2I rect = aRect;
  278. rect.Inflate( aAccuracy );
  279. if( aContained )
  280. return rect.Contains( GetBoundingBox() );
  281. return rect.Intersects( GetBoundingBox() );
  282. }
  283. wxString LIB_TEXTBOX::GetSelectMenuText( UNITS_PROVIDER* aUnitsProvider ) const
  284. {
  285. return wxString::Format( _( "Graphic Text Box" ) );
  286. }
  287. BITMAPS LIB_TEXTBOX::GetMenuImage() const
  288. {
  289. return BITMAPS::add_textbox;
  290. }
  291. void LIB_TEXTBOX::Plot( PLOTTER* aPlotter, bool aBackground, const VECTOR2I& aOffset,
  292. const TRANSFORM& aTransform, bool aDimmed ) const
  293. {
  294. wxASSERT( aPlotter != nullptr );
  295. if( IsPrivate() )
  296. return;
  297. if( aBackground )
  298. {
  299. LIB_SHAPE::Plot( aPlotter, aBackground, aOffset, aTransform, aDimmed );
  300. return;
  301. }
  302. RENDER_SETTINGS* renderSettings = aPlotter->RenderSettings();
  303. VECTOR2I start = aTransform.TransformCoordinate( m_start ) + aOffset;
  304. VECTOR2I end = aTransform.TransformCoordinate( m_end ) + aOffset;
  305. COLOR4D bg = renderSettings->GetBackgroundColor();
  306. if( bg == COLOR4D::UNSPECIFIED || !aPlotter->GetColorMode() )
  307. bg = COLOR4D::WHITE;
  308. int penWidth = GetEffectivePenWidth( renderSettings );
  309. COLOR4D color = GetStroke().GetColor();
  310. PLOT_DASH_TYPE lineStyle = GetStroke().GetPlotStyle();
  311. if( penWidth > 0 )
  312. {
  313. if( !aPlotter->GetColorMode() || color == COLOR4D::UNSPECIFIED )
  314. color = renderSettings->GetLayerColor( LAYER_DEVICE );
  315. if( lineStyle == PLOT_DASH_TYPE::DEFAULT )
  316. lineStyle = PLOT_DASH_TYPE::DASH;
  317. if( aDimmed )
  318. color = color.Mix( bg, 0.5f );
  319. aPlotter->SetColor( color );
  320. aPlotter->SetDash( penWidth, lineStyle );
  321. aPlotter->Rect( start, end, FILL_T::NO_FILL, penWidth );
  322. aPlotter->SetDash( penWidth, PLOT_DASH_TYPE::SOLID );
  323. }
  324. KIFONT::FONT* font = GetFont();
  325. if( !font )
  326. font = KIFONT::FONT::GetFont( renderSettings->GetDefaultFont(), IsBold(), IsItalic() );
  327. LIB_TEXTBOX text( *this );
  328. color = GetTextColor();
  329. if( !aPlotter->GetColorMode() || color == COLOR4D::UNSPECIFIED )
  330. color = renderSettings->GetLayerColor( LAYER_DEVICE );
  331. if( aDimmed )
  332. color = color.Mix( bg, 0.5f );
  333. penWidth = std::max( GetEffectiveTextPenWidth(), aPlotter->RenderSettings()->GetMinPenWidth() );
  334. if( aTransform.y1 )
  335. {
  336. text.SetTextAngle( text.GetTextAngle() == ANGLE_HORIZONTAL ? ANGLE_VERTICAL
  337. : ANGLE_HORIZONTAL );
  338. }
  339. // NB: GetDrawPos() will want Symbol Editor (upside-down) coordinates
  340. text.SetStart( VECTOR2I( start.x, -start.y ) );
  341. text.SetEnd( VECTOR2I( end.x, -end.y ) );
  342. std::vector<VECTOR2I> positions;
  343. wxArrayString strings_list;
  344. wxStringSplit( GetShownText(), strings_list, '\n' );
  345. positions.reserve( strings_list.Count() );
  346. text.GetLinePositions( positions, (int) strings_list.Count() );
  347. for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
  348. {
  349. aPlotter->Text( positions[ii], color, strings_list.Item( ii ), text.GetTextAngle(),
  350. text.GetTextSize(), text.GetHorizJustify(), text.GetVertJustify(),
  351. penWidth, text.IsItalic(), text.IsBold(), false, font );
  352. }
  353. }
  354. void LIB_TEXTBOX::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  355. {
  356. // Don't use GetShownText() here; we want to show the user the variable references
  357. aList.emplace_back( _( "Text Box" ), KIUI::EllipsizeStatusText( aFrame, GetText() ) );
  358. aList.emplace_back( _( "Font" ), GetFont() ? GetFont()->GetName() : _( "Default" ) );
  359. wxString textStyle[] = { _( "Normal" ), _( "Italic" ), _( "Bold" ), _( "Bold Italic" ) };
  360. int style = IsBold() && IsItalic() ? 3 : IsBold() ? 2 : IsItalic() ? 1 : 0;
  361. aList.emplace_back( _( "Style" ), textStyle[style] );
  362. aList.emplace_back( _( "Text Size" ), aFrame->MessageTextFromValue( GetTextWidth() ) );
  363. aList.emplace_back( _( "Box Width" ),
  364. aFrame->MessageTextFromValue( std::abs( GetEnd().x - GetStart().x ) ) );
  365. aList.emplace_back( _( "Box Height" ),
  366. aFrame->MessageTextFromValue( std::abs( GetEnd().y - GetStart().y ) ) );
  367. m_stroke.GetMsgPanelInfo( aFrame, aList );
  368. }
  369. void LIB_TEXTBOX::ViewGetLayers( int aLayers[], int& aCount ) const
  370. {
  371. aCount = 3;
  372. aLayers[0] = IsPrivate() ? LAYER_PRIVATE_NOTES : LAYER_DEVICE;
  373. aLayers[1] = IsPrivate() ? LAYER_NOTES_BACKGROUND : LAYER_DEVICE_BACKGROUND;
  374. aLayers[2] = LAYER_SELECTION_SHADOWS;
  375. }