1 /********************************************************************************
2 * *
3 * M e n u C o m m a n d W i d g e t *
4 * *
5 *********************************************************************************
6 * Copyright (C) 1997,2005 by Jeroen van der Zijp. All Rights Reserved. *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU Lesser General Public *
10 * License as published by the Free Software Foundation; either *
11 * version 2.1 of the License, or (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 GNU *
16 * Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public *
19 * License along with this library; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
21 *********************************************************************************
22 * $Id: FXMenuCommand.cpp,v 1.62 2005/01/16 16:06:07 fox Exp $ *
23 ********************************************************************************/
24 #include "xincs.h"
25 #include "fxver.h"
26 #include "fxdefs.h"
27 #include "fxkeys.h"
28 #include "FXHash.h"
29 #include "FXThread.h"
30 #include "FXStream.h"
31 #include "FXString.h"
32 #include "FXSize.h"
33 #include "FXPoint.h"
34 #include "FXRectangle.h"
35 #include "FXRegistry.h"
36 #include "FXAccelTable.h"
37 #include "FXApp.h"
38 #include "FXDCWindow.h"
39 #include "FXFont.h"
40 #include "FXIcon.h"
41 #include "FXMenuCommand.h"
42
43 /*
44 Notes:
45 - Help text from constructor is third part; second part should be
46 accelerator key combination.
47 - When menu label changes, hotkey might have to be adjusted.
48 - Fix it so menu stays up when after Alt-F, you press Alt-E.
49 - MenuItems should be derived from FXLabel.
50 - FXMenuCascade should send ID_POST/IDUNPOST to self.
51 - Look into SEL_FOCUS_SELF some more...
52 - We handle left, middle, right mouse buttons exactly the same;
53 this permits popup menus posted by any mouse button.
54 - MenuCommand should flip state when invoked, and send new state along
55 in ptr in callback.
56 */
57
58
59 #define LEADSPACE 22
60 #define TRAILSPACE 16
61
62 using namespace FX;
63
64 /*******************************************************************************/
65
66 namespace FX {
67
68 // Map
69 FXDEFMAP(FXMenuCommand) FXMenuCommandMap[]={
70 FXMAPFUNC(SEL_PAINT,0,FXMenuCommand::onPaint),
71 FXMAPFUNC(SEL_ENTER,0,FXMenuCommand::onEnter),
72 FXMAPFUNC(SEL_LEAVE,0,FXMenuCommand::onLeave),
73 FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXMenuCommand::onButtonPress),
74 FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXMenuCommand::onButtonRelease),
75 FXMAPFUNC(SEL_MIDDLEBUTTONPRESS,0,FXMenuCommand::onButtonPress),
76 FXMAPFUNC(SEL_MIDDLEBUTTONRELEASE,0,FXMenuCommand::onButtonRelease),
77 FXMAPFUNC(SEL_RIGHTBUTTONPRESS,0,FXMenuCommand::onButtonPress),
78 FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,0,FXMenuCommand::onButtonRelease),
79 FXMAPFUNC(SEL_KEYPRESS,0,FXMenuCommand::onKeyPress),
80 FXMAPFUNC(SEL_KEYRELEASE,0,FXMenuCommand::onKeyRelease),
81 FXMAPFUNC(SEL_KEYPRESS,FXWindow::ID_HOTKEY,FXMenuCommand::onHotKeyPress),
82 FXMAPFUNC(SEL_KEYRELEASE,FXWindow::ID_HOTKEY,FXMenuCommand::onHotKeyRelease),
83 FXMAPFUNC(SEL_COMMAND,FXWindow::ID_ACCEL,FXMenuCommand::onCmdAccel),
84 };
85
86
87 // Object implementation
FXIMPLEMENT(FXMenuCommand,FXMenuCaption,FXMenuCommandMap,ARRAYNUMBER (FXMenuCommandMap))88 FXIMPLEMENT(FXMenuCommand,FXMenuCaption,FXMenuCommandMap,ARRAYNUMBER(FXMenuCommandMap))
89
90
91 // Command menu item
92 FXMenuCommand::FXMenuCommand(){
93 flags|=FLAG_ENABLED;
94 acckey=0;
95 }
96
97
98 // Command menu item
FXMenuCommand(FXComposite * p,const FXString & text,FXIcon * ic,FXObject * tgt,FXSelector sel,FXuint opts)99 FXMenuCommand::FXMenuCommand(FXComposite* p,const FXString& text,FXIcon* ic,FXObject* tgt,FXSelector sel,FXuint opts):
100 FXMenuCaption(p,text,ic,opts){
101 FXAccelTable *table;
102 FXWindow *own;
103 flags|=FLAG_ENABLED;
104 defaultCursor=getApp()->getDefaultCursor(DEF_RARROW_CURSOR);
105 target=tgt;
106 message=sel;
107 accel=text.section('\t',1);
108 acckey=fxparseAccel(accel);
109 if(acckey){
110 own=getShell()->getOwner();
111 if(own){
112 table=own->getAccelTable();
113 if(table){
114 table->addAccel(acckey,this,FXSEL(SEL_COMMAND,ID_ACCEL));
115 }
116 }
117 }
118 }
119
120
121 // Get default width
getDefaultWidth()122 FXint FXMenuCommand::getDefaultWidth(){
123 FXint tw,aw,iw;
124 tw=aw=iw=0;
125 if(!label.empty()) tw=font->getTextWidth(label.text(),label.length());
126 if(!accel.empty()) aw=font->getTextWidth(accel.text(),accel.length());
127 if(aw && tw) aw+=5;
128 if(icon) iw=icon->getWidth()+5;
129 return FXMAX(iw,LEADSPACE)+tw+aw+TRAILSPACE;
130 }
131
132
133 // Get default height
getDefaultHeight()134 FXint FXMenuCommand::getDefaultHeight(){
135 FXint th,ih;
136 th=ih=0;
137 if(!label.empty() || !accel.empty()) th=font->getFontHeight()+5;
138 if(icon) ih=icon->getHeight()+5;
139 return FXMAX(th,ih);
140 }
141
142
143 // If window can have focus
canFocus() const144 FXbool FXMenuCommand::canFocus() const {
145 return 1;
146 }
147
148
149 // Enter
onEnter(FXObject * sender,FXSelector sel,void * ptr)150 long FXMenuCommand::onEnter(FXObject* sender,FXSelector sel,void* ptr){
151 FXMenuCaption::onEnter(sender,sel,ptr);
152 if(isEnabled() && canFocus()) setFocus();
153 return 1;
154 }
155
156
157 // Leave
onLeave(FXObject * sender,FXSelector sel,void * ptr)158 long FXMenuCommand::onLeave(FXObject* sender,FXSelector sel,void* ptr){
159 FXMenuCaption::onLeave(sender,sel,ptr);
160 if(isEnabled() && canFocus()) killFocus();
161 return 1;
162 }
163
164
165 // Pressed button
onButtonPress(FXObject *,FXSelector,void *)166 long FXMenuCommand::onButtonPress(FXObject*,FXSelector,void*){
167 if(!isEnabled()) return 0;
168 return 1;
169 }
170
171
172 // Released button
onButtonRelease(FXObject *,FXSelector,void *)173 long FXMenuCommand::onButtonRelease(FXObject*,FXSelector,void*){
174 FXbool active=isActive();
175 if(!isEnabled()) return 0;
176 getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
177 if(active && target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1); }
178 return 1;
179 }
180
181
182 // Keyboard press
onKeyPress(FXObject *,FXSelector,void * ptr)183 long FXMenuCommand::onKeyPress(FXObject*,FXSelector,void* ptr){
184 FXEvent* event=(FXEvent*)ptr;
185 if(!isEnabled()) return 0;
186 FXTRACE((200,"%s::onKeyPress %p keysym=0x%04x state=%04x\n",getClassName(),this,event->code,event->state));
187 switch(event->code){
188 case KEY_KP_Enter:
189 case KEY_Return:
190 case KEY_space:
191 case KEY_KP_Space:
192 return 1;
193 }
194 return 0;
195 }
196
197
198 // Keyboard release
onKeyRelease(FXObject *,FXSelector,void * ptr)199 long FXMenuCommand::onKeyRelease(FXObject*,FXSelector,void* ptr){
200 FXEvent* event=(FXEvent*)ptr;
201 if(!isEnabled()) return 0;
202 FXTRACE((200,"%s::onKeyRelease %p keysym=0x%04x state=%04x\n",getClassName(),this,event->code,event->state));
203 switch(event->code){
204 case KEY_KP_Enter:
205 case KEY_Return:
206 case KEY_space:
207 case KEY_KP_Space:
208 getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
209 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
210 return 1;
211 }
212 return 0;
213 }
214
215
216 // Hot key combination pressed
onHotKeyPress(FXObject *,FXSelector,void * ptr)217 long FXMenuCommand::onHotKeyPress(FXObject*,FXSelector,void* ptr){
218 FXTRACE((200,"%s::onHotKeyPress %p\n",getClassName(),this));
219 handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
220 return 1;
221 }
222
223
224 // Hot key combination released
onHotKeyRelease(FXObject *,FXSelector,void *)225 long FXMenuCommand::onHotKeyRelease(FXObject*,FXSelector,void*){
226 FXTRACE((200,"%s::onHotKeyRelease %p\n",getClassName(),this));
227 if(isEnabled()){
228 getParent()->handle(this,FXSEL(SEL_COMMAND,ID_UNPOST),NULL);
229 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
230 }
231 return 1;
232 }
233
234
235 // Accelerator activated
onCmdAccel(FXObject *,FXSelector,void *)236 long FXMenuCommand::onCmdAccel(FXObject*,FXSelector,void*){
237 if(isEnabled()){
238 if(target) target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXuval)1);
239 return 1;
240 }
241 return 0;
242 }
243
244
245 // Into focus chain
setFocus()246 void FXMenuCommand::setFocus(){
247 FXMenuCaption::setFocus();
248 flags|=FLAG_ACTIVE;
249 flags&=~FLAG_UPDATE;
250 update();
251 }
252
253
254 // Out of focus chain
killFocus()255 void FXMenuCommand::killFocus(){
256 FXMenuCaption::killFocus();
257 flags&=~FLAG_ACTIVE;
258 flags|=FLAG_UPDATE;
259 update();
260 }
261
262
263 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)264 long FXMenuCommand::onPaint(FXObject*,FXSelector,void* ptr){
265 FXEvent *ev=(FXEvent*)ptr;
266 FXDCWindow dc(this,ev);
267 FXint xx,yy;
268
269 xx=LEADSPACE;
270
271 // Grayed out
272 if(!isEnabled()){
273 dc.setForeground(backColor);
274 dc.fillRectangle(0,0,width,height);
275 if(icon){
276 dc.drawIconSunken(icon,3,(height-icon->getHeight())/2);
277 if(icon->getWidth()+5>xx) xx=icon->getWidth()+5;
278 }
279 if(!label.empty()){
280 yy=font->getFontAscent()+(height-font->getFontHeight())/2;
281 dc.setFont(font);
282 dc.setForeground(hiliteColor);
283 dc.drawText(xx+1,yy+1,label.text(),label.length());
284 if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel.text(),accel.length())+1,yy+1,accel.text(),accel.length());
285 if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff)+1,yy+2,font->getTextWidth(&label[hotoff],1),1);
286 dc.setForeground(shadowColor);
287 dc.drawText(xx,yy,label.text(),label.length());
288 if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel.text(),accel.length()),yy,accel.text(),accel.length());
289 if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],1),1);
290 }
291 }
292
293 // Active
294 else if(isActive()){
295 dc.setForeground(selbackColor);
296 dc.fillRectangle(0,0,width,height);
297 if(icon){
298 dc.drawIcon(icon,3,(height-icon->getHeight())/2);
299 if(icon->getWidth()+5>xx) xx=icon->getWidth()+5;
300 }
301 if(!label.empty()){
302 yy=font->getFontAscent()+(height-font->getFontHeight())/2;
303 dc.setFont(font);
304 dc.setForeground(isEnabled() ? seltextColor : shadowColor);
305 dc.drawText(xx,yy,label.text(),label.length());
306 if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel.text(),accel.length()),yy,accel.text(),accel.length());
307 if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],1),1);
308 }
309 }
310
311 // Normal
312 else{
313 dc.setForeground(backColor);
314 dc.fillRectangle(0,0,width,height);
315 if(icon){
316 dc.drawIcon(icon,3,(height-icon->getHeight())/2);
317 if(icon->getWidth()+5>xx) xx=icon->getWidth()+5;
318 }
319 if(!label.empty()){
320 yy=font->getFontAscent()+(height-font->getFontHeight())/2;
321 dc.setFont(font);
322 dc.setForeground(textColor);
323 dc.drawText(xx,yy,label.text(),label.length());
324 if(!accel.empty()) dc.drawText(width-TRAILSPACE-font->getTextWidth(accel.text(),accel.length()),yy,accel.text(),accel.length());
325 if(0<=hotoff) dc.fillRectangle(xx+font->getTextWidth(&label[0],hotoff),yy+1,font->getTextWidth(&label[hotoff],1),1);
326 }
327 }
328 return 1;
329 }
330
331
332 // Change accelerator text; note this just changes the text!
333 // This is because the accelerator's target may be different from
334 // the MenuCommands and we don't want to blow it away.
setAccelText(const FXString & text)335 void FXMenuCommand::setAccelText(const FXString& text){
336 if(accel!=text){
337 accel=text;
338 recalc();
339 update();
340 }
341 }
342
343
344 // Save object to stream
save(FXStream & store) const345 void FXMenuCommand::save(FXStream& store) const {
346 FXMenuCaption::save(store);
347 store << accel;
348 store << acckey;
349 }
350
351
352 // Load object from stream
load(FXStream & store)353 void FXMenuCommand::load(FXStream& store){
354 FXMenuCaption::load(store);
355 store >> accel;
356 store >> acckey;
357 }
358
359
360 // Need to uninstall accelerator
~FXMenuCommand()361 FXMenuCommand::~FXMenuCommand(){
362 FXAccelTable *table;
363 FXWindow *own;
364 if(acckey){
365 own=getShell()->getOwner();
366 if(own){
367 table=own->getAccelTable();
368 if(table){
369 table->removeAccel(acckey);
370 }
371 }
372 }
373 }
374
375 }
376