1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017-2021 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 
24 #include <widgets/color_swatch.h>
25 #include <wx/dcmemory.h>
26 
27 #include <dialogs/dialog_color_picker.h>
28 #include <memory>
29 
30 wxDEFINE_EVENT( COLOR_SWATCH_CHANGED, wxCommandEvent );
31 
32 using KIGFX::COLOR4D;
33 
34 
35 // See selcolor.cpp:
36 extern COLOR4D DisplayColorFrame( wxWindow* aParent, COLOR4D aOldColor );
37 
38 
MakeBitmap(const COLOR4D & aColor,const COLOR4D & aBackground,const wxSize & aSize,const wxSize & aCheckerboardSize,const COLOR4D & aCheckerboardBackground)39 wxBitmap COLOR_SWATCH::MakeBitmap( const COLOR4D& aColor, const COLOR4D& aBackground,
40                                    const wxSize& aSize, const wxSize& aCheckerboardSize,
41                                    const COLOR4D& aCheckerboardBackground )
42 {
43     wxBitmap    bitmap( aSize );
44     wxBrush     brush;
45     wxPen       pen;
46     wxMemoryDC  iconDC;
47 
48     iconDC.SelectObject( bitmap );
49     brush.SetStyle( wxBRUSHSTYLE_SOLID );
50 
51     if( aColor == COLOR4D::UNSPECIFIED )
52     {
53         // Draw a checkerboard
54         COLOR4D white;
55         COLOR4D black;
56         bool    rowCycle;
57 
58         if( aCheckerboardBackground.GetBrightness() > 0.4 )
59         {
60             white = COLOR4D::WHITE;
61             black = white.Darkened( 0.15 );
62             rowCycle = true;
63         }
64         else
65         {
66             black = COLOR4D::BLACK;
67             white = black.Brightened( 0.15 );
68             rowCycle = false;
69         }
70 
71         for( int x = 0; x < aSize.x; x += aCheckerboardSize.x )
72         {
73             bool colCycle = rowCycle;
74 
75             for( int y = 0; y < aSize.y; y += aCheckerboardSize.y )
76             {
77                 COLOR4D color = colCycle ? black : white;
78                 brush.SetColour( color.ToColour() );
79                 pen.SetColour( color.ToColour() );
80 
81                 iconDC.SetBrush( brush );
82                 iconDC.SetPen( pen );
83                 iconDC.DrawRectangle( x, y, x + aCheckerboardSize.x, y + aCheckerboardSize.y );
84 
85                 colCycle = !colCycle;
86             }
87 
88             rowCycle = !rowCycle;
89         }
90     }
91     else
92     {
93         brush.SetColour( aBackground.WithAlpha(1.0).ToColour() );
94         pen.SetColour( aBackground.WithAlpha(1.0).ToColour() );
95 
96         iconDC.SetBrush( brush );
97         iconDC.SetPen( pen );
98         iconDC.DrawRectangle( 0, 0, aSize.x, aSize.y );
99 
100         brush.SetColour( aColor.ToColour() );
101         pen.SetColour( aColor.ToColour() );
102 
103         iconDC.SetBrush( brush );
104         iconDC.SetPen( pen );
105         iconDC.DrawRectangle( 0, 0, aSize.x, aSize.y );
106     }
107 
108     return bitmap;
109 }
110 
111 
COLOR_SWATCH(wxWindow * aParent,const COLOR4D & aColor,int aID,const COLOR4D & aBackground,const COLOR4D & aDefault,SWATCH_SIZE aSwatchSize)112 COLOR_SWATCH::COLOR_SWATCH( wxWindow* aParent, const COLOR4D& aColor, int aID,
113                             const COLOR4D& aBackground, const COLOR4D& aDefault,
114                             SWATCH_SIZE aSwatchSize ) :
115         wxPanel( aParent, aID ),
116         m_color( aColor ),
117         m_background( aBackground ),
118         m_default( aDefault ),
119         m_userColors( nullptr ),
120         m_readOnly( false ),
121         m_supportsOpacity( true )
122 {
123     wxASSERT_MSG( aSwatchSize != SWATCH_EXPAND, "SWATCH_EXPAND not supported in COLOR_SWATCH" );
124 
125     switch( aSwatchSize )
126     {
127     case SWATCH_MEDIUM: m_size = ConvertDialogToPixels( SWATCH_SIZE_MEDIUM_DU ); break;
128     case SWATCH_SMALL:  m_size = ConvertDialogToPixels( SWATCH_SIZE_SMALL_DU );  break;
129     case SWATCH_LARGE:  m_size = ConvertDialogToPixels( SWATCH_SIZE_LARGE_DU );  break;
130     case SWATCH_EXPAND: m_size = ConvertDialogToPixels( SWATCH_SIZE_LARGE_DU );  break;
131     }
132 
133     m_checkerboardSize = ConvertDialogToPixels( CHECKERBOARD_SIZE_DU );
134     m_checkerboardBg = aParent->GetBackgroundColour();
135 
136     auto sizer = new wxBoxSizer( wxHORIZONTAL );
137     SetSizer( sizer );
138 
139     wxBitmap bitmap = COLOR_SWATCH::MakeBitmap( aColor, aBackground, m_size,
140                                                 m_checkerboardSize, m_checkerboardBg );
141     m_swatch = new wxStaticBitmap( this, aID, bitmap );
142 
143     sizer->Add( m_swatch, 0, 0 );
144 
145     setupEvents();
146 }
147 
148 
COLOR_SWATCH(wxWindow * aParent,wxWindowID aID,const wxPoint & aPos,const wxSize & aSize,long aStyle)149 COLOR_SWATCH::COLOR_SWATCH( wxWindow* aParent, wxWindowID aID, const wxPoint& aPos,
150                             const wxSize& aSize, long aStyle ) :
151         wxPanel( aParent, aID, aPos, aSize, aStyle ),
152         m_userColors( nullptr ),
153         m_readOnly( false ),
154         m_supportsOpacity( true )
155 {
156     if( aSize == wxDefaultSize )
157         m_size = ConvertDialogToPixels( SWATCH_SIZE_MEDIUM_DU );
158     else
159         m_size = aSize;
160 
161     m_checkerboardSize = ConvertDialogToPixels( CHECKERBOARD_SIZE_DU );
162     m_checkerboardBg = aParent->GetBackgroundColour();
163 
164     SetSize( m_size );
165 
166     auto sizer = new wxBoxSizer( wxHORIZONTAL );
167     SetSizer( sizer );
168 
169     wxBitmap bitmap = COLOR_SWATCH::MakeBitmap( COLOR4D::UNSPECIFIED, COLOR4D::UNSPECIFIED,
170                                                 m_size, m_checkerboardSize, m_checkerboardBg );
171     m_swatch = new wxStaticBitmap( this, aID, bitmap );
172 
173     sizer->Add( m_swatch, 0, 0 );
174 
175     setupEvents();
176 }
177 
178 
setupEvents()179 void COLOR_SWATCH::setupEvents()
180 {
181     wxWindow* topLevelParent = GetParent();
182 
183     while( topLevelParent && !topLevelParent->IsTopLevel() )
184         topLevelParent = topLevelParent->GetParent();
185 
186     if( topLevelParent && dynamic_cast<DIALOG_SHIM*>( topLevelParent ) )
187     {
188         m_swatch->Bind( wxEVT_LEFT_DOWN,
189                         [this] ( wxMouseEvent& aEvt )
190                         {
191                             GetNewSwatchColor();
192                         } );
193     }
194     else
195     {
196         // forward click to any other listeners, since we don't want them
197         m_swatch->Bind( wxEVT_LEFT_DOWN, &COLOR_SWATCH::rePostEvent, this );
198 
199         // bind the events that trigger the dialog
200         m_swatch->Bind( wxEVT_LEFT_DCLICK,
201                         [this] ( wxMouseEvent& aEvt )
202                         {
203                             GetNewSwatchColor();
204                         } );
205     }
206 
207     m_swatch->Bind( wxEVT_MIDDLE_DOWN,
208                     [this] ( wxMouseEvent& aEvt )
209                     {
210                         GetNewSwatchColor();
211                     } );
212 
213     m_swatch->Bind( wxEVT_RIGHT_DOWN, &COLOR_SWATCH::rePostEvent, this );
214 }
215 
216 
rePostEvent(wxEvent & aEvent)217 void COLOR_SWATCH::rePostEvent( wxEvent& aEvent )
218 {
219     wxPostEvent( this, aEvent );
220 }
221 
222 
sendSwatchChangeEvent(COLOR_SWATCH & aSender)223 static void sendSwatchChangeEvent( COLOR_SWATCH& aSender )
224 {
225     wxCommandEvent changeEvt( COLOR_SWATCH_CHANGED );
226 
227     // use this class as the object (alternative might be to
228     // set a custom event class but that's more work)
229     changeEvt.SetEventObject( &aSender );
230 
231     wxPostEvent( &aSender, changeEvt );
232 }
233 
234 
SetSwatchColor(const COLOR4D & aColor,bool aSendEvent)235 void COLOR_SWATCH::SetSwatchColor( const COLOR4D& aColor, bool aSendEvent )
236 {
237     m_color = aColor;
238 
239     wxBitmap bm = MakeBitmap( m_color, m_background, m_size, m_checkerboardSize, m_checkerboardBg );
240     m_swatch->SetBitmap( bm );
241 
242     if( aSendEvent )
243         sendSwatchChangeEvent( *this );
244 }
245 
246 
SetDefaultColor(const COLOR4D & aColor)247 void COLOR_SWATCH::SetDefaultColor( const COLOR4D& aColor )
248 {
249     m_default = aColor;
250 }
251 
252 
SetSwatchBackground(const COLOR4D & aBackground)253 void COLOR_SWATCH::SetSwatchBackground( const COLOR4D& aBackground )
254 {
255     m_background = aBackground;
256     wxBitmap bm = MakeBitmap( m_color, m_background, m_size, m_checkerboardSize, m_checkerboardBg );
257     m_swatch->SetBitmap( bm );
258 }
259 
260 
GetSwatchColor() const261 COLOR4D COLOR_SWATCH::GetSwatchColor() const
262 {
263     return m_color;
264 }
265 
266 
GetNewSwatchColor()267 void COLOR_SWATCH::GetNewSwatchColor()
268 {
269     if( m_readOnly )
270     {
271         if( m_readOnlyCallback )
272             m_readOnlyCallback();
273 
274         return;
275     }
276 
277     DIALOG_COLOR_PICKER dialog( ::wxGetTopLevelParent( this ), m_color, m_supportsOpacity,
278                                 m_userColors, m_default );
279 
280     if( dialog.ShowModal() == wxID_OK )
281     {
282         COLOR4D newColor = dialog.GetColor();
283 
284         if( newColor != COLOR4D::UNSPECIFIED || m_default == COLOR4D::UNSPECIFIED )
285         {
286             m_color = newColor;
287 
288             wxBitmap bm = MakeBitmap( newColor, m_background, m_size, m_checkerboardSize,
289                                       m_checkerboardBg );
290             m_swatch->SetBitmap( bm );
291 
292             sendSwatchChangeEvent( *this );
293         }
294     }
295 }
296