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