KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_drawing_sheet_reactive.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
20#define BOOST_TEST_NO_MAIN
21#include <boost/test/unit_test.hpp>
22
23#include <algorithm>
24#include <base_units.h>
29#include <page_info.h>
30#include <text_var_dependency.h>
31#include <title_block.h>
32
33
34namespace
35{
36struct DefaultLayoutFixture
37{
38 DefaultLayoutFixture()
39 {
40 // The drawing-sheet system is singleton-based; tests depend on a
41 // populated default layout to find text items to scan.
43 }
44};
45}
46
47
48BOOST_FIXTURE_TEST_SUITE( DrawingSheetReactive, DefaultLayoutFixture )
49
50
51BOOST_AUTO_TEST_CASE( CollectKeysFindsTitleBlockSources )
52{
53 // Default drawing sheet template references PROJECTNAME, REVISION, TITLE,
54 // COMMENT[1..9], ISSUE_DATE, COMPANY, etc. — the set is defined by the
55 // drawing sheet parser. Collect them and confirm several well-known ones
56 // are present.
57 PAGE_INFO page;
58 TITLE_BLOCK tb;
59
60 DS_PROXY_VIEW_ITEM proxy( unityScale, &page, nullptr, &tb, nullptr );
61
62 const auto keys = proxy.CollectTextVarKeys();
63
64 auto hasKey = [&]( TEXT_VAR_REF_KEY::KIND k, const wxString& primary )
65 {
66 return std::any_of( keys.begin(), keys.end(),
67 [&]( const TEXT_VAR_REF_KEY& key )
68 { return key.kind == k && key.primary == primary; } );
69 };
70
71 // The default drawing sheet always surfaces at least these tokens.
72 BOOST_CHECK( hasKey( TEXT_VAR_REF_KEY::KIND::TITLE_BLOCK, wxT( "REVISION" ) ) );
73 BOOST_CHECK( hasKey( TEXT_VAR_REF_KEY::KIND::TITLE_BLOCK, wxT( "TITLE" ) ) );
74 BOOST_CHECK( hasKey( TEXT_VAR_REF_KEY::KIND::TITLE_BLOCK, wxT( "COMPANY" ) ) );
75}
76
77
78BOOST_AUTO_TEST_CASE( AttachRegistersProxyAsDependent )
79{
80 PAGE_INFO page;
81 TITLE_BLOCK tb;
82
83 TEXT_VAR_TRACKER tracker;
84
85 {
86 DS_PROXY_VIEW_ITEM proxy( unityScale, &page, nullptr, &tb, nullptr );
87 proxy.AttachToTracker( &tracker );
88
89 // The proxy should be registered as a dependent on every title-block key
90 // its template references.
91 BOOST_CHECK(
92 tracker.Index().DependentCount(
93 TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) ) ) > 0u );
94 BOOST_CHECK(
95 tracker.Index().DependentCount(
96 TEXT_VAR_REF_KEY::FromToken( wxT( "TITLE" ) ) ) > 0u );
97 }
98}
99
100
101BOOST_AUTO_TEST_CASE( InvalidationReachesProxyViaListener )
102{
103 PAGE_INFO page;
104 TITLE_BLOCK tb;
105
106 TEXT_VAR_TRACKER tracker;
107
108 {
109 DS_PROXY_VIEW_ITEM proxy( unityScale, &page, nullptr, &tb, nullptr );
110 proxy.AttachToTracker( &tracker );
111
112 // Frames install a long-lived listener that routes invalidations to the
113 // current drawing sheet proxy. Verify the dispatch semantics with a
114 // minimal listener.
115 EDA_ITEM* seenDep = nullptr;
116 (void) tracker.AddInvalidateListener(
117 [&]( EDA_ITEM* dep, const TEXT_VAR_REF_KEY& )
118 {
119 if( dep == &proxy )
120 seenDep = dep;
121 } );
122
123 tracker.InvalidateKey( TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) ) );
124
125 BOOST_CHECK_EQUAL( seenDep, &proxy );
126 }
127}
128
129
130BOOST_AUTO_TEST_CASE( DetachUnregistersProxy )
131{
132 PAGE_INFO page;
133 TITLE_BLOCK tb;
134
135 DS_PROXY_VIEW_ITEM proxy( unityScale, &page, nullptr, &tb, nullptr );
136 TEXT_VAR_TRACKER tracker;
137
138 proxy.AttachToTracker( &tracker );
139 BOOST_CHECK(
140 tracker.Index().DependentCount(
141 TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) ) ) > 0u );
142
143 proxy.AttachToTracker( nullptr );
145 tracker.Index().DependentCount(
146 TEXT_VAR_REF_KEY::FromToken( wxT( "REVISION" ) ) ),
147 0u );
148}
149
150
151BOOST_AUTO_TEST_CASE( DestructorAutoUnregisters )
152{
153 PAGE_INFO page;
154 TITLE_BLOCK tb;
155
156 TEXT_VAR_TRACKER tracker;
157
158 {
159 DS_PROXY_VIEW_ITEM proxy( unityScale, &page, nullptr, &tb, nullptr );
160 proxy.AttachToTracker( &tracker );
161 BOOST_REQUIRE( tracker.Index().ItemCount() > 0u );
162 }
163
164 // Proxy destructor must clean up the index so no dangling pointer remains
165 // for a future invalidation to match against.
166 BOOST_CHECK_EQUAL( tracker.Index().ItemCount(), 0u );
167}
168
169
170BOOST_AUTO_TEST_CASE( RepeatedTextKeepsAllInstancesWithNegativeStep )
171{
172 // Regression: a repeated text used to lose every copy when the step was
173 // negative, because IsInsidePage() tested a phantom end point anchored to
174 // the bottom-right corner (text has no real end point). See #24309.
176 model.ClearList();
177
178 DS_DATA_ITEM_TEXT* text = new DS_DATA_ITEM_TEXT( wxT( "1" ) );
179 text->SetStart( 60.0, 60.0, LT_CORNER );
180 text->m_RepeatCount = 8;
181 text->m_IncrementLabel = 1;
182 text->m_IncrementVector = VECTOR2D( -5.0, -5.0 );
183 model.Append( text );
184
185 PAGE_INFO page;
186 TITLE_BLOCK tb;
187
188 DS_DRAW_ITEM_LIST drawList( unityScale );
189 drawList.BuildDrawItemsList( page, tb );
190
191 int textCount = 0;
192
193 for( DS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item; item = drawList.GetNext() )
194 {
195 if( item->Type() == WSG_TEXT_T )
196 textCount++;
197 }
198
199 BOOST_CHECK_EQUAL( textCount, 8 );
200}
201
202
203static int repeatCountAtY( double aY )
204{
206 model.ClearList();
207
208 DS_DATA_ITEM_TEXT* text = new DS_DATA_ITEM_TEXT( wxT( "1" ) );
209 text->SetStart( 25.0, aY, LT_CORNER );
210 text->m_RepeatCount = 100;
211 text->m_IncrementLabel = 1;
212 text->m_IncrementVector = VECTOR2D( 50.0, 0.0 );
213 model.Append( text );
214
215 PAGE_INFO page;
216 TITLE_BLOCK tb;
217
218 DS_DRAW_ITEM_LIST drawList( unityScale );
219 drawList.BuildDrawItemsList( page, tb );
220
221 int count = 0;
222
223 for( DS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item; item = drawList.GetNext() )
224 {
225 if( item->Type() == WSG_TEXT_T )
226 count++;
227 }
228
229 return count;
230}
231
232
233BOOST_AUTO_TEST_CASE( RepeatedTextAnchoredInPageMarginKeepsItsRepeats )
234{
235 int insideCount = repeatCountAtY( 1.0 );
236
237 BOOST_REQUIRE_GT( insideCount, 1 );
238
239 // A run anchored in the page margin must not lose its repeats. See #24309.
240 BOOST_CHECK_EQUAL( repeatCountAtY( -2.5 ), insideCount );
241}
242
243
244static std::vector<wxString> repeatTexts( const wxString& aBase, int aCount, int aLabelStep )
245{
247 model.ClearList();
248
250 text->SetStart( 60.0, 60.0, LT_CORNER );
251 text->m_RepeatCount = aCount;
252 text->m_IncrementLabel = aLabelStep;
253 text->m_IncrementVector = VECTOR2D( 2.0, 2.0 );
254 model.Append( text );
255
256 PAGE_INFO page;
257 TITLE_BLOCK tb;
258
259 DS_DRAW_ITEM_LIST drawList( unityScale );
260 drawList.BuildDrawItemsList( page, tb );
261
262 std::vector<wxString> out;
263
264 for( DS_DRAW_ITEM_BASE* item = drawList.GetFirst(); item; item = drawList.GetNext() )
265 {
266 if( item->Type() == WSG_TEXT_T )
267 out.push_back( static_cast<DS_DRAW_ITEM_TEXT*>( item )->GetText() );
268 }
269
270 return out;
271}
272
273
274BOOST_AUTO_TEST_CASE( RepeatedLabelStepsLettersAndNumbersCleanly )
275{
276 // A trailing letter must roll z -> aa, never into punctuation, and a
277 // trailing multi-digit number must carry (19 -> 20, not 110).
278 const std::vector<wxString> letters = repeatTexts( wxT( "y" ), 5, 1 );
279 const std::vector<wxString> expLetters = { wxT( "y" ), wxT( "z" ), wxT( "aa" ), wxT( "ab" ), wxT( "ac" ) };
280 BOOST_CHECK_EQUAL_COLLECTIONS( letters.begin(), letters.end(), expLetters.begin(), expLetters.end() );
281
282 const std::vector<wxString> numbers = repeatTexts( wxT( "19" ), 3, 1 );
283 const std::vector<wxString> expNumbers = { wxT( "19" ), wxT( "20" ), wxT( "21" ) };
284 BOOST_CHECK_EQUAL_COLLECTIONS( numbers.begin(), numbers.end(), expNumbers.begin(), expNumbers.end() );
285}
286
287
288BOOST_AUTO_TEST_CASE( RepeatedLabelStepsPastNonAsciiCharacters, *boost::unit_test::timeout( 30 ) )
289{
290 // A non-ASCII character in the label used to hang the incrementer, so the draw list never
291 // finished building (#25299). U+3042 is hiragana A, U+FF11 a full-width one
292 const std::vector<wxString> mixed = repeatTexts( wxString( L"1あ" ), 3, 1 );
293 const std::vector<wxString> expMixed = { wxString( L"1あ" ), wxString( L"2あ" ),
294 wxString( L"3あ" ) };
295 BOOST_CHECK_EQUAL_COLLECTIONS( mixed.begin(), mixed.end(), expMixed.begin(), expMixed.end() );
296
297 // Full-width digits have no ASCII part to step, so every copy keeps the base label
298 const std::vector<wxString> fullWidth = repeatTexts( wxString( L"1" ), 3, 1 );
299 const std::vector<wxString> expFullWidth = { wxString( L"1" ), wxString( L"1" ),
300 wxString( L"1" ) };
302 expFullWidth.end() );
303}
304
305
constexpr EDA_IU_SCALE unityScale
Definition base_units.h:124
Handle the graphic items list to draw/plot the frame and title block.
static DS_DATA_MODEL & GetTheInstance()
Return the instance of DS_DATA_MODEL used in the application.
Base class to handle basic graphic items.
Store the list of graphic items: rect, lines, polygons and texts to draw/plot the title block and fra...
DS_DRAW_ITEM_BASE * GetFirst()
void BuildDrawItemsList(const PAGE_INFO &aPageInfo, const TITLE_BLOCK &aTitleBlock)
Drawing or plot the drawing sheet.
DS_DRAW_ITEM_BASE * GetNext()
A graphic text.
void AttachToTracker(TEXT_VAR_TRACKER *aTracker)
Register this proxy with aTracker as a dependent on every title-block source variable its current tem...
std::vector< TEXT_VAR_REF_KEY > CollectTextVarKeys() const
Walk the current drawing-sheet definition and collect every ${...} reference encountered in its text ...
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
std::size_t DependentCount(const TEXT_VAR_REF_KEY &aKey) const
Coordinates the dependency index with change notifications.
ListenerHandle AddInvalidateListener(InvalidateCallback aCallback)
Register a listener that fires for every invalidation.
TEXT_VAR_DEPENDENCY_INDEX & Index()
void InvalidateKey(const TEXT_VAR_REF_KEY &aKey)
Fan out invalidation for a single explicit key — used when a non-item source changes (e....
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:38
@ LT_CORNER
Identifies a single resolvable source that a text item's ${...} reference depends on.
static TEXT_VAR_REF_KEY FromToken(const wxString &aToken)
Parse a raw token (the text between ${ and }) into a key using lexical classification only — no looku...
KIND
Categorizes a reference by the source that will produce its value.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
static int repeatCountAtY(double aY)
const std::vector< wxString > expMixed
const std::vector< wxString > fullWidth
static std::vector< wxString > repeatTexts(const wxString &aBase, int aCount, int aLabelStep)
const std::vector< wxString > expFullWidth
BOOST_AUTO_TEST_CASE(CollectKeysFindsTitleBlockSources)
BOOST_CHECK_EQUAL_COLLECTIONS(mixed.begin(), mixed.end(), expMixed.begin(), expMixed.end())
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
KIBIS_MODEL * model
BOOST_CHECK_EQUAL(result, "25.4")
@ WSG_TEXT_T
Definition typeinfo.h:215
VECTOR2< double > VECTOR2D
Definition vector2d.h:682