]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiToolbar.cpp
QDialogButtonBox for the remaining dialogs.
[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 Stefan Schimanski
11  * \author Abdelrazak Younes
12  *
13  * Full author contact details are available in file CREDITS.
14  */
15
16 #include <config.h>
17
18 #include "GuiToolbar.h"
19
20 #include "Action.h"
21 #include "Buffer.h"
22 #include "BufferParams.h"
23 #include "BufferView.h"
24 #include "Cursor.h"
25 #include "FuncRequest.h"
26 #include "FuncStatus.h"
27 #include "GuiApplication.h"
28 #include "GuiCommandBuffer.h"
29 #include "GuiView.h"
30 #include "IconPalette.h"
31 #include "InsertTableWidget.h"
32 #include "KeyMap.h"
33 #include "LayoutBox.h"
34 #include "LyX.h"
35 #include "LyXRC.h"
36 #include "qt_helpers.h"
37 #include "Session.h"
38 #include "Text.h"
39 #include "TextClass.h"
40 #include "Toolbars.h"
41
42 #include "insets/InsetText.h"
43
44 #include "support/debug.h"
45 #include "support/gettext.h"
46 #include "support/lstrings.h"
47
48 #include <QSettings>
49 #include <QShowEvent>
50 #include <QString>
51 #include <QToolBar>
52 #include <QToolButton>
53
54 #include "support/lassert.h"
55
56 using namespace std;
57 using namespace lyx::support;
58
59 namespace lyx {
60 namespace frontend {
61
62 GuiToolbar::GuiToolbar(ToolbarInfo const & tbinfo, GuiView & owner)
63         : QToolBar(toqstr(tbinfo.gui_name), &owner), visibility_(0),
64           owner_(owner), command_buffer_(0), tbinfo_(tbinfo), filled_(false),
65           restored_(false)
66 {
67         setIconSize(owner.iconSize());
68         connect(&owner, SIGNAL(iconSizeChanged(QSize)), this,
69                 SLOT(setIconSize(QSize)));
70
71         // This is used by QMainWindow::restoreState for proper main window state
72         // restauration.
73         setObjectName(toqstr(tbinfo.name));
74         restoreSession();
75 }
76
77
78 void GuiToolbar::setVisible(bool visible)
79 {
80         // This is a hack to find out which toolbars have been restored by
81         // MainWindow::restoreState and which toolbars should be initialized
82         // by us (i.e., new toolbars)
83         restored_ = true;
84         QToolBar::setVisible(visible);
85 }
86
87
88 bool GuiToolbar::isRestored() const
89 {
90         return restored_;
91 }
92
93
94 void GuiToolbar::fill()
95 {
96         if (filled_)
97                 return;
98         ToolbarInfo::item_iterator it = tbinfo_.items.begin();
99         ToolbarInfo::item_iterator end = tbinfo_.items.end();
100         for (; it != end; ++it)
101                 add(*it);
102         filled_ = true;
103 }
104
105
106 void GuiToolbar::showEvent(QShowEvent * ev)
107 {
108         fill();
109         ev->accept();
110 }
111
112
113 void GuiToolbar::setVisibility(int visibility)
114 {
115         visibility_ = visibility;
116 }
117
118
119 Action * GuiToolbar::addItem(ToolbarItem const & item)
120 {
121         QString text = toqstr(item.label_);
122         // Get the keys bound to this action, but keep only the
123         // first one later
124         KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(*item.func_);
125         if (!bindings.empty())
126                 text += " [" + toqstr(bindings.begin()->print(KeySequence::ForGui)) + "]";
127
128         Action * act = new Action(item.func_, getIcon(*item.func_, false), text,
129                                   text, this);
130         actions_.append(act);
131         return act;
132 }
133
134 namespace {
135
136 class PaletteButton : public QToolButton
137 {
138 private:
139         GuiToolbar * bar_;
140         ToolbarItem const & tbitem_;
141         bool initialized_;
142 public:
143         PaletteButton(GuiToolbar * bar, ToolbarItem const & item)
144                 : QToolButton(bar), bar_(bar), tbitem_(item), initialized_(false)
145         {
146                 QString const label = qt_(to_ascii(tbitem_.label_));
147                 setToolTip(label);
148                 setStatusTip(label);
149                 setText(label);
150                 connect(bar_, SIGNAL(iconSizeChanged(QSize)),
151                         this, SLOT(setIconSize(QSize)));
152                 setCheckable(true);
153                 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name_);
154                 if (tbinfo)
155                         // use the icon of first action for the toolbar button
156                         setIcon(getIcon(*tbinfo->items.begin()->func_, true));
157         }
158
159         void mousePressEvent(QMouseEvent * e)
160         {
161                 if (initialized_) {
162                         QToolButton::mousePressEvent(e);
163                         return;
164                 }
165
166                 initialized_ = true;
167
168                 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name_);
169                 if (!tbinfo) {
170                         LYXERR0("Unknown toolbar " << tbitem_.name_);
171                         return;
172                 }
173                 IconPalette * panel = new IconPalette(this);
174                 QString const label = qt_(to_ascii(tbitem_.label_));
175                 panel->setWindowTitle(label);
176                 connect(this, SIGNAL(clicked(bool)), panel, SLOT(setVisible(bool)));
177                 connect(panel, SIGNAL(visible(bool)), this, SLOT(setChecked(bool)));
178                 ToolbarInfo::item_iterator it = tbinfo->items.begin();
179                 ToolbarInfo::item_iterator const end = tbinfo->items.end();
180                 for (; it != end; ++it)
181                         if (!getStatus(*it->func_).unknown())
182                                 panel->addButton(bar_->addItem(*it));
183
184                 QToolButton::mousePressEvent(e);
185         }
186 };
187
188 } // namespace
189
190
191 MenuButtonBase::MenuButtonBase(GuiToolbar * bar, ToolbarItem const & item)
192         : QToolButton(bar), bar_(bar), tbitem_(item)
193 {
194         setPopupMode(QToolButton::InstantPopup);
195         QString const label = qt_(to_ascii(tbitem_.label_));
196         setToolTip(label);
197         setStatusTip(label);
198         setText(label);
199         QString const name = toqstr(tbitem_.name_);
200         QStringList imagedirs;
201         imagedirs << "images/math/" << "images/";
202         for (int i = 0; i < imagedirs.size(); ++i) {
203                 QString imagedir = imagedirs.at(i);
204                 FileName const fname = imageLibFileSearch(imagedir, name, "svgz,png",
205                         theGuiApp()->imageSearchMode());
206                 if (fname.exists()) {
207                         setIcon(QIcon(getPixmap(imagedir, name, "svgz,png")));
208                         break;
209                 }
210         }
211 }
212
213
214 void MenuButtonBase::actionTriggered(QAction * action)
215 {
216         QToolButton::setDefaultAction(action);
217         setPopupMode(QToolButton::DelayedPopup);
218 }
219
220
221 StaticMenuButton::StaticMenuButton(
222     GuiToolbar * bar, ToolbarItem const & item, bool const sticky)
223         : MenuButtonBase(bar, item)
224 {
225         if (sticky)
226                 connect(this, SIGNAL(triggered(QAction *)),
227                         this, SLOT(actionTriggered(QAction *)));
228         connect(bar, SIGNAL(iconSizeChanged(QSize)),
229                 this, SLOT(setIconSize(QSize)));
230         initialize();
231 }
232
233
234 void StaticMenuButton::initialize()
235 {
236         QString const label = qt_(to_ascii(tbitem_.label_));
237         ButtonMenu * m = new ButtonMenu(label, this);
238         m->setWindowTitle(label);
239         m->setTearOffEnabled(true);
240         connect(bar_, SIGNAL(updated()), m, SLOT(updateParent()));
241         connect(bar_, SIGNAL(updated()), this, SLOT(updateTriggered()));
242         ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name_);
243         if (!tbinfo) {
244                 LYXERR0("Unknown toolbar " << tbitem_.name_);
245                 return;
246         }
247         ToolbarInfo::item_iterator it = tbinfo->items.begin();
248         ToolbarInfo::item_iterator const end = tbinfo->items.end();
249         for (; it != end; ++it)
250                 if (!getStatus(*it->func_).unknown())
251                         m->add(bar_->addItem(*it));
252         setMenu(m);
253 }
254
255
256 void StaticMenuButton::updateTriggered()
257 {
258         if (!menu())
259                 return;
260
261         bool enabled = false;
262         QList<QAction *> acts = menu()->actions();
263         for (int i = 0; i < acts.size(); ++i)
264                 if (acts[i]->isEnabled()) {
265                         enabled = true;
266                         break;
267                 }
268         // Enable the MenuButton if at least one menu item is enabled
269         setEnabled(enabled);
270         // If a disabled item is default, switch to InstantPopup
271         // (this can happen if a user selects e.g. DVI and then
272         // turns non-TeX fonts on)
273         if (defaultAction() && !defaultAction()->isEnabled())
274                 setPopupMode(QToolButton::InstantPopup);
275 }
276
277
278 class DynamicMenuButton::Private
279 {
280         /// noncopyable
281         Private(Private const &);
282         void operator=(Private const &);
283 public:
284         Private() : inset_(0) {}
285         ///
286         DocumentClassConstPtr text_class_;
287         ///
288         InsetText const * inset_;
289 };
290
291
292 DynamicMenuButton::DynamicMenuButton(GuiToolbar * bar, ToolbarItem const & item)
293         : MenuButtonBase(bar, item), d(new Private())
294 {
295         initialize();
296 }
297
298
299 DynamicMenuButton::~DynamicMenuButton() 
300
301         delete d;
302 }
303
304
305 void DynamicMenuButton::initialize()
306 {
307         QString const label = qt_(to_ascii(tbitem_.label_));
308         ButtonMenu * m = new ButtonMenu(label, this);
309         m->setWindowTitle(label);
310         m->setTearOffEnabled(true);
311         connect(bar_, SIGNAL(updated()), m, SLOT(updateParent()));
312         connect(bar_, SIGNAL(updated()), this, SLOT(updateTriggered()));
313         connect(bar_, SIGNAL(iconSizeChanged(QSize)),
314                 this, SLOT(setIconSize(QSize)));
315         setMenu(m);
316 }
317
318
319 bool DynamicMenuButton::isMenuType(string const & s)
320 {
321         return s == "dynamic-custom-insets" ||
322                s == "dynamic-char-styles";
323 }
324
325
326 void DynamicMenuButton::updateTriggered()
327 {
328         QMenu * m = menu();
329         // the menu should exist by this point
330         // if not, we can at least avoid crashing in release mode
331         LASSERT(m, return);
332         GuiView const & owner = bar_->owner();
333         BufferView const * bv = owner.currentBufferView();
334         if (!bv) {
335                 m->clear();
336                 setEnabled(false);
337                 setMinimumWidth(sizeHint().width());
338                 d->text_class_.reset();
339                 d->inset_ = 0;
340                 return;
341         }
342
343         DocumentClassConstPtr text_class = 
344                         bv->buffer().params().documentClassPtr();
345         InsetText const * inset = &(bv->cursor().innerText()->inset());
346         // if the text class has changed, then we need to reload the menu
347         if (d->text_class_ != text_class) {
348                 d->text_class_ = text_class;
349                 // at the moment, we can just call loadFlexInsets, and it will
350                 // handle both types. if there were more types of menus, then we 
351                 // might need to have other options.
352                 loadFlexInsets();
353         }
354         // remember where we are
355         d->inset_ = inset;
356         // note that enabling here might need to be more subtle if there
357         // were other kinds of menus.
358         setEnabled(!bv->buffer().isReadonly() &&
359                                                  !m->isEmpty() && 
360                                                  inset->insetAllowed(FLEX_CODE));
361 }
362
363
364 void DynamicMenuButton::loadFlexInsets()
365 {
366         QMenu * m = menu();
367         m->clear();
368         string const & menutype = tbitem_.name_;
369         InsetLayout::InsetLyXType ftype;
370         if (menutype == "dynamic-custom-insets")
371                 ftype = InsetLayout::CUSTOM;
372         else if (menutype == "dynamic-char-styles")
373                 ftype = InsetLayout::CHARSTYLE;
374         else {
375                 // this should have been taken care of earlier
376                 LASSERT(false, return);
377         }
378
379         TextClass::InsetLayouts const & inset_layouts = 
380                         d->text_class_->insetLayouts();
381         for (auto const & iit : inset_layouts) {
382                 InsetLayout const & il = iit.second;
383                 if (il.lyxtype() != ftype)
384                         continue;
385                 docstring const name = iit.first;
386                 QString const loc_item = toqstr(translateIfPossible(
387                                 prefixIs(name, from_ascii("Flex:")) ? 
388                                 name.substr(5) : name));
389                 FuncRequest func(LFUN_FLEX_INSERT, 
390                                 from_ascii("\"") + name + from_ascii("\""), FuncRequest::TOOLBAR);
391                 Action * act = 
392                                 new Action(func, getIcon(func, false), loc_item, loc_item, this);
393                 m->addAction(act);
394         }
395 }
396
397
398 void GuiToolbar::add(ToolbarItem const & item)
399 {
400         switch (item.type_) {
401         case ToolbarItem::SEPARATOR:
402                 addSeparator();
403                 break;
404         case ToolbarItem::LAYOUTS: {
405                 LayoutBox * layout = owner_.getLayoutDialog();
406                 QObject::connect(this, SIGNAL(iconSizeChanged(QSize)),
407                         layout, SLOT(setIconSize(QSize)));
408                 QAction * action = addWidget(layout);
409                 action->setVisible(true);
410                 break;
411         }
412         case ToolbarItem::MINIBUFFER:
413                 command_buffer_ = new GuiCommandBuffer(&owner_);
414                 addWidget(command_buffer_);
415                 /// \todo find a Qt4 equivalent to setHorizontalStretchable(true);
416                 //setHorizontalStretchable(true);
417                 break;
418         case ToolbarItem::TABLEINSERT: {
419                 QToolButton * tb = new QToolButton;
420                 tb->setCheckable(true);
421                 tb->setIcon(getIcon(FuncRequest(LFUN_TABULAR_INSERT), true));
422                 QString const label = qt_(to_ascii(item.label_));
423                 tb->setToolTip(label);
424                 tb->setStatusTip(label);
425                 tb->setText(label);
426                 InsertTableWidget * iv = new InsertTableWidget(tb);
427                 connect(tb, SIGNAL(clicked(bool)), iv, SLOT(show(bool)));
428                 connect(iv, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool)));
429                 connect(this, SIGNAL(updated()), iv, SLOT(updateParent()));
430                 addWidget(tb);
431                 break;
432                 }
433         case ToolbarItem::ICONPALETTE:
434                 addWidget(new PaletteButton(this, item));
435                 break;
436         case ToolbarItem::POPUPMENU: {
437                 addWidget(new StaticMenuButton(this, item, false));
438                 break;
439                 }
440         case ToolbarItem::STICKYPOPUPMENU: {
441                 addWidget(new StaticMenuButton(this, item, true));
442                 break;
443                 }
444         case ToolbarItem::DYNAMICMENU: {
445                 // we only handle certain things
446                 if (DynamicMenuButton::isMenuType(item.name_))
447                         addWidget(new DynamicMenuButton(this, item));
448                 else
449                         LYXERR0("Unknown dynamic menu type: " << item.name_);
450                 break;
451         }
452         case ToolbarItem::COMMAND: {
453                 if (!getStatus(*item.func_).unknown())
454                         addAction(addItem(item));
455                 break;
456                 }
457         default:
458                 break;
459         }
460 }
461
462
463 void GuiToolbar::update(int context)
464 {
465         if (visibility_ & Toolbars::AUTO) {
466                 setVisible(visibility_ & context & Toolbars::ALLOWAUTO);
467                 if (isVisible() && commandBuffer() && (context & Toolbars::MINIBUFFER_FOCUS))
468                         commandBuffer()->setFocus();
469         }
470
471         // update visible toolbars only
472         if (!isVisible())
473                 return;
474
475         // This is a speed bottleneck because this is called on every keypress
476         // and update calls getStatus, which copies the cursor at least two times
477         for (int i = 0; i < actions_.size(); ++i)
478                 actions_[i]->update();
479
480         LayoutBox * layout = owner_.getLayoutDialog();
481         if (layout)
482                 layout->setEnabled(lyx::getStatus(FuncRequest(LFUN_LAYOUT)).enabled());
483
484         // emit signal
485         updated();
486 }
487
488
489 QString GuiToolbar::sessionKey() const
490 {
491         return "views/" + QString::number(owner_.id()) + "/" + objectName();
492 }
493
494
495 void GuiToolbar::saveSession(QSettings & settings) const
496 {
497         settings.setValue(sessionKey() + "/visibility", visibility_);
498         settings.setValue(sessionKey() + "/movability", isMovable());
499 }
500
501
502 void GuiToolbar::restoreSession()
503 {
504         QSettings settings;
505         int const error_val = -1;
506         int visibility =
507                 settings.value(sessionKey() + "/visibility", error_val).toInt();
508         if (visibility == error_val || visibility == 0) {
509                 // This should not happen, but in case we use the defaults
510                 LYXERR(Debug::GUI, "Session settings could not be found! Defaults are used instead.");
511                 visibility =
512                         guiApp->toolbars().defaultVisibility(fromqstr(objectName()));
513         }
514         setVisibility(visibility);
515
516         int movability = settings.value(sessionKey() + "/movability", true).toBool();
517         setMovable(movability);
518 }
519
520
521 void GuiToolbar::toggle()
522 {
523         docstring state;
524         if (visibility_ & Toolbars::ALLOWAUTO) {
525                 if (!(visibility_ & Toolbars::AUTO)) {
526                         visibility_ |= Toolbars::AUTO;
527                         hide();
528                         state = _("auto");
529                 } else {
530                         visibility_ &= ~Toolbars::AUTO;
531                         if (isVisible()) {
532                                 hide();
533                                 state = _("off");
534                         } else {
535                                 show();
536                                 state = _("on");
537                         }
538                 }
539         } else {
540                 if (isVisible()) {
541                         hide();
542                         state = _("off");
543                 } else {
544                         show();
545                         state = _("on");
546                 }
547         }
548
549         owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
550                 qstring_to_ucs4(windowTitle()), state));
551 }
552
553 void GuiToolbar::movable(bool silent)
554 {
555         // toggle movability
556         setMovable(!isMovable());
557
558         // manual update avoids bug in qt that the drag handle is not removed
559         // properly, e.g. in Windows
560         Q_EMIT update();
561
562         // silence for toggling of many toolbars for performance
563         if (!silent) {
564                 docstring state;
565                 if (isMovable())
566                         state = _("movable");
567                 else
568                         state = _("immovable");
569                 owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
570                         qstring_to_ucs4(windowTitle()), state));
571         }
572 }
573
574 } // namespace frontend
575 } // namespace lyx
576
577 #include "moc_GuiToolbar.cpp"