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 item to reset to defaults
443 Action * reset_act = new Action(FuncRequest(LFUN_FONT_DEFAULT, FuncRequest::TOOLBAR),
445 qt_("&Reset to default"),
446 qt_("Reset all font settings to their defaults"), this);
447 m->addAction(reset_act);
449 QToolButton::setDefaultAction(default_act);
450 QToolButton::setIcon(icon_textstyle_apply_);
451 setEnabled(lyx::getStatus(FuncRequest(LFUN_TEXTSTYLE_APPLY)).enabled()
452 || lyx::getStatus(FuncRequest(LFUN_FONT_DEFAULT)).enabled());
453 } else if (menutype == "paste") {
455 setPopupMode(QToolButton::MenuButtonPopup);
456 Action * default_action = new Action(FuncRequest(LFUN_PASTE),
458 qt_("Paste"), qt_("Paste"), this);
461 QToolButton::setDefaultAction(default_action);
464 docstring_list const sel = cap::availableSelections(&bv->buffer());
466 docstring_list::const_iterator cit = sel.begin();
467 docstring_list::const_iterator end = sel.end();
469 for (unsigned int index = 0; cit != end; ++cit, ++index) {
470 docstring const s = *cit;
471 FuncRequest func(LFUN_PASTE, convert<docstring>(index),
472 FuncRequest::TOOLBAR);
473 docstring const lb = char_type('&') + convert<docstring>(index)
474 + from_ascii(". ") + s ;
475 Action * act = new Action(func, QIcon(), toqstr(lb), toqstr(s), this);
478 QToolButton::setDefaultAction(default_action);
479 setEnabled(lyx::getStatus(FuncRequest(LFUN_PASTE)).enabled());
484 void DynamicMenuButton::loadFlexInsets()
488 string const & menutype = tbitem_.name;
490 if (menutype == "dynamic-custom-insets")
491 ftype = InsetLyXType::CUSTOM;
492 else if (menutype == "dynamic-char-styles")
493 ftype = InsetLyXType::CHARSTYLE;
495 // this should have been taken care of earlier
496 LASSERT(false, return);
499 TextClass::InsetLayouts const & inset_layouts =
500 d->text_class_->insetLayouts();
501 for (auto const & iit : inset_layouts) {
502 InsetLayout const & il = iit.second;
503 if (il.lyxtype() != ftype)
505 docstring const name = iit.first;
506 QString const loc_item = toqstr(translateIfPossible(
507 prefixIs(name, from_ascii("Flex:")) ?
508 name.substr(5) : name));
509 FuncRequest func(LFUN_FLEX_INSERT,
510 from_ascii("\"") + name + from_ascii("\""), FuncRequest::TOOLBAR);
512 new Action(func, getIcon(func, false), loc_item, loc_item, this);
518 void GuiToolbar::add(ToolbarItem const & item)
521 case ToolbarItem::SEPARATOR:
524 case ToolbarItem::LAYOUTS: {
525 LayoutBox * layout = owner_.getLayoutDialog();
526 QObject::connect(this, SIGNAL(iconSizeChanged(QSize)),
527 layout, SLOT(setIconSize(QSize)));
528 QAction * action = addWidget(layout);
529 action->setVisible(true);
532 case ToolbarItem::MINIBUFFER:
533 command_buffer_ = new GuiCommandBuffer(&owner_);
534 addWidget(command_buffer_);
535 /// \todo find a Qt5 equivalent to setHorizontalStretchable(true);
536 //setHorizontalStretchable(true);
538 case ToolbarItem::TABLEINSERT: {
539 QToolButton * tb = new QToolButton;
540 tb->setCheckable(true);
541 tb->setIcon(getIcon(FuncRequest(LFUN_TABULAR_INSERT), true));
542 QString const label = qt_(to_ascii(item.label));
543 tb->setToolTip(label);
544 tb->setStatusTip(label);
546 InsertTableWidget * iv = new InsertTableWidget(tb);
547 connect(tb, SIGNAL(clicked(bool)), iv, SLOT(show(bool)));
548 connect(iv, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool)));
549 connect(this, SIGNAL(updated()), iv, SLOT(updateParent()));
553 case ToolbarItem::ICONPALETTE:
554 addWidget(new PaletteButton(this, item));
556 case ToolbarItem::POPUPMENU: {
557 addWidget(new StaticMenuButton(this, item, false));
560 case ToolbarItem::STICKYPOPUPMENU: {
561 addWidget(new StaticMenuButton(this, item, true));
564 case ToolbarItem::DYNAMICMENU: {
565 // we only handle certain things
566 if (DynamicMenuButton::isMenuType(item.name))
567 addWidget(new DynamicMenuButton(this, item));
569 LYXERR0("Unknown dynamic menu type: " << item.name);
572 case ToolbarItem::BIDICOMMAND: {
573 if (!getStatus(*item.func).unknown())
574 addAction(addItem(item));
577 case ToolbarItem::COMMAND: {
578 if (!getStatus(*item.func).unknown())
579 addAction(addItem(item));
588 void GuiToolbar::update(int context)
590 if (visibility_ & Toolbars::AUTO) {
591 setVisible(visibility_ & context & Toolbars::ALLOWAUTO);
592 if (isVisible() && commandBuffer() && (context & Toolbars::MINIBUFFER_FOCUS))
593 commandBuffer()->setFocus();
596 // update visible toolbars only
600 // This is a speed bottleneck because this is called on every keypress
601 // and update calls getStatus, which copies the cursor at least two times
602 for (auto const & action : actions_)
605 LayoutBox * layout = owner_.getLayoutDialog();
607 layout->setEnabled(lyx::getStatus(FuncRequest(LFUN_LAYOUT)).enabled());
614 QString GuiToolbar::sessionKey() const
616 return "views/" + QString::number(owner_.id()) + "/" + objectName();
620 void GuiToolbar::saveSession(QSettings & settings) const
622 settings.setValue(sessionKey() + "/visibility", visibility_);
623 settings.setValue(sessionKey() + "/movability", isMovable());
627 void GuiToolbar::restoreSession()
630 int const error_val = -1;
632 settings.value(sessionKey() + "/visibility", error_val).toInt();
633 if (visibility == error_val || visibility == 0) {
634 // This should not happen, but in case we use the defaults
635 LYXERR(Debug::GUI, "Session settings could not be found! Defaults are used instead.");
637 guiApp->toolbars().defaultVisibility(fromqstr(objectName()));
639 setVisibility(visibility);
641 int movability = settings.value(sessionKey() + "/movability", true).toBool();
642 setMovable(movability);
646 bool GuiToolbar::isVisibilityOn() const
648 return visibility_ & Toolbars::ON;
652 void GuiToolbar::setState(string const state)
655 if (state == "auto") {
656 if (visibility_ & Toolbars::ALLOWAUTO) {
657 visibility_ |= Toolbars::AUTO;
659 newstate = _("auto");
661 owner_.message(bformat(_("Toolbar \"%1$s\" does not support state \"auto\""),
662 qstring_to_ucs4(windowTitle())));
664 if (visibility_ & Toolbars::AUTO)
665 visibility_ &= ~Toolbars::AUTO;
666 if (state == "off") {
669 } else if (state == "on") {
675 owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
676 qstring_to_ucs4(windowTitle()), newstate));
680 void GuiToolbar::toggle()
682 if (visibility_ & Toolbars::ALLOWAUTO) {
683 if (!(visibility_ & Toolbars::AUTO) && !isVisibilityOn()) {
686 if (isVisibilityOn())
699 void GuiToolbar::movable(bool silent)
702 setMovable(!isMovable());
704 // manual update avoids bug in qt that the drag handle is not removed
705 // properly, e.g. in Windows
708 // silence for toggling of many toolbars for performance
712 state = _("movable");
714 state = _("immovable");
715 owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
716 qstring_to_ucs4(windowTitle()), state));
720 } // namespace frontend
723 #include "moc_GuiToolbar.cpp"