1 // MenuItem.cc for FbTk - Fluxbox Toolkit
2 // Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a
5 // copy of this software and associated documentation files (the "Software"),
6 // to deal in the Software without restriction, including without limitation
7 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 // and/or sell copies of the Software, and to permit persons to whom the
9 // Software is furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 // DEALINGS IN THE SOFTWARE.
21 
22 #include "MenuItem.hh"
23 #include "Menu.hh"
24 #include "MenuTheme.hh"
25 #include "App.hh"
26 #include "Command.hh"
27 #include "Image.hh"
28 #include "GContext.hh"
29 #include "PixmapWithMask.hh"
30 #include "StringUtil.hh"
31 
32 #include <X11/keysym.h>
33 
34 
35 namespace FbTk {
36 
~MenuItem()37 MenuItem::~MenuItem() { }
38 
click(int button,int time,unsigned int mods)39 void MenuItem::click(int button, int time, unsigned int mods) {
40     if (m_command.get() != 0) {
41         if (m_menu && m_close_on_click && (mods & ControlMask) == 0)
42             m_menu->hide();
43         // we need a local variable, since the command may destroy this object
44         RefCount<Command<void> > tmp(m_command);
45         tmp->execute();
46     }
47 }
48 
drawLine(FbDrawable & draw,const FbTk::ThemeProxy<MenuTheme> & theme,size_t n_chars,int text_x,int text_y,unsigned int width,size_t skip_chars) const49 void MenuItem::drawLine(FbDrawable &draw,
50                         const FbTk::ThemeProxy<MenuTheme> &theme, size_t n_chars,
51                         int text_x, int text_y, unsigned int width,
52                         size_t skip_chars) const {
53 
54     // avoid drawing an ugly dot
55     if (n_chars == 0) {
56         return;
57     }
58 
59     const FbString& text = m_label.visual();
60     const size_t n = std::min(n_chars, text.size());
61     const FbTk::Font& font = theme->hiliteFont();
62     int font_height = static_cast<int>(font.height());
63     int height = static_cast<int>(theme->itemHeight());
64     int font_top = (height - font_height)/2;
65     int bevel_width = static_cast<int>(theme->bevelWidth());
66     int underline_height = font_top + font.ascent() + 2;
67     int bottom = height - bevel_width - 1;
68     int text_w = font.textWidth(label());
69     int text_pixels = font.textWidth(text.c_str()+skip_chars, n);
70     int skip_pixels = 0;
71     if (skip_chars > 0) {
72         skip_pixels = font.textWidth(text.c_str(), skip_chars);
73     }
74 
75     text_y += std::min(bottom, underline_height);
76 
77     // pay attention to the text justification
78     switch(theme->hiliteFontJustify()) {
79     case FbTk::LEFT:
80         text_x += bevel_width + height + 1;
81         break;
82     case FbTk::RIGHT:
83         text_x += width - (height + bevel_width + text_w);
84         break;
85     default: //center
86         text_x += ((width + 1 - text_w) / 2);
87         break;
88     }
89 
90     text_x += skip_pixels;
91 
92     draw.drawLine(theme->hiliteUnderlineGC().gc(),
93                   text_x, text_y, text_x + text_pixels, text_y);
94 
95 }
96 
draw(FbDrawable & draw,const FbTk::ThemeProxy<MenuTheme> & theme,bool highlight,bool draw_foreground,bool draw_background,int x,int y,unsigned int width,unsigned int height) const97 void MenuItem::draw(FbDrawable &draw,
98                     const FbTk::ThemeProxy<MenuTheme> &theme,
99                     bool highlight, bool draw_foreground, bool draw_background,
100                     int x, int y,
101                     unsigned int width, unsigned int height) const {
102 
103     // text and submenu icon are background
104     // selected pixmaps are foreground
105 
106     int h = static_cast<int>(height);
107     int bevel = theme->bevelWidth();
108     Display *disp = App::instance()->display();
109 
110     //
111     // Icon
112     //
113     if (draw_background) {
114         if (icon() != 0) {
115             // copy pixmap, so we don't resize the original
116             FbPixmap tmp_pixmap, tmp_mask;
117             tmp_pixmap.copy(icon()->pixmap());
118             tmp_mask.copy(icon()->mask());
119 
120             // scale pixmap to right size
121             if ((h - (2*bevel)) != static_cast<int>(tmp_pixmap.height())) {
122                 int scale_size = h - 2*bevel;
123                 if (scale_size > 0) {
124                     tmp_pixmap.scale(scale_size, scale_size);
125                     tmp_mask.scale(scale_size, scale_size);
126                 }
127             }
128 
129             if (tmp_pixmap.drawable() != 0) {
130                 GC gc = theme->frameTextGC().gc();
131                 int icon_x = x + bevel;
132                 int icon_y = y + bevel;
133                 // enable clip mask
134                 XSetClipMask(disp, gc, tmp_mask.drawable());
135                 XSetClipOrigin(disp, gc, icon_x, icon_y);
136 
137                 if (draw.depth() == tmp_pixmap.depth()) {
138                     draw.copyArea(tmp_pixmap.drawable(),
139                                   gc,
140                                   0, 0,
141                                   icon_x, icon_y,
142                                   tmp_pixmap.width(), tmp_pixmap.height());
143                 } else { // TODO: wrong in soon-to-be-common circumstances
144                     XGCValues backup;
145                     XGetGCValues(draw.display(), gc, GCForeground|GCBackground,
146                                  &backup);
147                     XSetForeground(draw.display(), gc,
148                                    Color("black", theme->screenNum()).pixel());
149                     XSetBackground(draw.display(), gc,
150                                    Color("white", theme->screenNum()).pixel());
151                     XCopyPlane(draw.display(), tmp_pixmap.drawable(),
152                                draw.drawable(), gc,
153                                0, 0, tmp_pixmap.width(), tmp_pixmap.height(),
154                                icon_x, icon_y, 1);
155                     XSetForeground(draw.display(), gc, backup.foreground);
156                     XSetBackground(draw.display(), gc, backup.background);
157                 }
158 
159                 // restore clip mask
160                 XSetClipMask(disp, gc, None);
161             }
162         }
163     }
164 
165     if (label().logical().empty())
166         return;
167 
168     // text is background
169     if (draw_background) {
170         const GContext &tgc =
171             (highlight ? theme->hiliteTextGC() :
172              (isEnabled() ? theme->frameTextGC() : theme->disableTextGC() ) );
173         const Font& font = (highlight ? theme->hiliteFont() : theme->frameFont());
174 
175         //
176         // Text
177         //
178         int text_y = y;
179         int text_x = x;
180         int text_w = font.textWidth(label());
181 
182         int height_offset = theme->itemHeight() - (font.height() + 2*bevel);
183         text_y = y + bevel + font.ascent() + height_offset/2;
184 
185         switch(highlight ? theme->hiliteFontJustify() : theme->frameFontJustify()) {
186         case FbTk::LEFT:
187             text_x = x + bevel + h + 1;
188             break;
189 
190         case FbTk::RIGHT:
191             text_x = x +  width - (h + bevel + text_w);
192             break;
193         default: //center
194             text_x = x + ((width + 1 - text_w) / 2);
195             break;
196         }
197 
198         font.drawText(draw, theme->screenNum(), tgc.gc(), label(), text_x, text_y);
199     }
200 
201     GC gc = (highlight) ? theme->hiliteTextGC().gc() :
202         theme->frameTextGC().gc();
203     int sel_x = x;
204     int sel_y = y;
205     int item_pm_height = static_cast<int>(theme->itemHeight());
206 
207     if (theme->bulletPos() == FbTk::RIGHT)
208         sel_x += static_cast<int>(width) - h - bevel;
209 
210 
211     // 'draw_foreground' was used to decide if to draw the toggleitem.
212     // somehow(tm) this lead to not rendering the toggleitem on bringing
213     // up the menu. it would be rendered once the user highlights the item,
214     // but not before. i removed the check against 'draw_foreground' and
215     // thus make it an unused variable.
216     if (isToggleItem()) {
217 
218         const PixmapWithMask *pm = 0;
219 
220         if (isSelected()) {
221             if (highlight && theme->highlightSelectedPixmap().pixmap().drawable() != 0)
222                 pm = &theme->highlightSelectedPixmap();
223             else
224                 pm = &theme->selectedPixmap();
225         } else {
226             if (highlight && theme->highlightUnselectedPixmap().pixmap().drawable() != 0)
227                 pm = &theme->highlightUnselectedPixmap();
228             else
229                 pm = &theme->unselectedPixmap();
230         }
231 
232         if (pm != 0 && pm->pixmap().drawable() != 0) {
233             int selw = static_cast<int>(pm->width());
234             int selh = static_cast<int>(pm->height());
235             int offset_x = 0;
236             int offset_y = 0;
237             if (selw < item_pm_height)
238                 offset_x += (item_pm_height - selw) / 2;
239             if (selh < item_pm_height)
240                 offset_y += (item_pm_height - selh) / 2;
241 
242             XSetClipMask(disp, gc, pm->mask().drawable());
243             XSetClipOrigin(disp, gc, sel_x+offset_x, sel_y+offset_y);
244             // copy bullet pixmap to drawable
245             draw.copyArea(pm->pixmap().drawable(),
246                           gc,
247                           0, 0,
248                           sel_x+offset_x, sel_y+offset_y,
249                           selw,
250                           selh);
251             // disable clip mask
252             XSetClipMask(disp, gc, None);
253         } else if (isSelected()) {
254             draw.fillRectangle(theme->hiliteGC().gc(),
255                                sel_x+item_pm_height/4, sel_y+item_pm_height/4, item_pm_height/2, item_pm_height/2);
256         }
257     }
258 
259     //
260     // Submenu (background)
261     //
262     if (draw_background && submenu()) {
263 
264         const PixmapWithMask *pm = 0;
265 
266         if (highlight && theme->highlightBulletPixmap().pixmap().drawable() != 0)
267             pm = &theme->highlightBulletPixmap();
268         else
269             pm = &theme->bulletPixmap();
270 
271         if (pm && pm->pixmap().drawable() != 0) {
272             int selw = static_cast<int>(pm->width());
273             int selh = static_cast<int>(pm->height());
274             int offset_x = 0;
275             int offset_y = 0;
276             if (selw < item_pm_height)
277                 offset_x += (item_pm_height - selw) / 2;
278             if (selh < item_pm_height)
279                 offset_y += (item_pm_height - selh) / 2;
280 
281             XSetClipMask(disp, gc, pm->mask().drawable());
282             XSetClipOrigin(disp, gc, sel_x+offset_x, sel_y+offset_y);
283             // copy bullet pixmap to drawable
284             draw.copyArea(pm->pixmap().drawable(),
285                           gc,
286                           0, 0,
287                           sel_x+offset_x, sel_y+offset_y,
288                           selw,
289                           selh);
290             // disable clip mask
291             XSetClipMask(disp, gc, None);
292 
293         } else {
294             int half_w = item_pm_height / 2;
295             int quarter_w = item_pm_height / 4;
296             switch (theme->bullet()) {
297             case MenuTheme::SQUARE:
298                 draw.drawRectangle(gc, sel_x+quarter_w, y+quarter_w, half_w, half_w);
299                 break;
300 
301             case MenuTheme::TRIANGLE:
302                     draw.drawTriangle(gc, ((theme->bulletPos() == FbTk::RIGHT)?
303                                            FbTk::FbDrawable::RIGHT:
304                                            FbTk::FbDrawable::LEFT),
305                                       sel_x, sel_y,
306                                       item_pm_height,
307                                       item_pm_height,
308                                       300); // 33% triangle
309                 break;
310 
311             case MenuTheme::DIAMOND:
312                 XPoint dia[4];
313 
314                 dia[0].x = sel_x + half_w - 3;
315                 dia[0].y = sel_y + half_w;
316                 dia[1].x = 3;
317                 dia[1].y = -3;
318                 dia[2].x = 3;
319                 dia[2].y = 3;
320                 dia[3].x = -3;
321                 dia[3].y = 3;
322 
323                 draw.fillPolygon(gc, dia, 4, Convex,
324                                  CoordModePrevious);
325                 break;
326             default:
327                 break;
328             }
329         }
330     }
331 
332 
333 }
334 
setIcon(const std::string & filename,int screen_num)335 void MenuItem::setIcon(const std::string &filename, int screen_num) {
336     if (filename.empty()) {
337         m_icon.reset(0);
338         return;
339     }
340 
341     if (m_icon.get() == 0)
342         m_icon.reset(new Icon);
343 
344     m_icon->filename = FbTk::StringUtil::expandFilename(filename);
345     m_icon->pixmap.reset(Image::load(m_icon->filename.c_str(),
346                          screen_num));
347 }
348 
height(const FbTk::ThemeProxy<MenuTheme> & theme) const349 unsigned int MenuItem::height(const FbTk::ThemeProxy<MenuTheme> &theme) const {
350     const unsigned int bevel = theme->bevelWidth();
351     return std::max(theme->itemHeight(),
352                     std::max(theme->frameFont().height() + 2*bevel,
353                              theme->hiliteFont().height() + 2*bevel));
354 }
355 
width(const FbTk::ThemeProxy<MenuTheme> & theme) const356 unsigned int MenuItem::width(const FbTk::ThemeProxy<MenuTheme> &theme) const {
357     // textwidth + bevel width on each side of the text
358     const unsigned int icon_width = height(theme);
359     const unsigned int normal = 2 * (theme->bevelWidth() + icon_width) +
360                                 std::max(theme->frameFont().textWidth(label()),
361                                          theme->hiliteFont().textWidth(label()));
362     return m_icon.get() == 0 ? normal : normal + icon_width;
363 }
364 
updateTheme(const FbTk::ThemeProxy<MenuTheme> & theme)365 void MenuItem::updateTheme(const FbTk::ThemeProxy<MenuTheme> &theme) {
366     if (m_icon.get() == 0)
367         return;
368 
369     m_icon->pixmap.reset(Image::load(m_icon->filename.c_str(), theme->screenNum()));
370 
371 
372 }
373 
showSubmenu()374 void MenuItem::showSubmenu() {
375     if (submenu() != 0)
376         submenu()->show();
377 }
378 
379 } // end namespace FbTk
380