1 /********************************************************************************
2 *                                                                               *
3 *                          M e n u C h e c k   W i d g e t                      *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2002,2020 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxkeys.h"
26 #include "FXArray.h"
27 #include "FXHash.h"
28 #include "FXMutex.h"
29 #include "FXStream.h"
30 #include "FXString.h"
31 #include "FXSize.h"
32 #include "FXPoint.h"
33 #include "FXRectangle.h"
34 #include "FXStringDictionary.h"
35 #include "FXSettings.h"
36 #include "FXRegistry.h"
37 #include "FXAccelTable.h"
38 #include "FXFont.h"
39 #include "FXEvent.h"
40 #include "FXWindow.h"
41 #include "FXDCWindow.h"
42 #include "FXApp.h"
43 #include "FXIcon.h"
44 #include "FXMenuCommand.h"
45 #include "FXMenuCheck.h"
46 
47 /*
48   Notes:
49   - FXMenuCheck should flip state when invoked, and send new state along
50     in ptr in callback.
51 */
52 
53 
54 #define LEADSPACE   22
55 #define TRAILSPACE  16
56 
57 using namespace FX;
58 
59 /*******************************************************************************/
60 
61 namespace FX {
62 
63 // Map
64 FXDEFMAP(FXMenuCheck) FXMenuCheckMap[]={
65   FXMAPFUNC(SEL_PAINT,0,FXMenuCheck::onPaint),
66   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXMenuCheck::onButtonPress),
67   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXMenuCheck::onButtonRelease),
68   FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,FXMenuCheck::onButtonPress),
69   FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE,0,FXMenuCheck::onButtonRelease),
70   FXMAPFUNC(SEL_RIGHTBUTTONPRESS,0,FXMenuCheck::onButtonPress),
71   FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,0,FXMenuCheck::onButtonRelease),
72   FXMAPFUNC(SEL_KEYPRESS,0,FXMenuCheck::onKeyPress),
73   FXMAPFUNC(SEL_KEYRELEASE,0,FXMenuCheck::onKeyRelease),
74   FXMAPFUNC(SEL_KEYPRESS,FXWindow::ID_HOTKEY,FXMenuCheck::onHotKeyPress),
75   FXMAPFUNC(SEL_KEYRELEASE,FXWindow::ID_HOTKEY,FXMenuCheck::onHotKeyRelease),
76   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_CHECK,FXMenuCheck::onCheck),
77   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_UNCHECK,FXMenuCheck::onUncheck),
78   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_UNKNOWN,FXMenuCheck::onUnknown),
79   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_SETVALUE,FXMenuCheck::onCmdSetValue),
80   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_SETINTVALUE,FXMenuCheck::onCmdSetIntValue),
81   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_GETINTVALUE,FXMenuCheck::onCmdGetIntValue),
82   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_ACCEL,FXMenuCheck::onCmdAccel),
83   };
84 
85 
86 // Object implementation
FXIMPLEMENT(FXMenuCheck,FXMenuCommand,FXMenuCheckMap,ARRAYNUMBER (FXMenuCheckMap))87 FXIMPLEMENT(FXMenuCheck,FXMenuCommand,FXMenuCheckMap,ARRAYNUMBER(FXMenuCheckMap))
88 
89 
90 // Command menu item
91 FXMenuCheck::FXMenuCheck(){
92   boxColor=0;
93   check=false;
94   }
95 
96 
97 // Command menu item
FXMenuCheck(FXComposite * p,const FXString & text,FXObject * tgt,FXSelector sel,FXuint opts)98 FXMenuCheck::FXMenuCheck(FXComposite* p,const FXString& text,FXObject* tgt,FXSelector sel,FXuint opts):FXMenuCommand(p,text,NULL,tgt,sel,opts){
99   boxColor=getApp()->getBackColor();
100   check=false;
101   }
102 
103 
104 // Get default width
getDefaultWidth()105 FXint FXMenuCheck::getDefaultWidth(){
106   FXint tw,aw;
107   tw=aw=0;
108   if(!label.empty()) tw=font->getTextWidth(label.text(),label.length());
109   if(!accel.empty()) aw=font->getTextWidth(accel.text(),accel.length());
110   if(aw && tw) aw+=5;
111   return LEADSPACE+tw+aw+TRAILSPACE;
112   }
113 
114 
115 // Get default height
getDefaultHeight()116 FXint FXMenuCheck::getDefaultHeight(){
117   FXint th=0;
118   if(!label.empty() || !accel.empty()) th=font->getFontHeight()+5;
119   return FXMAX(th,20);
120   }
121 
122 
123 // Check button
setCheck(FXuchar s)124 void FXMenuCheck::setCheck(FXuchar s){
125   if(check!=s){
126     check=s;
127     update();
128     }
129   }
130 
131 
132 // Change state to checked
onCheck(FXObject *,FXSelector,void *)133 long FXMenuCheck::onCheck(FXObject*,FXSelector,void*){
134   setCheck(true);
135   return 1;
136   }
137 
138 
139 // Change state to unchecked
onUncheck(FXObject *,FXSelector,void *)140 long FXMenuCheck::onUncheck(FXObject*,FXSelector,void*){
141   setCheck(false);
142   return 1;
143   }
144 
145 
146 // Change state to indeterminate
onUnknown(FXObject *,FXSelector,void *)147 long FXMenuCheck::onUnknown(FXObject*,FXSelector,void*){
148   setCheck(maybe);
149   return 1;
150   }
151 
152 
153 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)154 long FXMenuCheck::onCmdSetValue(FXObject*,FXSelector,void* ptr){
155   setCheck((FXuchar)(FXuval)ptr);
156   return 1;
157   }
158 
159 
160 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)161 long FXMenuCheck::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
162   setCheck((FXuchar)*((FXint*)ptr));
163   return 1;
164   }
165 
166 
167 // Obtain value from text field
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)168 long FXMenuCheck::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
169   *((FXint*)ptr)=getCheck();
170   return 1;
171   }
172 
173 
174 // Pressed button
onButtonPress(FXObject *,FXSelector,void *)175 long FXMenuCheck::onButtonPress(FXObject*,FXSelector,void*){
176   if(!isEnabled()) return 0;
177   return 1;
178   }
179 
180 
181 // Released button
onButtonRelease(FXObject *,FXSelector,void *)182 long FXMenuCheck::onButtonRelease(FXObject*,FXSelector,void*){
183   FXbool active=isActive();
184   if(!isEnabled()) return 0;
185   getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
186   if(active){
187     setCheck(!check);
188     if(target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check); }
189     }
190   return 1;
191   }
192 
193 
194 // Keyboard press
onKeyPress(FXObject *,FXSelector,void * ptr)195 long FXMenuCheck::onKeyPress(FXObject*,FXSelector,void* ptr){
196   FXEvent* event=(FXEvent*)ptr;
197   if(isEnabled() && !(flags&FLAG_PRESSED)){
198     FXTRACE((200,"%s::onKeyPress %p keysym=0x%04x state=%04x\n",getClassName(),this,event->code,event->state));
199     if(event->code==KEY_space || event->code==KEY_KP_Space || event->code==KEY_Return || event->code==KEY_KP_Enter){
200       flags|=FLAG_PRESSED;
201       return 1;
202       }
203     }
204   return 0;
205   }
206 
207 
208 // Keyboard release
onKeyRelease(FXObject *,FXSelector,void * ptr)209 long FXMenuCheck::onKeyRelease(FXObject*,FXSelector,void* ptr){
210   FXEvent* event=(FXEvent*)ptr;
211   if(isEnabled() && (flags&FLAG_PRESSED)){
212     FXTRACE((200,"%s::onKeyRelease %p keysym=0x%04x state=%04x\n",getClassName(),this,event->code,event->state));
213     if(event->code==KEY_space || event->code==KEY_KP_Space || event->code==KEY_Return || event->code==KEY_KP_Enter){
214       flags&=~FLAG_PRESSED;
215       setCheck(!check);
216       getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
217       if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);
218       return 1;
219       }
220     }
221   return 0;
222   }
223 
224 
225 // Hot key combination pressed
onHotKeyPress(FXObject *,FXSelector,void * ptr)226 long FXMenuCheck::onHotKeyPress(FXObject*,FXSelector,void* ptr){
227   FXTRACE((200,"%s::onHotKeyPress %p\n",getClassName(),this));
228   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
229   if(isEnabled() && !(flags&FLAG_PRESSED)){
230     flags|=FLAG_PRESSED;
231     }
232   return 1;
233   }
234 
235 
236 // Hot key combination released
onHotKeyRelease(FXObject *,FXSelector,void *)237 long FXMenuCheck::onHotKeyRelease(FXObject*,FXSelector,void*){
238   FXTRACE((200,"%s::onHotKeyRelease %p\n",getClassName(),this));
239   if(isEnabled() && (flags&FLAG_PRESSED)){
240     flags&=~FLAG_PRESSED;
241     setCheck(!check);
242     getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
243     if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);
244     }
245   return 1;
246   }
247 
248 
249 // Accelerator activated
onCmdAccel(FXObject *,FXSelector,void *)250 long FXMenuCheck::onCmdAccel(FXObject*,FXSelector,void*){
251   if(isEnabled()){
252     setCheck(!check);
253     if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);
254     return 1;
255     }
256   return 0;
257   }
258 
259 
260 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)261 long FXMenuCheck::onPaint(FXObject*,FXSelector,void* ptr){
262   FXEvent *ev=(FXEvent*)ptr;
263   FXDCWindow dc(this,ev);
264   FXint xx,yy;
265 
266   xx=LEADSPACE;
267 
268   // Grayed out
269   if(!isEnabled()){
270     dc.setForeground(backColor);
271     dc.fillRectangle(0,0,width,height);
272     if(!label.empty()){
273       yy=font->getFontAscent()+(height-font->getFontHeight())/2;
274       dc.setFont(font);
275       dc.setForeground(hiliteColor);
276       dc.drawText(xx+1,yy+1,label);
277       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel)+1,yy+1,accel);
278       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff)+1,yy+2,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
279       dc.setForeground(shadowColor);
280       dc.drawText(xx,yy,label);
281       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel),yy,accel);
282       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
283       }
284     }
285 
286   // Active
287   else if(isActive()){
288     dc.setForeground(selbackColor);
289     dc.fillRectangle(0,0,width,height);
290     if(!label.empty()){
291       yy=font->getFontAscent()+(height-font->getFontHeight())/2;
292       dc.setFont(font);
293       dc.setForeground(isEnabled() ? seltextColor : shadowColor);
294       dc.drawText(xx,yy,label);
295       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel),yy,accel);
296       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
297       }
298     }
299 
300   // Normal
301   else{
302     dc.setForeground(backColor);
303     dc.fillRectangle(0,0,width,height);
304     if(!label.empty()){
305       yy=font->getFontAscent()+(height-font->getFontHeight())/2;
306       dc.setFont(font);
307       dc.setForeground(textColor);
308       dc.drawText(xx,yy,label);
309       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel),yy,accel);
310       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
311       }
312     }
313 
314   // Draw the box
315   xx=5;
316   yy=(height-9)/2;
317   if(!isEnabled())
318     dc.setForeground(backColor);
319   else
320     dc.setForeground(boxColor);
321   dc.fillRectangle(xx+1,yy+1,8,8);
322   dc.setForeground(shadowColor);
323   dc.drawRectangle(xx,yy,9,9);
324 
325   // Draw the check
326   if(check!=false){
327     FXSegment seg[6];
328     seg[0].x1=2+xx; seg[0].y1=4+yy; seg[0].x2=4+xx; seg[0].y2=6+yy;
329     seg[1].x1=2+xx; seg[1].y1=5+yy; seg[1].x2=4+xx; seg[1].y2=7+yy;
330     seg[2].x1=2+xx; seg[2].y1=6+yy; seg[2].x2=4+xx; seg[2].y2=8+yy;
331     seg[3].x1=4+xx; seg[3].y1=6+yy; seg[3].x2=8+xx; seg[3].y2=2+yy;
332     seg[4].x1=4+xx; seg[4].y1=7+yy; seg[4].x2=8+xx; seg[4].y2=3+yy;
333     seg[5].x1=4+xx; seg[5].y1=8+yy; seg[5].x2=8+xx; seg[5].y2=4+yy;
334     if(isEnabled()){
335       if(check==maybe)
336         dc.setForeground(shadowColor);
337       else
338         dc.setForeground(textColor);
339       }
340     else{
341       dc.setForeground(shadowColor);
342       }
343     dc.drawLineSegments(seg,6);
344     }
345 
346   return 1;
347   }
348 
349 
350 // Set box color
setBoxColor(FXColor clr)351 void FXMenuCheck::setBoxColor(FXColor clr){
352   if(clr!=boxColor){
353     boxColor=clr;
354     update();
355     }
356   }
357 
358 
359 // Save object to stream
save(FXStream & store) const360 void FXMenuCheck::save(FXStream& store) const {
361   FXMenuCommand::save(store);
362   store << check;
363   store << boxColor;
364   }
365 
366 
367 // Load object from stream
load(FXStream & store)368 void FXMenuCheck::load(FXStream& store){
369   FXMenuCommand::load(store);
370   store >> check;
371   store >> boxColor;
372   }
373 
374 
375 }
376