KiCad PCB EDA Suite
Loading...
Searching...
No Matches
wx_data_view_hyperlink_renderer.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
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 */
20
22
23#include <wx/dcclient.h>
24#include <wx/settings.h>
25#include <wx/utils.h>
26
27
28namespace
29{
30
31// Parse [label](url) starting at aStart
32bool tryParseMarkdown( const wxString& aText, size_t aStart, size_t& aEnd, wxString& aLabel, wxString& aHref )
33{
34 if( aStart >= aText.length() || aText[aStart] != '[' )
35 return false;
36
37 size_t labelEnd = aText.find( ']', aStart + 1 );
38
39 if( labelEnd == wxString::npos || labelEnd + 1 >= aText.length() || aText[labelEnd + 1] != '(' )
40 return false;
41
42 // Match balanced parens so URLs like "file:///foo (1).pdf" survive.
43 size_t hrefStart = labelEnd + 2;
44 size_t hrefEnd = wxString::npos;
45 int depth = 1;
46
47 for( size_t i = hrefStart; i < aText.length(); ++i )
48 {
49 wxChar c = aText[i];
50
51 if( c == '(' )
52 ++depth;
53 else if( c == ')' && --depth == 0 )
54 {
55 hrefEnd = i;
56 break;
57 }
58 }
59
60 if( hrefEnd == wxString::npos )
61 return false;
62
63 aLabel = aText.SubString( aStart + 1, labelEnd - 1 );
64 aHref = aText.SubString( hrefStart, hrefEnd - 1 );
65 aEnd = hrefEnd + 1;
66 return true;
67}
68
69
70void layoutRuns( wxDC& aDC, std::vector<HYPERLINK_DV_RENDERER::RUN>& aRuns, int aX, int aY, int aHeight )
71{
72 int x = aX;
73
74 for( HYPERLINK_DV_RENDERER::RUN& run : aRuns )
75 {
76 wxSize ext = aDC.GetTextExtent( run.text );
77 run.bounds = wxRect( x, aY, ext.x, aHeight > 0 ? aHeight : ext.y );
78 x += ext.x;
79 }
80}
81
82} // namespace
83
84
85bool HYPERLINK_DV_RENDERER::IsSafeUrl( const wxString& aHref )
86{
87 wxString lower = aHref.Lower();
88
89 if( lower.StartsWith( wxT( "http://" ) ) || lower.StartsWith( wxT( "https://" ) ) )
90 return true;
91
92 bool isLocalFile = lower.StartsWith( wxT( "file://" ) ) || lower.StartsWith( wxT( "\\\\" ) );
93
94 if( isLocalFile )
95 {
96 static const wxString blockedExtensions[] = { wxT( ".exe" ), wxT( ".com" ), wxT( ".bat" ), wxT( ".cmd" ),
97 wxT( ".ps1" ), wxT( ".vbs" ), wxT( ".js" ), wxT( ".jar" ),
98 wxT( ".msi" ), wxT( ".scr" ), wxT( ".pif" ), wxT( ".lnk" ),
99 wxT( ".hta" ) };
100
101 for( const wxString& ext : blockedExtensions )
102 {
103 if( lower.EndsWith( ext ) )
104 return false;
105 }
106
107 return true;
108 }
109
110 return false;
111}
112
113
114wxString HYPERLINK_DV_RENDERER::StripMarkup( const wxString& aValue )
115{
116 std::vector<RUN> runs;
117 ParseRuns( aValue, runs );
118
119 wxString result;
120
121 for( const RUN& run : runs )
122 result += run.text;
123
124 return result;
125}
126
127
128void HYPERLINK_DV_RENDERER::ParseRuns( const wxString& aValue, std::vector<RUN>& aRuns )
129{
130 aRuns.clear();
131
132 size_t pos = 0;
133 wxString plain;
134
135 while( pos < aValue.length() )
136 {
137 size_t end = pos;
138 wxString label;
139 wxString href;
140
141 if( aValue[pos] == '[' && tryParseMarkdown( aValue, pos, end, label, href ) )
142 {
143 if( !plain.IsEmpty() )
144 {
145 aRuns.push_back( { plain, wxEmptyString, wxRect() } );
146 plain.clear();
147 }
148
149 if( IsSafeUrl( href ) )
150 aRuns.push_back( { label, href, wxRect() } );
151 else
152 aRuns.push_back( { label, wxEmptyString, wxRect() } );
153
154 pos = end;
155 }
156 else
157 {
158 plain += aValue[pos];
159 ++pos;
160 }
161 }
162
163 if( !plain.IsEmpty() )
164 aRuns.push_back( { plain, wxEmptyString, wxRect() } );
165}
166
167
169 wxDataViewCustomRenderer( wxT( "string" ), wxDATAVIEW_CELL_INERT, wxDVR_DEFAULT_ALIGNMENT )
170{
171}
172
173
174bool HYPERLINK_DV_RENDERER::SetValue( const wxVariant& aValue )
175{
176 m_value = aValue.GetString();
178 return true;
179}
180
181
182bool HYPERLINK_DV_RENDERER::GetValue( wxVariant& aValue ) const
183{
184 aValue = m_value;
185 return true;
186}
187
188
190{
191 if( m_value.IsEmpty() )
192 return wxSize( wxDVC_DEFAULT_RENDERER_SIZE, wxDVC_DEFAULT_RENDERER_SIZE );
193
194 // Sum the visible-text width across runs (markup is hidden, so the raw
195 // m_value width over-reports).
196 wxSize size( 0, 0 );
197
198 for( const RUN& run : m_runs )
199 {
200 wxSize ext = GetTextExtent( run.text );
201 size.x += ext.x;
202 size.y = std::max( size.y, ext.y );
203 }
204
205 if( size.y == 0 )
206 size.y = wxDVC_DEFAULT_RENDERER_SIZE;
207
208 return size;
209}
210
211
212bool HYPERLINK_DV_RENDERER::Render( wxRect aCell, wxDC* aDC, int aState )
213{
214 RenderBackground( aDC, aCell );
215
216 // Capture aDC's font so HitTestRunsForCell can measure with the same font.
217 m_renderFont = aDC->GetFont();
218
219 layoutRuns( *aDC, m_runs, aCell.x, aCell.y, aCell.height );
220
221 const bool selected = ( aState & wxDATAVIEW_CELL_SELECTED ) != 0;
222 int xoffset = 0;
223
224#ifdef __WXOSX__
225 const int textState = 0;
226#else
227 const int textState = aState;
228#endif
229
230 for( const RUN& run : m_runs )
231 {
232 wxRect runRect = aCell;
233 runRect.x += xoffset;
234 runRect.width = run.bounds.width;
235
236 if( !run.href.IsEmpty() )
237 {
238 wxDCTextColourChanger col( *aDC, selected ? wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT )
239 : wxSystemSettings::GetColour( wxSYS_COLOUR_HOTLIGHT ) );
240
241 wxFont linkFont = aDC->GetFont();
242 linkFont.SetUnderlined( true );
243 wxDCFontChanger font( *aDC, linkFont );
244
245 RenderText( run.text, 0, runRect, aDC, textState );
246 }
247 else
248 {
249#ifdef __WXOSX__
250 if( selected )
251 aDC->SetTextForeground( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT ) );
252#endif
253 RenderText( run.text, 0, runRect, aDC, textState );
254 }
255
256 xoffset += run.bounds.width;
257 }
258
259 return true;
260}
261
262
263bool HYPERLINK_DV_RENDERER::HitTestRunsForCell( const wxString& aValue, const wxRect& aCell, const wxPoint& aPoint,
264 wxString* aHref ) const
265{
266 std::vector<RUN> runs;
267 ParseRuns( aValue, runs );
268
269 wxClientDC dc( const_cast<wxDataViewCtrl*>( GetView() ) );
270 dc.SetFont( m_renderFont.IsOk() ? m_renderFont : GetView()->GetFont() );
271 layoutRuns( dc, runs, aCell.x, aCell.y, aCell.height );
272
273 for( const RUN& run : runs )
274 {
275 if( !run.href.IsEmpty() && run.bounds.Contains( aPoint ) )
276 {
277 if( aHref )
278 *aHref = run.href;
279
280 return true;
281 }
282 }
283
284 return false;
285}
VECTOR2I end
wxString result
Test unit parsing edge cases and error handling.