]> git.lyx.org Git - lyx.git/blob - src/frontends/xforms/XFormsToolbar.C
Simplify the mechanics of generating the 'inactive' pixmap.
[lyx.git] / src / frontends / xforms / XFormsToolbar.C
1 /**
2  * \file XFormsToolbar.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 //  Added pseudo-action handling, asierra 180296
13
14 #include <config.h>
15
16 #include "XFormsToolbar.h"
17
18 #include "Color.h"
19 #include "Tooltips.h"
20 #include "xforms_helpers.h"
21
22 #include "buffer.h"
23 #include "bufferparams.h"
24 #include "debug.h"
25 #include "funcrequest.h"
26 #include "FuncStatus.h"
27 #include "gettext.h"
28 #include "lyxfunc.h"
29
30 #include "support/lstrings.h"
31
32 #include "lyx_forms.h"
33 #include "lyx_xpm.h"
34 #include "combox.h"
35
36 #include <boost/bind.hpp>
37
38 #include <sstream>
39 #include <vector>
40
41 using lyx::frontend::Box;
42 using lyx::frontend::BoxList;
43
44 using lyx::support::compare_ascii_no_case;
45
46 using std::distance;
47 using std::endl;
48 using std::string;
49 using std::vector;
50
51
52 // some constants
53 const int standardspacing = 2; // the usual space between items
54 const int sepspace = 6; // extra space
55 const int buttonwidth = 30; // the standard button width
56 const int height = 30; // the height of all items in the toolbar
57
58 namespace {
59
60 XFormsView::Position getPosition(ToolbarBackend::Flags const & flags)
61 {
62         if (flags & ToolbarBackend::TOP)
63                 return XFormsView::Top;
64         if (flags & ToolbarBackend::BOTTOM)
65                 return XFormsView::Bottom;
66         if (flags & ToolbarBackend::LEFT)
67                 return XFormsView::Left;
68         if (flags & ToolbarBackend::RIGHT)
69                 return XFormsView::Right;
70         return XFormsView::Top;
71 }
72
73
74 LyXTextClass const & getTextClass(LyXView const & lv)
75 {
76         return lv.buffer()->params().getLyXTextClass();
77 }
78
79 } // namespace anon
80
81
82 XFormsToolbar::toolbarItem::toolbarItem()
83         : icon(0),
84           unused_pixmap(0),
85           active_pixmap(0),
86           inactive_pixmap(0),
87           mask(0)
88 {}
89
90
91 XFormsToolbar::toolbarItem::~toolbarItem()
92 {
93         kill_icon();
94 }
95
96
97 void XFormsToolbar::toolbarItem::kill_icon()
98 {
99         if (unused_pixmap)
100                 // XForms will take care of cleaning up the other pixmap
101                 XFreePixmap(fl_get_display(), unused_pixmap);
102
103         unused_pixmap = 0;
104         active_pixmap = 0;
105         inactive_pixmap = 0;
106         mask = 0;
107         icon = 0;
108 }
109
110
111 XFormsToolbar::toolbarItem &
112 XFormsToolbar::toolbarItem::operator=(toolbarItem const & ti)
113 {
114         if (this == &ti)
115                 return *this;
116
117         // If we already have an icon, release it.
118         // But we don't copy the icon from ti
119         kill_icon();
120
121         func = ti.func;
122
123         return *this;
124 }
125
126
127 void XFormsToolbar::toolbarItem::generateInactivePixmaps()
128 {
129         if (!icon || icon->objclass != FL_PIXMAPBUTTON)
130                 return;
131
132         // Store the existing (active) pixmap.
133         fl_get_pixmap_pixmap(icon, &active_pixmap, &mask);
134
135         if (active_pixmap == 0 || mask == 0)
136                 return;
137
138         // Ascertain the width and height of the pixmap.
139         Display * display = fl_get_display();
140         unsigned int width;
141         unsigned int height;
142         unsigned int uidummy;
143         int idummy;
144         Window win;
145
146         XGetGeometry(display, active_pixmap, &win, &idummy, &idummy,
147                      &width, &height, &uidummy, &uidummy);
148
149         // Produce a darker shade of the button background as the
150         // inactive color. Note the 'hsv.v - 0.2'.
151         unsigned int r, g, b;
152         fl_getmcolor(FL_PIXMAPBUTTON_COL1, &r, &g, &b);
153         HSVColor hsv(RGBColor(r, g, b));
154         hsv.v = std::max(0.0, hsv.v - 0.2);
155         string const inactive_color = X11hexname(RGBColor(hsv));
156
157         // Generate an XPM dataset for a uniformly-colored pixmap with
158         // the same dimensions as active_pixmap.
159
160         // The data set has the form:
161         // "<width> <height> <ncolors> <chars per pixel>",
162         // "o c <inactive_color>",
163         // "oooooooooooooooo", // <width> 'o' chars.
164         // repeated <height> times.
165
166         std::ostringstream line1_ss;
167         line1_ss << width << ' ' << height << " 1 1";
168         string const line1 = line1_ss.str();
169         string const line2 = "o c " + inactive_color;
170         string const data(width, 'o');
171         vector<char *> inactive_data(height + 2,
172                                      const_cast<char *>(data.c_str()));
173         inactive_data[0] = const_cast<char *>(line1.c_str());
174         inactive_data[1] = const_cast<char *>(line2.c_str());
175
176         char ** raw_inactive_data =
177                 const_cast<char **>(&*inactive_data.begin());
178
179         // Generate a pixmap of this data set.
180         // Together with 'mask' above, this is sufficient to display
181         // an inactive version of our active_pixmap.
182         Screen * screen = ScreenOfDisplay(display, fl_screen);
183
184         XpmCreatePixmapFromData(fl_get_display(),
185                                 XRootWindowOfScreen(screen),
186                                 raw_inactive_data,
187                                 &inactive_pixmap,
188                                 0,
189                                 0);
190 }
191
192
193 Toolbars::ToolbarPtr make_toolbar(ToolbarBackend::Toolbar const & tbb,
194                                   LyXView & owner)
195 {
196         return Toolbars::ToolbarPtr(new XFormsToolbar(tbb, owner));
197 }
198
199
200 XFormsToolbar::XFormsToolbar(ToolbarBackend::Toolbar const & tbb,
201                              LyXView & o)
202         : toolbar_(0),
203           toolbar_buttons_(0),
204           owner_(static_cast<XFormsView &>(o)),
205           tooltip_(new Tooltips)
206 {
207         position_ = getPosition(tbb.flags);
208         BoxList & boxlist = owner_.getBox(position_).children();
209         toolbar_ = &boxlist.push_back(Box(0,0));
210
211         // If the toolbar is horizontal, then it contains three
212         // vertically-aligned Boxes,the center one of which is to
213         // contain the buttons, aligned horizontally.
214         // The other two provide some visual padding.
215
216         // If it is vertical, then this is swapped around.
217
218         Box::Orientation const toolbar_orientation =
219                 (position_ == XFormsView::Left ||
220                  position_ == XFormsView::Right)
221                 ? Box::Vertical : Box::Horizontal;
222
223         Box::Orientation const padding_orientation =
224                 (toolbar_orientation == Box::Vertical)
225                 ? Box::Horizontal : Box::Vertical;
226
227         toolbar_->set(padding_orientation);
228
229         // A bit of a hack, but prevents 'M-x' causing the addition of
230         // visible borders.
231         int const padding =
232                 (tbb.name == "minibuffer") ?
233                 0 : 2 + abs(fl_get_border_width());
234
235         toolbar_->children().push_back(Box(padding, padding));
236
237         Box & toolbar_center = toolbar_->children().push_back(Box(0,0));
238         toolbar_center.set(toolbar_orientation);
239         toolbar_buttons_ = &toolbar_center.children();
240
241         toolbar_->children().push_back(Box(padding, padding));
242
243         using lyx::frontend::WidgetMap;
244         owner_.metricsUpdated.connect(boost::bind(&WidgetMap::updateMetrics,
245                                                   &widgets_));
246
247         // Populate the toolbar.
248         ToolbarBackend::item_iterator it = tbb.items.begin();
249         ToolbarBackend::item_iterator end = tbb.items.end();
250         for (; it != end; ++it)
251                 add(it->first, it->second);
252
253 }
254
255
256 XFormsToolbar::~XFormsToolbar()
257 {
258         fl_freeze_form(owner_.getForm());
259
260         // G++ vector does not have clear defined
261         //toollist.clear();
262         toollist_.erase(toollist_.begin(), toollist_.end());
263
264         fl_unfreeze_form(owner_.getForm());
265 }
266
267
268 namespace {
269
270 extern "C" {
271
272 void C_ToolbarCB(FL_OBJECT * ob, long ac)
273 {
274         if (!ob || !ob->u_vdata)
275                 return;
276
277         XFormsToolbar * ptr = static_cast<XFormsToolbar *>(ob->u_vdata);
278         XFormsView & owner = ptr->owner_;
279         owner.getLyXFunc().dispatch(ptr->funcs[ac], true);
280 }
281
282 } // extern "C"
283
284 } // namespace anon
285
286
287 void XFormsToolbar::hide(bool update_metrics)
288 {
289         toolbar_->set(Box::Invisible);
290         if (update_metrics)
291                 owner_.updateMetrics();
292 }
293
294
295 void XFormsToolbar::show(bool update_metrics)
296 {
297         toolbar_->set(Box::Visible);
298         toolbar_->show();
299         if (update_metrics)
300                 owner_.updateMetrics();
301 }
302
303
304 void XFormsToolbar::add(FuncRequest const & func, string const & tooltip)
305 {
306         toolbarItem item;
307         item.func = func;
308
309         switch (func.action) {
310         case ToolbarBackend::SEPARATOR:
311                 toolbar_buttons_->push_back(Box(sepspace, sepspace));
312                 break;
313
314         case ToolbarBackend::MINIBUFFER:
315                 // Not implemented.
316                 // XForms uses the same widget to display the buffer messages
317                 // and to input commands.
318                 break;
319
320         case ToolbarBackend::LAYOUTS:
321                 layout_.reset(new XLayoutBox(owner_, *this));
322                 break;
323
324         default: {
325                 FL_OBJECT * obj;
326
327                 toolbar_buttons_->push_back(Box(standardspacing,
328                                                 standardspacing));
329
330                 item.icon = obj =
331                         fl_add_pixmapbutton(FL_NORMAL_BUTTON,
332                                             0, 0, 0, 0, "");
333
334                 widgets_.add(obj, *toolbar_buttons_, buttonwidth, height);
335
336                 fl_set_object_resize(obj, FL_RESIZE_ALL);
337
338                 int gravity = 0;
339                 if (position_ == XFormsView::Top ||
340                     position_ == XFormsView::Left)
341                         gravity = NorthWestGravity;
342                 else if (position_ == XFormsView::Right)
343                         gravity = NorthEastGravity;
344                 else if (position_ == XFormsView::Bottom)
345                         gravity = SouthWestGravity;
346
347                 fl_set_object_gravity(obj, gravity, gravity);
348
349                 Funcs::iterator fit = funcs.insert(funcs.end(), func);
350                 int const index = distance(funcs.begin(), fit);
351                 fl_set_object_callback(obj, C_ToolbarCB, index);
352                 // Remove the blue feedback rectangle
353                 fl_set_pixmapbutton_focus_outline(obj, 0);
354
355                 tooltip_->init(obj, tooltip);
356
357                 // The view that this object belongs to.
358                 obj->u_vdata = this;
359
360                 string const xpm = toolbarbackend.getIcon(func);
361                 fl_set_pixmapbutton_file(obj, xpm.c_str());
362                 break;
363         }
364         }
365
366         toollist_.push_back(item);
367 }
368
369
370 void XFormsToolbar::update()
371 {
372         ToolbarList::iterator p = toollist_.begin();
373         ToolbarList::iterator end = toollist_.end();
374         for (; p != end; ++p) {
375                 if (!p->icon)
376                         continue;
377
378                 FuncStatus const status =
379                         owner_.getLyXFunc().getStatus(p->func);
380
381                 if (status.onoff(true)) {
382                         // I'd like to use a different color
383                         // here, but then the problem is to
384                         // know how to use transparency with
385                         // Xpm library. It seems pretty
386                         // complicated to me (JMarc)
387                         fl_set_object_color(p->icon, FL_LEFT_BCOL, FL_BLUE);
388                         fl_set_object_boxtype(p->icon, FL_DOWN_BOX);
389                 } else {
390                         fl_set_object_color(p->icon, FL_MCOL, FL_BLUE);
391                         fl_set_object_boxtype(p->icon, FL_UP_BOX);
392                 }
393
394                 // This must go here rather than in XFormsToolbar::add, else
395                 // LyX aborts with a BadPixmap error.
396                 if (!p->active_pixmap)
397                         p->generateInactivePixmaps();
398
399                 if (status.enabled()) {
400                         fl_activate_object(p->icon);
401                         fl_set_pixmap_pixmap(p->icon,
402                                              p->active_pixmap,
403                                              p->mask);
404                         p->unused_pixmap = p->inactive_pixmap;
405                 } else {
406                         fl_deactivate_object(p->icon);
407                         fl_set_pixmap_pixmap(p->icon,
408                                              p->inactive_pixmap,
409                                              p->mask);
410                         p->unused_pixmap = p->active_pixmap;
411                 }
412         }
413
414         bool const enable = owner_.getLyXFunc().
415                 getStatus(FuncRequest(LFUN_LAYOUT)).enabled();
416
417         if (layout_.get())
418                 layout_->setEnabled(enable);
419 }
420
421
422 namespace {
423
424 extern "C"
425 void C_LayoutBoxSelectedCB(FL_OBJECT * ob, long)
426 {
427         if (!ob || !ob->u_vdata)
428                 return;
429         XLayoutBox * ptr = static_cast<XLayoutBox *>(ob->u_vdata);
430         ptr->selected();
431 }
432
433 } // namespace anon
434
435
436 XLayoutBox::XLayoutBox(LyXView & owner, XFormsToolbar & toolbar)
437         : owner_(owner)
438 {
439         toolbar.toolbar_buttons_->push_back(Box(standardspacing, 0));
440
441         combox_ = fl_add_combox(FL_DROPLIST_COMBOX,
442                                 0, 0, 135, height, "");
443
444         toolbar.widgets_.add(combox_, *toolbar.toolbar_buttons_, 135, height);
445
446         fl_set_combox_browser_height(combox_, 400);
447         fl_set_object_boxtype(combox_, FL_DOWN_BOX);
448         fl_set_object_color(combox_, FL_MCOL, FL_MCOL);
449         fl_set_object_gravity(combox_, FL_NorthWest, FL_NorthWest);
450         fl_set_object_resize(combox_, FL_RESIZE_ALL);
451
452         combox_->u_vdata = this;
453         fl_set_object_callback(combox_, C_LayoutBoxSelectedCB, 0);
454 }
455
456
457 void XLayoutBox::set(string const & layout)
458 {
459         LyXTextClass const & tc = getTextClass(owner_);
460
461         string const layoutname = _(tc[layout]->name());
462
463         int const nnames = fl_get_combox_maxitems(combox_);
464         for (int i = 1; i <= nnames; ++i) {
465                 string const name = fl_get_combox_line(combox_, i);
466                 if (name == layoutname) {
467                         fl_set_combox(combox_, i);
468                         break;
469                 }
470         }
471 }
472
473
474 void XLayoutBox::update()
475 {
476         LyXTextClass const & tc = getTextClass(owner_);
477
478         fl_clear_combox(combox_);
479
480         LyXTextClass::const_iterator it = tc.begin();
481         LyXTextClass::const_iterator const end = tc.end();
482         for (; it != end; ++it) {
483                 // ignore obsolete entries
484                 if ((*it)->obsoleted_by().empty()) {
485                         string const & name = _((*it)->name());
486                         fl_addto_combox(combox_, name.c_str());
487                 }
488         }
489
490         // we need to do this.
491         fl_redraw_object(combox_);
492 }
493
494
495 void XLayoutBox::clear()
496 {
497         fl_clear_combox(combox_);
498         fl_redraw_object(combox_);
499 }
500
501
502 void XLayoutBox::open()
503 {
504         fl_show_combox_browser(combox_);
505 }
506
507
508 void XLayoutBox::setEnabled(bool enable)
509 {
510         ::setEnabled(combox_, enable);
511 }
512
513
514 void XLayoutBox::selected()
515 {
516         string const layoutguiname = getString(combox_);
517
518         LyXTextClass const & tc = getTextClass(owner_);
519
520         LyXTextClass::const_iterator it  = tc.begin();
521         LyXTextClass::const_iterator const end = tc.end();
522         for (; it != end; ++it) {
523                 string const & name = (*it)->name();
524                 if (_(name) == layoutguiname) {
525                         owner_.getLyXFunc()
526                                 .dispatch(FuncRequest(LFUN_LAYOUT, name),
527                                           true);
528                         return;
529                 }
530         }
531         lyxerr << "ERROR (XLayoutBox::selected): layout not found!" << endl;
532 }