1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015 CERN
5  * Copyright (C) 2015-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
7  *
8  * This program is free software: you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation, either version 2 of the License, or (at your
11  * option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <algorithm>
23 
24 #include "wx_html_report_panel.h"
25 
26 #include <wildcards_and_files_ext.h>
27 #include <gal/color4d.h>
28 #include <wx/clipbrd.h>
29 #include <string_utils.h>
30 #include <wx/ffile.h>
31 #include <wx/log.h>
32 #include <wx/filedlg.h>
33 #include <wx/msgdlg.h>
34 #include <wx/menu.h>
35 #include <wx/textctrl.h>
36 #include <kiplatform/ui.h>
37 
WX_HTML_REPORT_PANEL(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,long style)38 WX_HTML_REPORT_PANEL::WX_HTML_REPORT_PANEL( wxWindow* parent, wxWindowID id, const wxPoint& pos,
39                                             const wxSize& size, long style ) :
40         WX_HTML_REPORT_PANEL_BASE( parent, id, pos, size, style ),
41         m_reporter( this ),
42         m_severities( -1 ),
43         m_lazyUpdate( false )
44 {
45     syncCheckboxes();
46     m_htmlView->SetFont( KIUI::GetInfoFont( m_htmlView ) );
47     Flush();
48 
49     Connect( wxEVT_COMMAND_MENU_SELECTED,
50              wxMenuEventHandler( WX_HTML_REPORT_PANEL::onMenuEvent ), nullptr, this );
51 
52     m_htmlView->Bind( wxEVT_SYS_COLOUR_CHANGED,
53                       wxSysColourChangedEventHandler( WX_HTML_REPORT_PANEL::onThemeChanged ),
54                       this );
55 }
56 
57 
~WX_HTML_REPORT_PANEL()58 WX_HTML_REPORT_PANEL::~WX_HTML_REPORT_PANEL()
59 {
60 }
61 
62 
onThemeChanged(wxSysColourChangedEvent & aEvent)63 void WX_HTML_REPORT_PANEL::onThemeChanged( wxSysColourChangedEvent &aEvent )
64 {
65     Flush();
66 
67     aEvent.Skip();
68 }
69 
70 
MsgPanelSetMinSize(const wxSize & aMinSize)71 void WX_HTML_REPORT_PANEL::MsgPanelSetMinSize( const wxSize& aMinSize )
72 {
73     m_fgSizer->SetMinSize( aMinSize );
74     GetSizer()->SetSizeHints( this );
75 }
76 
77 
Reporter()78 REPORTER& WX_HTML_REPORT_PANEL::Reporter()
79 {
80     return m_reporter;
81 }
82 
83 
Report(const wxString & aText,SEVERITY aSeverity,REPORTER::LOCATION aLocation)84 void WX_HTML_REPORT_PANEL::Report( const wxString& aText, SEVERITY aSeverity,
85                                    REPORTER::LOCATION aLocation )
86 {
87     REPORT_LINE line;
88     line.message = aText;
89     line.severity = aSeverity;
90 
91     if( aLocation == REPORTER::LOC_HEAD )
92         m_reportHead.push_back( line );
93     else if( aLocation == REPORTER::LOC_TAIL )
94         m_reportTail.push_back( line );
95     else
96         m_report.push_back( line );
97 
98     if( !m_lazyUpdate )
99     {
100         m_htmlView->AppendToPage( generateHtml( line ) );
101         scrollToBottom();
102     }
103 }
104 
105 
SetLazyUpdate(bool aLazyUpdate)106 void WX_HTML_REPORT_PANEL::SetLazyUpdate( bool aLazyUpdate )
107 {
108     m_lazyUpdate = aLazyUpdate;
109 }
110 
111 
Flush(bool aSort)112 void WX_HTML_REPORT_PANEL::Flush( bool aSort )
113 {
114     wxString html;
115 
116     if( aSort )
117     {
118         std::sort( m_report.begin(), m_report.end(),
119                 []( const REPORT_LINE& a, const REPORT_LINE& b)
120                 {
121                     return a.severity < b.severity;
122                 });
123     }
124 
125     for( const auto& line : m_reportHead )
126         html += generateHtml( line );
127 
128     for( const auto& line : m_report )
129         html += generateHtml( line );
130 
131     for( const auto& line : m_reportTail )
132         html += generateHtml( line );
133 
134     m_htmlView->SetPage( html );
135     scrollToBottom();
136 }
137 
138 
scrollToBottom()139 void WX_HTML_REPORT_PANEL::scrollToBottom()
140 {
141     int x, y, xUnit, yUnit;
142 
143     m_htmlView->GetVirtualSize( &x, &y );
144     m_htmlView->GetScrollPixelsPerUnit( &xUnit, &yUnit );
145     m_htmlView->Scroll( 0, y / yUnit );
146 
147     updateBadges();
148 }
149 
150 
updateBadges()151 void WX_HTML_REPORT_PANEL::updateBadges()
152 {
153     int count = Count(RPT_SEVERITY_ERROR );
154     m_errorsBadge->UpdateNumber( count, RPT_SEVERITY_ERROR );
155 
156     count = Count(RPT_SEVERITY_WARNING );
157     m_warningsBadge->UpdateNumber( count, RPT_SEVERITY_WARNING );
158 }
159 
160 
Count(int severityMask)161 int WX_HTML_REPORT_PANEL::Count( int severityMask )
162 {
163     int count = 0;
164 
165     for( const auto& reportLineArray : { m_report, m_reportHead, m_reportTail } )
166     {
167         for( const REPORT_LINE& reportLine : reportLineArray )
168         {
169             if( severityMask & reportLine.severity )
170                 count++;
171         }
172     }
173 
174     return count;
175 }
176 
177 
generateHtml(const REPORT_LINE & aLine)178 wxString WX_HTML_REPORT_PANEL::generateHtml( const REPORT_LINE& aLine )
179 {
180     wxString retv;
181 
182     if( !( m_severities & aLine.severity ) )
183         return retv;
184 
185     if( KIPLATFORM::UI::IsDarkTheme() )
186     {
187         switch( aLine.severity )
188         {
189         case RPT_SEVERITY_ERROR:
190             retv = "<font color=#F04040 size=3>" + _( "Error:" ) + " </font>"
191                    "<font size=3>" + aLine.message + "</font><br>";
192             break;
193         case RPT_SEVERITY_WARNING:
194             retv = "<font size=3>" + _( "Warning:" ) + wxS( " " ) + aLine.message + "</font><br>";
195             break;
196         case RPT_SEVERITY_INFO:
197             retv = "<font color=#909090 size=3>" + aLine.message + "</font><br>";
198             break;
199         case RPT_SEVERITY_ACTION:
200             retv = "<font color=#60D060 size=3>" + aLine.message + "</font><br>";
201             break;
202         default:
203             retv = "<font size=3>" + aLine.message + "</font><br>";
204         }
205     }
206     else
207     {
208         switch( aLine.severity )
209         {
210         case RPT_SEVERITY_ERROR:
211             retv = "<font color=#D00000 size=3>" + _( "Error:" ) + " </font>"
212                    "<font size=3>" + aLine.message + "</font><br>";
213             break;
214         case RPT_SEVERITY_WARNING:
215             retv = "<font size=3>" + _( "Warning:" ) + wxS( " " ) + aLine.message + "</font><br>";
216             break;
217         case RPT_SEVERITY_INFO:
218             retv = "<font color=#808080 size=3>" + aLine.message + "</font><br>";
219             break;
220         case RPT_SEVERITY_ACTION:
221             retv = "<font color=#008000 size=3>" + aLine.message + "</font><br>";
222             break;
223         default:
224             retv = "<font size=3>" + aLine.message + "</font><br>";
225         }
226     }
227 
228     // wxHtmlWindow fails to do correct baseline alignment between Japanese/Chinese cells and
229     // Roman cells.  This keeps the line in a single cell.
230     retv.Replace( " ", "&nbsp;" );
231 
232     return retv;
233 }
234 
235 
generatePlainText(const REPORT_LINE & aLine)236 wxString WX_HTML_REPORT_PANEL::generatePlainText( const REPORT_LINE& aLine )
237 {
238     switch( aLine.severity )
239     {
240     case RPT_SEVERITY_ERROR:   return _( "Error:" ) + wxS( " " ) + aLine.message + wxT( "\n" );
241     case RPT_SEVERITY_WARNING: return _( "Warning:" ) + wxS( " " ) + aLine.message + wxT( "\n" );
242     case RPT_SEVERITY_INFO:    return _( "Info:" ) + wxS( " " ) + aLine.message + wxT( "\n" );
243     default:                   return aLine.message + wxT( "\n" );
244     }
245 }
246 
247 
onRightClick(wxMouseEvent & event)248 void WX_HTML_REPORT_PANEL::onRightClick( wxMouseEvent& event )
249 {
250     wxMenu popup;
251     popup.Append( wxID_COPY, "Copy" );
252     PopupMenu( &popup );
253 }
254 
255 
onMenuEvent(wxMenuEvent & event)256 void WX_HTML_REPORT_PANEL::onMenuEvent( wxMenuEvent& event )
257 {
258     if( event.GetId() == wxID_COPY )
259     {
260         wxLogNull doNotLog; // disable logging of failed clipboard actions
261 
262         if( wxTheClipboard->Open() )
263         {
264             bool primarySelection = wxTheClipboard->IsUsingPrimarySelection();
265             wxTheClipboard->UsePrimarySelection( false );   // required to use the main clipboard
266             wxTheClipboard->SetData( new wxTextDataObject( m_htmlView->SelectionToText() ) );
267             wxTheClipboard->Flush(); // Allow data to be available after closing KiCad
268             wxTheClipboard->Close();
269             wxTheClipboard->UsePrimarySelection( primarySelection );
270         }
271     }
272 }
273 
274 
275 // Don't globally define this; different facilities use different definitions of "ALL"
276 static int RPT_SEVERITY_ALL = RPT_SEVERITY_WARNING | RPT_SEVERITY_ERROR | RPT_SEVERITY_INFO |
277                               RPT_SEVERITY_ACTION;
278 
279 
onCheckBoxShowAll(wxCommandEvent & event)280 void WX_HTML_REPORT_PANEL::onCheckBoxShowAll( wxCommandEvent& event )
281 {
282     if( event.IsChecked() )
283         m_severities = RPT_SEVERITY_ALL;
284     else
285         m_severities = RPT_SEVERITY_ERROR;
286 
287     syncCheckboxes();
288     Flush( true );
289 }
290 
291 
syncCheckboxes()292 void WX_HTML_REPORT_PANEL::syncCheckboxes()
293 {
294     m_checkBoxShowAll->SetValue( m_severities == RPT_SEVERITY_ALL );
295     m_checkBoxShowWarnings->SetValue( m_severities & RPT_SEVERITY_WARNING );
296     m_checkBoxShowErrors->SetValue( m_severities & RPT_SEVERITY_ERROR );
297     m_checkBoxShowInfos->SetValue( m_severities & RPT_SEVERITY_INFO );
298     m_checkBoxShowActions->SetValue( m_severities & RPT_SEVERITY_ACTION );
299 }
300 
301 
onCheckBoxShowWarnings(wxCommandEvent & event)302 void WX_HTML_REPORT_PANEL::onCheckBoxShowWarnings( wxCommandEvent& event )
303 {
304     if( event.IsChecked() )
305         m_severities |= RPT_SEVERITY_WARNING;
306     else
307         m_severities &= ~RPT_SEVERITY_WARNING;
308 
309     syncCheckboxes();
310     Flush( true );
311 }
312 
313 
onCheckBoxShowErrors(wxCommandEvent & event)314 void WX_HTML_REPORT_PANEL::onCheckBoxShowErrors( wxCommandEvent& event )
315 {
316     if( event.IsChecked() )
317         m_severities |= RPT_SEVERITY_ERROR;
318     else
319         m_severities &= ~RPT_SEVERITY_ERROR;
320 
321     syncCheckboxes();
322     Flush( true );
323 }
324 
325 
onCheckBoxShowInfos(wxCommandEvent & event)326 void WX_HTML_REPORT_PANEL::onCheckBoxShowInfos( wxCommandEvent& event )
327 {
328     if( event.IsChecked() )
329         m_severities |= RPT_SEVERITY_INFO;
330     else
331         m_severities &= ~RPT_SEVERITY_INFO;
332 
333     syncCheckboxes();
334     Flush( true );
335 }
336 
337 
onCheckBoxShowActions(wxCommandEvent & event)338 void WX_HTML_REPORT_PANEL::onCheckBoxShowActions( wxCommandEvent& event )
339 {
340     if( event.IsChecked() )
341         m_severities |= RPT_SEVERITY_ACTION;
342     else
343         m_severities &= ~RPT_SEVERITY_ACTION;
344 
345     syncCheckboxes();
346     Flush( true );
347 }
348 
349 
onBtnSaveToFile(wxCommandEvent & event)350 void WX_HTML_REPORT_PANEL::onBtnSaveToFile( wxCommandEvent& event )
351 {
352     wxFileName fn;
353 
354     if( m_reportFileName.empty() )
355         fn = wxT( "./report.txt" );
356     else
357         fn = m_reportFileName;
358 
359     wxFileDialog dlg( this, _( "Save Report to File" ), fn.GetPath(), fn.GetFullName(),
360                       TextFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
361 
362     if( dlg.ShowModal() != wxID_OK )
363         return;
364 
365     fn = dlg.GetPath();
366 
367     if( fn.GetExt().IsEmpty() )
368         fn.SetExt( "txt" );
369 
370     wxFFile f( fn.GetFullPath(), "wb" );
371 
372     if( !f.IsOpened() )
373     {
374         wxString msg;
375 
376         msg.Printf( _( "Cannot write report to file '%s'." ),
377                     fn.GetFullPath().GetData() );
378         wxMessageBox( msg, _( "File save error" ), wxOK | wxICON_ERROR, this );
379         return;
380     }
381 
382     for( REPORT_LINES section : { m_reportHead, m_report, m_reportTail } )
383     {
384         for( const REPORT_LINE& l : section )
385         {
386             wxString s = generatePlainText( l );
387 
388             ConvertSmartQuotesAndDashes( &s );
389             f.Write( s );
390         }
391     }
392 
393     m_reportFileName = fn.GetFullPath();
394     f.Close();
395 }
396 
397 
Clear()398 void WX_HTML_REPORT_PANEL::Clear()
399 {
400     m_report.clear();
401     m_reportHead.clear();
402     m_reportTail.clear();
403 }
404 
405 
SetLabel(const wxString & aLabel)406 void WX_HTML_REPORT_PANEL::SetLabel( const wxString& aLabel )
407 {
408     m_box->GetStaticBox()->SetLabel( aLabel );
409 }
410 
411 
SetVisibleSeverities(int aSeverities)412 void WX_HTML_REPORT_PANEL::SetVisibleSeverities( int aSeverities )
413 {
414     if( aSeverities < 0 )
415         m_severities = RPT_SEVERITY_ALL;
416     else
417         m_severities = aSeverities;
418 
419     syncCheckboxes();
420 }
421 
422 
GetVisibleSeverities() const423 int WX_HTML_REPORT_PANEL::GetVisibleSeverities() const
424 {
425     return m_severities;
426 }
427 
428 
SetFileName(const wxString & aReportFileName)429 void WX_HTML_REPORT_PANEL::SetFileName( const wxString& aReportFileName )
430 {
431     m_reportFileName = aReportFileName;
432 }
433 
434 
GetFileName(void)435 wxString& WX_HTML_REPORT_PANEL::GetFileName( void )
436 {
437     return ( m_reportFileName );
438 }
439 
440 
SetShowSeverity(SEVERITY aSeverity,bool aValue)441 void WX_HTML_REPORT_PANEL::SetShowSeverity( SEVERITY aSeverity, bool aValue )
442 {
443     switch( aSeverity )
444     {
445     case RPT_SEVERITY_INFO:    m_checkBoxShowInfos->SetValue( aValue );    break;
446     case RPT_SEVERITY_ACTION:  m_checkBoxShowActions->SetValue( aValue );  break;
447     case RPT_SEVERITY_WARNING: m_checkBoxShowWarnings->SetValue( aValue ); break;
448     default:                   m_checkBoxShowErrors->SetValue( aValue );   break;
449     }
450 }
451