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