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.
6 * \author Lars Gullik Bjønnes
8 * \author Jean-Marc Lasgouttes
9 * \author Angus Leeming
10 * \author Stefan Schimanski
11 * \author Abdelrazak Younes
13 * Full author contact details are available in file CREDITS.
18 #include "GuiToolbar.h"
22 #include "BufferParams.h"
23 #include "BufferView.h"
25 #include "CutAndPaste.h"
26 #include "FuncRequest.h"
27 #include "FuncStatus.h"
28 #include "GuiApplication.h"
29 #include "GuiCommandBuffer.h"
31 #include "IconPalette.h"
32 #include "InsertTableWidget.h"
34 #include "LayoutBox.h"
38 #include "qt_helpers.h"
41 #include "TextClass.h"
44 #include "insets/InsetText.h"
46 #include "support/convert.h"
47 #include "support/debug.h"
48 #include "support/docstring_list.h"
49 #include "support/gettext.h"
50 #include "support/lstrings.h"
56 #include <QToolButton>
58 #include "support/lassert.h"
61 using namespace lyx::support;
66 QIcon DynamicMenuButton::icon_textstyle_apply_;
67 QIcon DynamicMenuButton::icon_undo_;
68 QIcon DynamicMenuButton::icon_paste_;
71 GuiToolbar::GuiToolbar(ToolbarInfo const & tbinfo, GuiView & owner)
72 : QToolBar(toqstr(tbinfo.gui_name), &owner), visibility_(0),
73 owner_(owner), command_buffer_(nullptr), tbinfo_(tbinfo), filled_(false),
76 setIconSize(owner.iconSize());
77 connect(&owner, SIGNAL(iconSizeChanged(QSize)), this,
78 SLOT(setIconSize(QSize)));
79 setContextMenuPolicy(Qt::CustomContextMenu);
80 connect(this, SIGNAL(customContextMenuRequested(QPoint)),
81 SLOT(showContextMenu(QPoint)));
83 // This is used by QMainWindow::restoreState for proper main window state
85 setObjectName(toqstr(tbinfo.name));
90 void GuiToolbar::setVisible(bool visible)
92 // This is a hack to find out which toolbars have been restored by
93 // MainWindow::restoreState and which toolbars should be initialized
94 // by us (i.e., new toolbars)
96 // Record the actual visibility in toolbar state visibility_.
97 // This is useful to restore the visibility of toolbars when
98 // returning from full-screen. Therefore the recording is disabled
99 // while LyX is in full-screen state.
100 if (!owner_.isFullScreen()) {
102 visibility_ |= Toolbars::ON;
104 visibility_ &= ~Toolbars::ON;
106 QToolBar::setVisible(visible);
110 bool GuiToolbar::isRestored() const
116 void GuiToolbar::fill()
120 ToolbarInfo::item_iterator it = tbinfo_.items.begin();
121 ToolbarInfo::item_iterator end = tbinfo_.items.end();
122 for (; it != end; ++it)
128 void GuiToolbar::refill()
136 void GuiToolbar::showEvent(QShowEvent * ev)
143 void GuiToolbar::setVisibility(int visibility)
145 visibility_ = visibility;
149 void GuiToolbar::showContextMenu(QPoint pos)
151 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), owner_);
152 menu->exec(mapToGlobal(pos));
156 Action * GuiToolbar::addItem(ToolbarItem const & item, bool menu)
158 QString text = toqstr(item.label);
159 QString tooltip = text;
160 // Get the keys bound to this action, but keep only the
162 KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(*item.func);
163 if (!bindings.empty()) {
164 QString binding = toqstr(bindings.begin()->print(KeySequence::ForGui));
166 text += '\t' + binding;
168 tooltip += " [" + binding + "]";
171 Action * act = new Action(item.func, getIcon(*item.func, false), text,
173 if (item.type == ToolbarItem::BIDICOMMAND)
174 act->setRtlIcon(getIcon(*item.func, false, true));
176 actions_.append(act);
182 class PaletteButton : public QToolButton
186 ToolbarItem const & tbitem_;
189 PaletteButton(GuiToolbar * bar, ToolbarItem const & item)
190 : QToolButton(bar), bar_(bar), tbitem_(item), initialized_(false)
192 QString const label = qt_(to_ascii(tbitem_.label));
196 connect(bar_, SIGNAL(iconSizeChanged(QSize)),
197 this, SLOT(setIconSize(QSize)));
199 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name);
201 // use the icon of first action for the toolbar button
202 setIcon(getIcon(*tbinfo->items.begin()->func, true));
205 void mousePressEvent(QMouseEvent * e) override
208 QToolButton::mousePressEvent(e);
214 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name);
216 LYXERR0("Unknown toolbar " << tbitem_.name);
219 IconPalette * panel = new IconPalette(this);
220 QString const label = qt_(to_ascii(tbitem_.label));
221 panel->setWindowTitle(label);
222 connect(this, SIGNAL(clicked(bool)), panel, SLOT(setVisible(bool)));
223 connect(panel, SIGNAL(visible(bool)), this, SLOT(setChecked(bool)));
224 ToolbarInfo::item_iterator it = tbinfo->items.begin();
225 ToolbarInfo::item_iterator const end = tbinfo->items.end();
226 for (; it != end; ++it)
227 if (!getStatus(*it->func).unknown())
228 panel->addButton(bar_->addItem(*it));
230 QToolButton::mousePressEvent(e);
237 MenuButtonBase::MenuButtonBase(GuiToolbar * bar, ToolbarItem const & item)
238 : QToolButton(bar), bar_(bar), tbitem_(item)
240 setPopupMode(QToolButton::InstantPopup);
241 QString const label = qt_(to_ascii(tbitem_.label));
245 QString const name = toqstr(tbitem_.name);
246 QStringList imagedirs;
247 imagedirs << "images/math/" << "images/";
248 for (int i = 0; i < imagedirs.size(); ++i) {
249 QString imagedir = imagedirs.at(i);
250 FileName const fname = imageLibFileSearch(imagedir, name, "svgz,png",
251 theGuiApp()->imageSearchMode());
252 if (fname.exists()) {
253 setIcon(QIcon(getPixmap(imagedir, name, "svgz,png")));
260 void MenuButtonBase::actionTriggered(QAction * action)
262 QToolButton::setDefaultAction(action);
263 setPopupMode(QToolButton::DelayedPopup);
267 StaticMenuButton::StaticMenuButton(
268 GuiToolbar * bar, ToolbarItem const & item, bool const sticky)
269 : MenuButtonBase(bar, item)
272 connect(this, SIGNAL(triggered(QAction *)),
273 this, SLOT(actionTriggered(QAction *)));
274 connect(bar, SIGNAL(iconSizeChanged(QSize)),
275 this, SLOT(setIconSize(QSize)));
280 void StaticMenuButton::initialize()
282 QString const label = qt_(to_ascii(tbitem_.label));
283 ButtonMenu * m = new ButtonMenu(label, this);
284 m->setWindowTitle(label);
285 m->setTearOffEnabled(true);
286 connect(bar_, SIGNAL(updated()), m, SLOT(updateParent()));
287 connect(bar_, SIGNAL(updated()), this, SLOT(updateTriggered()));
288 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name);
290 LYXERR0("Unknown toolbar " << tbitem_.name);
293 ToolbarInfo::item_iterator it = tbinfo->items.begin();
294 ToolbarInfo::item_iterator const end = tbinfo->items.end();
295 for (; it != end; ++it)
296 if (!getStatus(*it->func).unknown())
297 m->add(bar_->addItem(*it, true));
302 void StaticMenuButton::updateTriggered()
307 bool enabled = false;
308 QList<QAction *> acts = menu()->actions();
309 for (auto const & act : acts)
310 if (act->isEnabled()) {
314 // Enable the MenuButton if at least one menu item is enabled
316 // If a disabled item is default, switch to InstantPopup
317 // (this can happen if a user selects e.g. DVI and then
318 // turns non-TeX fonts on)
319 if (defaultAction() && !defaultAction()->isEnabled())
320 setPopupMode(QToolButton::InstantPopup);
324 class DynamicMenuButton::Private
327 Private(Private const &);
328 void operator=(Private const &);
330 Private() : inset_(nullptr) {}
332 DocumentClassConstPtr text_class_;
334 InsetText const * inset_;
338 DynamicMenuButton::DynamicMenuButton(GuiToolbar * bar, ToolbarItem const & item)
339 : MenuButtonBase(bar, item), d(new Private())
345 DynamicMenuButton::~DynamicMenuButton()
351 void DynamicMenuButton::initialize()
353 QString const label = qt_(to_ascii(tbitem_.label));
354 ButtonMenu * m = new ButtonMenu(label, this);
355 m->setWindowTitle(label);
356 m->setTearOffEnabled(true);
357 connect(bar_, SIGNAL(updated()), m, SLOT(updateParent()));
358 connect(bar_, SIGNAL(updated()), this, SLOT(updateTriggered()));
359 connect(bar_, SIGNAL(iconSizeChanged(QSize)),
360 this, SLOT(setIconSize(QSize)));
365 bool DynamicMenuButton::isMenuType(string const & s)
367 return s == "dynamic-custom-insets"
368 || s == "dynamic-char-styles"
369 || s == "textstyle-apply"
374 void DynamicMenuButton::resetIconCache()
376 icon_textstyle_apply_ = getIcon(FuncRequest(LFUN_TEXTSTYLE_APPLY), false);
377 icon_undo_ = getIcon(FuncRequest(LFUN_UNDO), false);
378 icon_paste_ = getIcon(FuncRequest(LFUN_PASTE), false);
382 void DynamicMenuButton::updateTriggered()
385 // the menu should exist by this point
386 // if not, we can at least avoid crashing in release mode
388 GuiView const & owner = bar_->owner();
389 BufferView const * bv = owner.currentBufferView();
391 string const & menutype = tbitem_.name;
392 if (menutype == "dynamic-custom-insets" || menutype == "dynamic-char-styles") {
396 setMinimumWidth(sizeHint().width());
397 d->text_class_.reset();
401 DocumentClassConstPtr text_class =
402 bv->buffer().params().documentClassPtr();
403 InsetText const * inset = &(bv->cursor().innerText()->inset());
404 // if the text class has changed, then we need to reload the menu
405 if (d->text_class_ != text_class) {
406 d->text_class_ = text_class;
407 // at the moment, we can just call loadFlexInsets, and it will
408 // handle both types. if there were more types of menus, then we
409 // might need to have other options.
412 // remember where we are
414 // note that enabling here might need to be more subtle if there
415 // were other kinds of menus.
416 setEnabled(!bv->buffer().isReadonly()
418 && inset->insetAllowed(FLEX_CODE));
419 } else if (menutype == "textstyle-apply") {
421 setPopupMode(QToolButton::MenuButtonPopup);
423 QToolButton::setIcon(icon_textstyle_apply_);
427 vector<docstring> ffList = bv->cursor().innerText()->getFreeFonts();
429 Action * default_act = nullptr;
430 for (auto const & f : ffList) {
431 FuncRequest func(LFUN_TEXTSTYLE_APPLY, convert<docstring>(i),
432 FuncRequest::TOOLBAR);
433 docstring const lb = char_type('&') + convert<docstring>(i)
434 + from_ascii(". ") + f ;
435 Action * act = new Action(func, QIcon(), toqstr(lb), toqstr(f), this);
437 // The most recent one is the default
442 // Add items to reset to defaults
443 Action * reset_act = new Action(FuncRequest(LFUN_FONT_DEFAULT, FuncRequest::TOOLBAR),
445 qt_("&Reset to default (keep language)"),
446 qt_("Reset all font settings to their defaults but keep language settings"), this);
447 m->addAction(reset_act);
448 Action * reset_act_lang = new Action(FuncRequest(LFUN_COMMAND_SEQUENCE, "font-default ; language reset",
449 FuncRequest::TOOLBAR),
451 qt_("Reset to default (including &language)"),
452 qt_("Reset all font settings and the language to their defaults"), this);
453 m->addAction(reset_act_lang);
455 QToolButton::setDefaultAction(default_act);
456 QToolButton::setIcon(icon_textstyle_apply_);
457 setEnabled(lyx::getStatus(FuncRequest(LFUN_TEXTSTYLE_APPLY)).enabled()
458 || lyx::getStatus(FuncRequest(LFUN_FONT_DEFAULT)).enabled());
459 } else if (menutype == "paste") {
461 setPopupMode(QToolButton::MenuButtonPopup);
462 Action * default_action = new Action(FuncRequest(LFUN_PASTE),
464 qt_("Paste"), qt_("Paste"), this);
467 QToolButton::setDefaultAction(default_action);
470 docstring_list const sel = cap::availableSelections(&bv->buffer());
472 docstring_list::const_iterator cit = sel.begin();
473 docstring_list::const_iterator end = sel.end();
475 for (unsigned int index = 0; cit != end; ++cit, ++index) {
476 docstring const s = *cit;
477 FuncRequest func(LFUN_PASTE, convert<docstring>(index),
478 FuncRequest::TOOLBAR);
479 docstring const lb = char_type('&') + convert<docstring>(index)
480 + from_ascii(". ") + s ;
481 Action * act = new Action(func, QIcon(), toqstr(lb), toqstr(s), this);
484 QToolButton::setDefaultAction(default_action);
485 setEnabled(lyx::getStatus(FuncRequest(LFUN_PASTE)).enabled());
490 void DynamicMenuButton::loadFlexInsets()
494 string const & menutype = tbitem_.name;
496 if (menutype == "dynamic-custom-insets")
497 ftype = InsetLyXType::CUSTOM;
498 else if (menutype == "dynamic-char-styles")
499 ftype = InsetLyXType::CHARSTYLE;
501 // this should have been taken care of earlier
502 LASSERT(false, return);
505 TextClass::InsetLayouts const & inset_layouts =
506 d->text_class_->insetLayouts();
507 for (auto const & iit : inset_layouts) {
508 InsetLayout const & il = iit.second;
509 if (il.lyxtype() != ftype)
511 docstring const name = iit.first;
512 QString const loc_item = toqstr(translateIfPossible(
513 prefixIs(name, from_ascii("Flex:")) ?
514 name.substr(5) : name));
515 FuncRequest func(LFUN_FLEX_INSERT,
516 from_ascii("\"") + name + from_ascii("\""), FuncRequest::TOOLBAR);
518 new Action(func, getIcon(func, false), loc_item, loc_item, this);
524 void GuiToolbar::add(ToolbarItem const & item)
527 case ToolbarItem::SEPARATOR:
530 case ToolbarItem::LAYOUTS: {
531 LayoutBox * layout = owner_.getLayoutDialog();
532 QObject::connect(this, SIGNAL(iconSizeChanged(QSize)),
533 layout, SLOT(setIconSize(QSize)));
534 QAction * action = addWidget(layout);
535 action->setVisible(true);
538 case ToolbarItem::MINIBUFFER:
539 command_buffer_ = new GuiCommandBuffer(&owner_);
540 addWidget(command_buffer_);
541 /// \todo find a Qt5 equivalent to setHorizontalStretchable(true);
542 //setHorizontalStretchable(true);
544 case ToolbarItem::TABLEINSERT: {
545 QToolButton * tb = new QToolButton;
546 tb->setCheckable(true);
547 tb->setIcon(getIcon(FuncRequest(LFUN_TABULAR_INSERT), true));
548 QString const label = qt_(to_ascii(item.label));
549 tb->setToolTip(label);
550 tb->setStatusTip(label);
552 InsertTableWidget * iv = new InsertTableWidget(tb);
553 connect(tb, SIGNAL(clicked(bool)), iv, SLOT(show(bool)));
554 connect(iv, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool)));
555 connect(this, SIGNAL(updated()), iv, SLOT(updateParent()));
559 case ToolbarItem::ICONPALETTE:
560 addWidget(new PaletteButton(this, item));
562 case ToolbarItem::POPUPMENU: {
563 addWidget(new StaticMenuButton(this, item, false));
566 case ToolbarItem::STICKYPOPUPMENU: {
567 addWidget(new StaticMenuButton(this, item, true));
570 case ToolbarItem::DYNAMICMENU: {
571 // we only handle certain things
572 if (DynamicMenuButton::isMenuType(item.name))
573 addWidget(new DynamicMenuButton(this, item));
575 LYXERR0("Unknown dynamic menu type: " << item.name);
578 case ToolbarItem::BIDICOMMAND: {
579 if (!getStatus(*item.func).unknown())
580 addAction(addItem(item));
583 case ToolbarItem::COMMAND: {
584 if (!getStatus(*item.func).unknown())
585 addAction(addItem(item));
594 void GuiToolbar::update(int context)
596 if (visibility_ & Toolbars::AUTO) {
597 setVisible(visibility_ & context & Toolbars::ALLOWAUTO);
598 if (isVisible() && commandBuffer() && (context & Toolbars::MINIBUFFER_FOCUS))
599 commandBuffer()->setFocus();
602 // update visible toolbars only
606 // This is a speed bottleneck because this is called on every keypress
607 // and update calls getStatus, which copies the cursor at least two times
608 for (auto const & action : actions_)
611 LayoutBox * layout = owner_.getLayoutDialog();
613 layout->setEnabled(lyx::getStatus(FuncRequest(LFUN_LAYOUT)).enabled());
620 QString GuiToolbar::sessionKey() const
622 return "views/" + QString::number(owner_.id()) + "/" + objectName();
626 void GuiToolbar::saveSession(QSettings & settings) const
628 settings.setValue(sessionKey() + "/visibility", visibility_);
629 settings.setValue(sessionKey() + "/movability", isMovable());
633 void GuiToolbar::restoreSession()
636 int const error_val = -1;
638 settings.value(sessionKey() + "/visibility", error_val).toInt();
639 if (visibility == error_val || visibility == 0) {
640 // This should not happen, but in case we use the defaults
641 LYXERR(Debug::GUI, "Session settings could not be found! Defaults are used instead.");
643 guiApp->toolbars().defaultVisibility(fromqstr(objectName()));
645 setVisibility(visibility);
647 int movability = settings.value(sessionKey() + "/movability", true).toBool();
648 setMovable(movability);
652 bool GuiToolbar::isVisibilityOn() const
654 return visibility_ & Toolbars::ON;
658 void GuiToolbar::setState(string const state)
661 if (state == "auto") {
662 if (visibility_ & Toolbars::ALLOWAUTO) {
663 visibility_ |= Toolbars::AUTO;
665 newstate = _("auto");
667 owner_.message(bformat(_("Toolbar \"%1$s\" does not support state \"auto\""),
668 qstring_to_ucs4(windowTitle())));
670 if (visibility_ & Toolbars::AUTO)
671 visibility_ &= ~Toolbars::AUTO;
672 if (state == "off") {
675 } else if (state == "on") {
681 owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
682 qstring_to_ucs4(windowTitle()), newstate));
686 void GuiToolbar::toggle()
688 if (visibility_ & Toolbars::ALLOWAUTO) {
689 if (!(visibility_ & Toolbars::AUTO) && !isVisibilityOn()) {
692 if (isVisibilityOn())
705 void GuiToolbar::movable(bool silent)
708 setMovable(!isMovable());
710 // manual update avoids bug in qt that the drag handle is not removed
711 // properly, e.g. in Windows
714 // silence for toggling of many toolbars for performance
718 state = _("movable");
720 state = _("immovable");
721 owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
722 qstring_to_ucs4(windowTitle()), state));
726 } // namespace frontend
729 #include "moc_GuiToolbar.cpp"