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