]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiToolbar.cpp
Search for toolbar images in the filesystem and afterwards in the resource.
[lyx.git] / src / frontends / qt4 / GuiToolbar.cpp
1 /**
2  * \file qt4/GuiToolbar.cpp
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 John Levon
8  * \author Jean-Marc Lasgouttes
9  * \author Angus Leeming
10  * \author Abdelrazak Younes
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "debug.h"
20 #include "FuncRequest.h"
21 #include "FuncStatus.h"
22 #include "gettext.h"
23 #include "IconPalette.h"
24 #include "Layout.h"
25 #include "LyXFunc.h"
26 #include "ToolbarBackend.h"
27
28 #include "GuiView.h"
29 #include "GuiCommandBuffer.h"
30 #include "GuiToolbar.h"
31 #include "LyXAction.h"
32 #include "Action.h"
33 #include "qt_helpers.h"
34 #include "InsertTableWidget.h"
35
36 #include "support/filetools.h"
37 #include "support/lstrings.h"
38 #include "support/lyxalgo.h" // sorted
39
40 #include <QComboBox>
41 #include <QToolBar>
42 #include <QToolButton>
43 #include <QAction>
44 #include <QPixmap>
45
46
47 static void initializeResources()
48 {
49         static bool initialized = false;
50         if (!initialized) {
51                 Q_INIT_RESOURCE(Resources); 
52                 initialized = true;
53         }
54 }
55
56
57 namespace lyx {
58 namespace frontend {
59
60 using std::string;
61 using std::endl;
62
63 using support::libFileSearch;
64 using support::subst;
65 using support::compare;
66
67
68 namespace {
69
70 struct PngMap {
71         char const * key;
72         char const * value;
73 };
74
75
76 bool operator<(PngMap const & lhs, PngMap const & rhs)
77 {
78                 return compare(lhs.key, rhs.key) < 0;
79 }
80
81
82 class CompareKey {
83 public:
84         CompareKey(string const & name) : name_(name) {}
85         bool operator()(PngMap const & other) const { return other.key == name_; }
86 private:
87         string const name_;
88 };
89
90
91 PngMap sorted_png_map[] = {
92         { "Bumpeq", "bumpeq2" },
93         { "Cap", "cap2" },
94         { "Cup", "cup2" },
95         { "Delta", "delta2" },
96         { "Downarrow", "downarrow2" },
97         { "Gamma", "gamma2" },
98         { "Lambda", "lambda2" },
99         { "Leftarrow", "leftarrow2" },
100         { "Leftrightarrow", "leftrightarrow2" },
101         { "Longleftarrow", "longleftarrow2" },
102         { "Longleftrightarrow", "longleftrightarrow2" },
103         { "Longrightarrow", "longrightarrow2" },
104         { "Omega", "omega2" },
105         { "Phi", "phi2" },
106         { "Pi", "pi2" },
107         { "Psi", "psi2" },
108         { "Rightarrow", "rightarrow2" },
109         { "Sigma", "sigma2" },
110         { "Subset", "subset2" },
111         { "Supset", "supset2" },
112         { "Theta", "theta2" },
113         { "Uparrow", "uparrow2" },
114         { "Updownarrow", "updownarrow2" },
115         { "Upsilon", "upsilon2" },
116         { "Vdash", "vdash3" },
117         { "Xi", "xi2" },
118         { "nLeftarrow", "nleftarrow2" },
119         { "nLeftrightarrow", "nleftrightarrow2" },
120         { "nRightarrow", "nrightarrow2" },
121         { "nVDash", "nvdash3" },
122         { "nvDash", "nvdash2" },
123         { "textrm \\AA", "textrm_AA"},
124         { "textrm \\O", "textrm_Oe"},
125         { "vDash", "vdash2" }
126 };
127
128 size_t const nr_sorted_png_map = sizeof(sorted_png_map) / sizeof(PngMap);
129
130
131 string const find_png(string const & name)
132 {
133         PngMap const * const begin = sorted_png_map;
134         PngMap const * const end = begin + nr_sorted_png_map;
135         BOOST_ASSERT(sorted(begin, end));
136
137         PngMap const * const it = std::find_if(begin, end, CompareKey(name));
138
139         string png_name;
140         if (it != end)
141                 png_name = it->value;
142         else {
143                 png_name = subst(name, "_", "underscore");
144                 png_name = subst(png_name, ' ', '_');
145
146                 // This way we can have "math-delim { }" on the toolbar.
147                 png_name = subst(png_name, "(", "lparen");
148                 png_name = subst(png_name, ")", "rparen");
149                 png_name = subst(png_name, "[", "lbracket");
150                 png_name = subst(png_name, "]", "rbracket");
151                 png_name = subst(png_name, "{", "lbrace");
152                 png_name = subst(png_name, "}", "rbrace");
153                 png_name = subst(png_name, "|", "bars");
154                 png_name = subst(png_name, ",", "thinspace");
155                 png_name = subst(png_name, ":", "mediumspace");
156                 png_name = subst(png_name, ";", "thickspace");
157                 png_name = subst(png_name, "!", "negthinspace");
158         }
159
160         LYXERR(Debug::GUI) << "find_png(" << name << ")\n"
161                            << "Looking for math PNG called \""
162                            << png_name << '"' << std::endl;
163         return png_name;
164 }
165
166 } // namespace anon
167
168
169 /// return a icon for the given action
170 static QIcon getIcon(FuncRequest const & f, bool unknown)
171 {
172         initializeResources();
173         QPixmap pm;
174         string name1;
175         string name2;
176         string path;
177         string fullname;
178
179         switch (f.action) {
180         case LFUN_MATH_INSERT:
181                 if (!f.argument().empty()) {
182                         path = "math/";
183                         name1 = find_png(to_utf8(f.argument()).substr(1));
184                 }
185                 break;
186         case LFUN_MATH_DELIM:
187         case LFUN_MATH_BIGDELIM:
188                 path = "math/";
189                 name1 = find_png(to_utf8(f.argument()));
190                 break;
191         default:
192                 name2 = lyxaction.getActionName(f.action);
193                 name1 = name2;
194
195                 if (!f.argument().empty())
196                         name1 = subst(name2 + ' ' + to_utf8(f.argument()), ' ', '_');
197         }
198
199         fullname = libFileSearch("images/" + path, name1, "png").absFilename();
200         if (pm.load(toqstr(fullname)))
201                 return pm;
202
203         fullname = libFileSearch("images/" + path, name2, "png").absFilename();
204         if (pm.load(toqstr(fullname)))
205                 return pm;
206
207         if (pm.load(":/images/" + toqstr(path + name1) + ".png"))
208                 return pm;
209
210         if (pm.load(":/images/" + toqstr(path + name2) + ".png"))
211                 return pm;
212
213         LYXERR(Debug::GUI) << "Cannot find icon for command \""
214                            << lyxaction.getActionName(f.action)
215                            << '(' << to_utf8(f.argument()) << ")\"" << endl;
216         if (unknown)
217                 pm.load(":/images/unknown.png");
218
219         return pm;
220 }
221
222
223 static TextClass const & textClass(LyXView const & lv)
224 {
225         return lv.buffer()->params().getTextClass();
226 }
227
228
229 /////////////////////////////////////////////////////////////////////
230 //
231 // GuiLayoutBox
232 //
233 /////////////////////////////////////////////////////////////////////
234
235 GuiLayoutBox::GuiLayoutBox(GuiViewBase & owner)
236         : owner_(owner)
237 {
238         setSizeAdjustPolicy(QComboBox::AdjustToContents);
239         setFocusPolicy(Qt::ClickFocus);
240         setMinimumWidth(sizeHint().width());
241         setMaxVisibleItems(100);
242
243         QObject::connect(this, SIGNAL(activated(QString)),
244                          this, SLOT(selected(QString)));
245 }
246
247
248 void GuiLayoutBox::set(docstring const & layout)
249 {
250         TextClass const & tc = textClass(owner_);
251
252         QString const & name = toqstr(translateIfPossible(tc[layout]->name()));
253
254         int i = 0;
255         for (; i < count(); ++i) {
256                 if (name == itemText(i))
257                         break;
258         }
259
260         if (i == count()) {
261                 lyxerr << "Trying to select non existent layout type "
262                         << fromqstr(name) << endl;
263                 return;
264         }
265
266         setCurrentIndex(i);
267 }
268
269
270 void GuiLayoutBox::updateContents()
271 {
272         TextClass const & tc = textClass(owner_);
273
274         setUpdatesEnabled(false);
275         clear();
276
277         TextClass::const_iterator it = tc.begin();
278         TextClass::const_iterator const end = tc.end();
279         for (; it != end; ++it) {
280                 // ignore obsolete entries
281                 addItem(toqstr(translateIfPossible((*it)->name())));
282         }
283
284         // needed to recalculate size hint
285         hide();
286         setMinimumWidth(sizeHint().width());
287         show();
288
289         setUpdatesEnabled(true);
290 }
291
292
293 void GuiLayoutBox::selected(const QString & str)
294 {
295         owner_.setFocus();
296         TextClass const & tc = owner_.buffer()->params().getTextClass();
297         docstring const name = qstring_to_ucs4(str);
298         TextClass::const_iterator it  = tc.begin();
299         TextClass::const_iterator const end = tc.end();
300         for (; it != end; ++it) {
301                 docstring const & itname = (*it)->name();
302                 if (translateIfPossible(itname) == name) {
303                         FuncRequest const func(LFUN_LAYOUT, itname,
304                                                FuncRequest::TOOLBAR);
305                         owner_.dispatch(func);
306                         return;
307                 }
308         }
309         lyxerr << "ERROR (layoutSelected): layout not found!" << endl;
310 }
311
312
313
314 /////////////////////////////////////////////////////////////////////
315 //
316 // GuiToolbar
317 //
318 /////////////////////////////////////////////////////////////////////
319
320
321 GuiToolbar::GuiToolbar(ToolbarInfo const & tbinfo, GuiViewBase & owner)
322         : QToolBar(qt_(tbinfo.gui_name), &owner), owner_(owner),
323           layout_(0), command_buffer_(0)
324 {
325         // give visual separation between adjacent toolbars
326         addSeparator();
327
328         // TODO: save toolbar position
329         setMovable(true);
330
331         ToolbarInfo::item_iterator it = tbinfo.items.begin();
332         ToolbarInfo::item_iterator end = tbinfo.items.end();
333         for (; it != end; ++it)
334                 add(*it);
335 }
336
337
338 Action * GuiToolbar::addItem(ToolbarItem const & item)
339 {
340         Action * act = new Action(owner_,
341                 getIcon(item.func_, false),
342           toqstr(item.label_), item.func_, toqstr(item.label_));
343         actions_.append(act);
344         return act;
345 }
346
347
348 void GuiToolbar::add(ToolbarItem const & item)
349 {
350         switch (item.type_) {
351         case ToolbarItem::SEPARATOR:
352                 addSeparator();
353                 break;
354         case ToolbarItem::LAYOUTS:
355                 layout_ = new GuiLayoutBox(owner_);
356                 addWidget(layout_);
357                 break;
358         case ToolbarItem::MINIBUFFER:
359                 command_buffer_ = new GuiCommandBuffer(&owner_);
360                 addWidget(command_buffer_);
361                 /// \todo find a Qt4 equivalent to setHorizontalStretchable(true);
362                 //setHorizontalStretchable(true);
363                 break;
364         case ToolbarItem::TABLEINSERT: {
365                 QToolButton * tb = new QToolButton;
366                 tb->setCheckable(true);
367                 tb->setIcon(getIcon(FuncRequest(LFUN_TABULAR_INSERT), true));
368                 tb->setToolTip(qt_(to_ascii(item.label_)));
369                 tb->setStatusTip(qt_(to_ascii(item.label_)));
370                 tb->setText(qt_(to_ascii(item.label_)));
371                 InsertTableWidget * iv = new InsertTableWidget(owner_, tb);
372                 connect(tb, SIGNAL(clicked(bool)), iv, SLOT(show(bool)));
373                 connect(iv, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool)));
374                 connect(this, SIGNAL(updated()), iv, SLOT(updateParent()));
375                 addWidget(tb);
376                 break;
377                 }
378         case ToolbarItem::ICONPALETTE: {
379                 QToolButton * tb = new QToolButton(this);
380                 tb->setToolTip(qt_(to_ascii(item.label_)));
381                 tb->setStatusTip(qt_(to_ascii(item.label_)));
382                 tb->setText(qt_(to_ascii(item.label_)));
383                 connect(this, SIGNAL(iconSizeChanged(QSize)),
384                         tb, SLOT(setIconSize(QSize)));
385                 IconPalette * panel = new IconPalette(tb);
386                 panel->setWindowTitle(qt_(to_ascii(item.label_)));
387                 connect(this, SIGNAL(updated()), panel, SLOT(updateParent()));
388                 ToolbarInfo const * tbinfo = toolbarbackend.getDefinedToolbarInfo(item.name_);
389                 if (!tbinfo) {
390                         lyxerr << "Unknown toolbar " << item.name_ << endl;
391                         break;
392                 }
393                 ToolbarInfo::item_iterator it = tbinfo->items.begin();
394                 ToolbarInfo::item_iterator const end = tbinfo->items.end();
395                 for (; it != end; ++it)
396                         if (!getStatus(it->func_).unknown()) {
397                                 panel->addButton(addItem(*it));
398                                 // use the icon of first action for the toolbar button
399                                 if (it == tbinfo->items.begin())
400                                         tb->setIcon(getIcon(it->func_, true));
401                         }
402                 tb->setCheckable(true);
403                 connect(tb, SIGNAL(clicked(bool)), panel, SLOT(setVisible(bool)));
404                 connect(panel, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool)));
405                 addWidget(tb);
406                 break;
407                 }
408         case ToolbarItem::POPUPMENU: {
409                 QToolButton * tb = new QToolButton;
410                 tb->setPopupMode(QToolButton::InstantPopup);
411                 tb->setToolTip(qt_(to_ascii(item.label_)));
412                 tb->setStatusTip(qt_(to_ascii(item.label_)));
413                 tb->setText(qt_(to_ascii(item.label_)));
414                 tb->setIcon(QPixmap(":images/math/" + toqstr(item.name_) + ".png"));
415                 connect(this, SIGNAL(iconSizeChanged(QSize)),
416                         tb, SLOT(setIconSize(QSize)));
417
418                 ButtonMenu * m = new ButtonMenu(qt_(to_ascii(item.label_)), tb);
419                 m->setWindowTitle(qt_(to_ascii(item.label_)));
420                 m->setTearOffEnabled(true);
421                 connect(this, SIGNAL(updated()), m, SLOT(updateParent()));
422                 ToolbarInfo const * tbinfo = toolbarbackend.getDefinedToolbarInfo(item.name_);
423                 if (!tbinfo) {
424                         lyxerr << "Unknown toolbar " << item.name_ << endl;
425                         break;
426                 }
427                 ToolbarInfo::item_iterator it = tbinfo->items.begin();
428                 ToolbarInfo::item_iterator const end = tbinfo->items.end();
429                 for (; it != end; ++it)
430                         if (!getStatus(it->func_).unknown())
431                                 m->add(addItem(*it));
432                 tb->setMenu(m);
433                 addWidget(tb);
434                 break;
435                 }
436         case ToolbarItem::COMMAND: {
437                 if (!getStatus(item.func_).unknown())
438                         addAction(addItem(item));
439                 break;
440                 }
441         default:
442                 break;
443         }
444 }
445
446
447 void GuiToolbar::saveInfo(ToolbarSection::ToolbarInfo & tbinfo)
448 {
449         // if tbinfo.state == auto *do not* set on/off
450         if (tbinfo.state != ToolbarSection::ToolbarInfo::AUTO) {
451                 if (GuiToolbar::isVisible())
452                         tbinfo.state = ToolbarSection::ToolbarInfo::ON;
453                 else
454                         tbinfo.state = ToolbarSection::ToolbarInfo::OFF;
455         }
456         //
457         // no need to save it here.
458         Qt::ToolBarArea loc = owner_.toolBarArea(this);
459
460         if (loc == Qt::TopToolBarArea)
461                 tbinfo.location = ToolbarSection::ToolbarInfo::TOP;
462         else if (loc == Qt::BottomToolBarArea)
463                 tbinfo.location = ToolbarSection::ToolbarInfo::BOTTOM;
464         else if (loc == Qt::RightToolBarArea)
465                 tbinfo.location = ToolbarSection::ToolbarInfo::RIGHT;
466         else if (loc == Qt::LeftToolBarArea)
467                 tbinfo.location = ToolbarSection::ToolbarInfo::LEFT;
468         else
469                 tbinfo.location = ToolbarSection::ToolbarInfo::NOTSET;
470
471         // save toolbar position. They are not used to restore toolbar position
472         // now because move(x,y) does not work for toolbar.
473         tbinfo.posx = pos().x();
474         tbinfo.posy = pos().y();
475 }
476
477
478 void GuiToolbar::updateContents()
479 {
480         // update visible toolbars only
481         if (!isVisible())
482                 return;
483         // This is a speed bottleneck because this is called on every keypress
484         // and update calls getStatus, which copies the cursor at least two times
485         for (int i = 0; i < actions_.size(); ++i)
486                 actions_[i]->update();
487
488         // emit signal
489         updated();
490 }
491
492
493 } // namespace frontend
494 } // namespace lyx
495
496 #include "GuiToolbar_moc.cpp"