1 /********************************************************************************
2 *                                                                               *
3 *                          M e n u C h e c k   W i d g e t                      *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2002,2021 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 #define TOPIC_KEYBOARD  1009
54 
55 #define LEADSPACE   22
56 #define TRAILSPACE  16
57 
58 using namespace FX;
59 
60 /*******************************************************************************/
61 
62 namespace FX {
63 
64 // Map
65 FXDEFMAP(FXMenuCheck) FXMenuCheckMap[]={
66   FXMAPFUNC(SEL_PAINT,0,FXMenuCheck::onPaint),
67   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXMenuCheck::onButtonPress),
68   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXMenuCheck::onButtonRelease),
69   FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,FXMenuCheck::onButtonPress),
70   FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE,0,FXMenuCheck::onButtonRelease),
71   FXMAPFUNC(SEL_RIGHTBUTTONPRESS,0,FXMenuCheck::onButtonPress),
72   FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,0,FXMenuCheck::onButtonRelease),
73   FXMAPFUNC(SEL_KEYPRESS,0,FXMenuCheck::onKeyPress),
74   FXMAPFUNC(SEL_KEYRELEASE,0,FXMenuCheck::onKeyRelease),
75   FXMAPFUNC(SEL_KEYPRESS,FXWindow::ID_HOTKEY,FXMenuCheck::onHotKeyPress),
76   FXMAPFUNC(SEL_KEYRELEASE,FXWindow::ID_HOTKEY,FXMenuCheck::onHotKeyRelease),
77   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_CHECK,FXMenuCheck::onCheck),
78   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_UNCHECK,FXMenuCheck::onUncheck),
79   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_UNKNOWN,FXMenuCheck::onUnknown),
80   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_SETVALUE,FXMenuCheck::onCmdSetValue),
81   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_SETINTVALUE,FXMenuCheck::onCmdSetIntValue),
82   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_GETINTVALUE,FXMenuCheck::onCmdGetIntValue),
83   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_ACCEL,FXMenuCheck::onCmdAccel),
84   };
85 
86 
87 // Object implementation
FXIMPLEMENT(FXMenuCheck,FXMenuCommand,FXMenuCheckMap,ARRAYNUMBER (FXMenuCheckMap))88 FXIMPLEMENT(FXMenuCheck,FXMenuCommand,FXMenuCheckMap,ARRAYNUMBER(FXMenuCheckMap))
89 
90 
91 // Command menu item
92 FXMenuCheck::FXMenuCheck(){
93   boxColor=0;
94   check=false;
95   }
96 
97 
98 // Command menu item
FXMenuCheck(FXComposite * p,const FXString & text,FXObject * tgt,FXSelector sel,FXuint opts)99 FXMenuCheck::FXMenuCheck(FXComposite* p,const FXString& text,FXObject* tgt,FXSelector sel,FXuint opts):FXMenuCommand(p,text,NULL,tgt,sel,opts){
100   boxColor=getApp()->getBackColor();
101   check=false;
102   }
103 
104 
105 // Get default width
getDefaultWidth()106 FXint FXMenuCheck::getDefaultWidth(){
107   FXint tw,aw;
108   tw=aw=0;
109   if(!label.empty()) tw=font->getTextWidth(label.text(),label.length());
110   if(!accel.empty()) aw=font->getTextWidth(accel.text(),accel.length());
111   if(aw && tw) aw+=5;
112   return LEADSPACE+tw+aw+TRAILSPACE;
113   }
114 
115 
116 // Get default height
getDefaultHeight()117 FXint FXMenuCheck::getDefaultHeight(){
118   FXint th=0;
119   if(!label.empty() || !accel.empty()) th=font->getFontHeight()+5;
120   return FXMAX(th,20);
121   }
122 
123 
124 // Check button
setCheck(FXuchar s)125 void FXMenuCheck::setCheck(FXuchar s){
126   if(check!=s){
127     check=s;
128     update();
129     }
130   }
131 
132 
133 // Change state to checked
onCheck(FXObject *,FXSelector,void *)134 long FXMenuCheck::onCheck(FXObject*,FXSelector,void*){
135   setCheck(true);
136   return 1;
137   }
138 
139 
140 // Change state to unchecked
onUncheck(FXObject *,FXSelector,void *)141 long FXMenuCheck::onUncheck(FXObject*,FXSelector,void*){
142   setCheck(false);
143   return 1;
144   }
145 
146 
147 // Change state to indeterminate
onUnknown(FXObject *,FXSelector,void *)148 long FXMenuCheck::onUnknown(FXObject*,FXSelector,void*){
149   setCheck(maybe);
150   return 1;
151   }
152 
153 
154 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)155 long FXMenuCheck::onCmdSetValue(FXObject*,FXSelector,void* ptr){
156   setCheck((FXuchar)(FXuval)ptr);
157   return 1;
158   }
159 
160 
161 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)162 long FXMenuCheck::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
163   setCheck((FXuchar)*((FXint*)ptr));
164   return 1;
165   }
166 
167 
168 // Obtain value from text field
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)169 long FXMenuCheck::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
170   *((FXint*)ptr)=getCheck();
171   return 1;
172   }
173 
174 
175 // Pressed button
onButtonPress(FXObject *,FXSelector,void *)176 long FXMenuCheck::onButtonPress(FXObject*,FXSelector,void*){
177   if(!isEnabled()) return 0;
178   return 1;
179   }
180 
181 
182 // Released button
onButtonRelease(FXObject *,FXSelector,void *)183 long FXMenuCheck::onButtonRelease(FXObject*,FXSelector,void*){
184   FXbool active=isActive();
185   if(!isEnabled()) return 0;
186   getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
187   if(active){
188     setCheck(!check);
189     if(target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check); }
190     }
191   return 1;
192   }
193 
194 
195 // Keyboard press
onKeyPress(FXObject *,FXSelector,void * ptr)196 long FXMenuCheck::onKeyPress(FXObject*,FXSelector,void* ptr){
197   FXEvent* event=(FXEvent*)ptr;
198   if(isEnabled() && !(flags&FLAG_PRESSED)){
199     FXTRACE((TOPIC_KEYBOARD,"%s::onKeyPress %p keysym=0x%04x state=%04x\n",getClassName(),this,event->code,event->state));
200     if(event->code==KEY_space || event->code==KEY_KP_Space || event->code==KEY_Return || event->code==KEY_KP_Enter){
201       flags|=FLAG_PRESSED;
202       return 1;
203       }
204     }
205   return 0;
206   }
207 
208 
209 // Keyboard release
onKeyRelease(FXObject *,FXSelector,void * ptr)210 long FXMenuCheck::onKeyRelease(FXObject*,FXSelector,void* ptr){
211   FXEvent* event=(FXEvent*)ptr;
212   if(isEnabled() && (flags&FLAG_PRESSED)){
213     FXTRACE((TOPIC_KEYBOARD,"%s::onKeyRelease %p keysym=0x%04x state=%04x\n",getClassName(),this,event->code,event->state));
214     if(event->code==KEY_space || event->code==KEY_KP_Space || event->code==KEY_Return || event->code==KEY_KP_Enter){
215       flags&=~FLAG_PRESSED;
216       setCheck(!check);
217       getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
218       if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);
219       return 1;
220       }
221     }
222   return 0;
223   }
224 
225 
226 // Hot key combination pressed
onHotKeyPress(FXObject *,FXSelector,void * ptr)227 long FXMenuCheck::onHotKeyPress(FXObject*,FXSelector,void* ptr){
228   FXTRACE((200,"%s::onHotKeyPress %p\n",getClassName(),this));
229   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
230   if(isEnabled() && !(flags&FLAG_PRESSED)){
231     flags|=FLAG_PRESSED;
232     }
233   return 1;
234   }
235 
236 
237 // Hot key combination released
onHotKeyRelease(FXObject *,FXSelector,void *)238 long FXMenuCheck::onHotKeyRelease(FXObject*,FXSelector,void*){
239   FXTRACE((200,"%s::onHotKeyRelease %p\n",getClassName(),this));
240   if(isEnabled() && (flags&FLAG_PRESSED)){
241     flags&=~FLAG_PRESSED;
242     setCheck(!check);
243     getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
244     if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);
245     }
246   return 1;
247   }
248 
249 
250 // Accelerator activated
onCmdAccel(FXObject *,FXSelector,void *)251 long FXMenuCheck::onCmdAccel(FXObject*,FXSelector,void*){
252   if(isEnabled()){
253     setCheck(!check);
254     if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)check);
255     return 1;
256     }
257   return 0;
258   }
259 
260 
261 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)262 long FXMenuCheck::onPaint(FXObject*,FXSelector,void* ptr){
263   FXEvent *ev=(FXEvent*)ptr;
264   FXDCWindow dc(this,ev);
265   FXint xx,yy;
266 
267   xx=LEADSPACE;
268 
269   // Grayed out
270   if(!isEnabled()){
271     dc.setForeground(backColor);
272     dc.fillRectangle(0,0,width,height);
273     if(!label.empty()){
274       yy=font->getFontAscent()+(height-font->getFontHeight())/2;
275       dc.setFont(font);
276       dc.setForeground(hiliteColor);
277       dc.drawText(xx+1,yy+1,label);
278       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel)+1,yy+1,accel);
279       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff)+1,yy+2,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
280       dc.setForeground(shadowColor);
281       dc.drawText(xx,yy,label);
282       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel),yy,accel);
283       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
284       }
285     }
286 
287   // Active
288   else if(isActive()){
289     dc.setForeground(selbackColor);
290     dc.fillRectangle(0,0,width,height);
291     if(!label.empty()){
292       yy=font->getFontAscent()+(height-font->getFontHeight())/2;
293       dc.setFont(font);
294       dc.setForeground(isEnabled() ? seltextColor : shadowColor);
295       dc.drawText(xx,yy,label);
296       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel),yy,accel);
297       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
298       }
299     }
300 
301   // Normal
302   else{
303     dc.setForeground(backColor);
304     dc.fillRectangle(0,0,width,height);
305     if(!label.empty()){
306       yy=font->getFontAscent()+(height-font->getFontHeight())/2;
307       dc.setFont(font);
308       dc.setForeground(textColor);
309       dc.drawText(xx,yy,label);
310       if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel),yy,accel);
311       if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],wclen(&label[hotoff])),1);
312       }
313     }
314 
315   // Draw the box
316   xx=5;
317   yy=(height-9)/2;
318   if(!isEnabled())
319     dc.setForeground(backColor);
320   else
321     dc.setForeground(boxColor);
322   dc.fillRectangle(xx+1,yy+1,8,8);
323   dc.setForeground(shadowColor);
324   dc.drawRectangle(xx,yy,9,9);
325 
326   // Draw the check
327   if(check!=false){
328     FXSegment seg[6];
329     seg[0].x1=2+xx; seg[0].y1=4+yy; seg[0].x2=4+xx; seg[0].y2=6+yy;
330     seg[1].x1=2+xx; seg[1].y1=5+yy; seg[1].x2=4+xx; seg[1].y2=7+yy;
331     seg[2].x1=2+xx; seg[2].y1=6+yy; seg[2].x2=4+xx; seg[2].y2=8+yy;
332     seg[3].x1=4+xx; seg[3].y1=6+yy; seg[3].x2=8+xx; seg[3].y2=2+yy;
333     seg[4].x1=4+xx; seg[4].y1=7+yy; seg[4].x2=8+xx; seg[4].y2=3+yy;
334     seg[5].x1=4+xx; seg[5].y1=8+yy; seg[5].x2=8+xx; seg[5].y2=4+yy;
335     if(isEnabled()){
336       if(check==maybe)
337         dc.setForeground(shadowColor);
338       else
339         dc.setForeground(textColor);
340       }
341     else{
342       dc.setForeground(shadowColor);
343       }
344     dc.drawLineSegments(seg,6);
345     }
346 
347   return 1;
348   }
349 
350 
351 // Set box color
setBoxColor(FXColor clr)352 void FXMenuCheck::setBoxColor(FXColor clr){
353   if(clr!=boxColor){
354     boxColor=clr;
355     update();
356     }
357   }
358 
359 
360 // Save object to stream
save(FXStream & store) const361 void FXMenuCheck::save(FXStream& store) const {
362   FXMenuCommand::save(store);
363   store << check;
364   store << boxColor;
365   }
366 
367 
368 // Load object from stream
load(FXStream & store)369 void FXMenuCheck::load(FXStream& store){
370   FXMenuCommand::load(store);
371   store >> check;
372   store >> boxColor;
373   }
374 
375 
376 }
377