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