]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiToolbar.cpp
7b0c20213e2d26c84d9f7f3db7f17c6e79621b5f
[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 "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiView.h"
24 #include "IconPalette.h"
25 #include "InsertTableWidget.h"
26 #include "qt_helpers.h"
27 #include "Toolbars.h"
28
29 #include "Buffer.h"
30 #include "BufferParams.h"
31 #include "BufferView.h"
32 #include "Cursor.h"
33 #include "FuncRequest.h"
34 #include "FuncStatus.h"
35 #include "KeyMap.h"
36 #include "Layout.h"
37 #include "LyXFunc.h"
38 #include "LyXRC.h"
39 #include "Paragraph.h"
40 #include "TextClass.h"
41
42 #include "insets/InsetText.h"
43
44 #include "support/debug.h"
45 #include "support/filetools.h"
46 #include "support/gettext.h"
47 #include "support/lstrings.h"
48
49 #include <QAbstractItemDelegate>
50 #include <QAbstractTextDocumentLayout>
51 #include <QApplication>
52 #include <QComboBox>
53 #include <QFontMetrics>
54 #include <QHeaderView>
55 #include <QItemDelegate>
56 #include <QKeyEvent>
57 #include <QList>
58 #include <QListView>
59 #include <QPainter>
60 #include <QPixmap>
61 #include <QSettings>
62 #include <QSortFilterProxyModel>
63 #include <QStandardItem>
64 #include <QStandardItemModel>
65 #include <QString>
66 #include <QTextDocument>
67 #include <QTextFrame>
68 #include <QToolBar>
69 #include <QToolButton>
70 #include <QVariant>
71
72 #include "support/lassert.h"
73
74 using namespace std;
75 using namespace lyx::support;
76
77 namespace lyx {
78 namespace frontend {
79
80 /////////////////////////////////////////////////////////////////////
81 //
82 // GuiLayoutBox
83 //
84 /////////////////////////////////////////////////////////////////////
85
86 class LayoutItemDelegate : public QItemDelegate {
87 public:
88         ///
89         explicit LayoutItemDelegate(QObject * parent = 0)
90                 : QItemDelegate(parent)
91         {}
92         
93         ///
94         void paint(QPainter * painter, QStyleOptionViewItem const & option,
95                 QModelIndex const & index) const
96         {
97                 QStyleOptionViewItem opt = option;
98                 
99                 // default background
100                 painter->fillRect(opt.rect, opt.palette.color(QPalette::Base));
101                 
102                 // category header?
103                 if (lyxrc.group_layouts) {
104                         QSortFilterProxyModel const * model
105                         = static_cast<QSortFilterProxyModel const *>(index.model());
106                         
107                         QString stdCat = category(*model->sourceModel(), 0);
108                         QString cat = category(*index.model(), index.row());
109                         
110                         // not the standard layout and not the same as in the previous line?
111                         if (stdCat != cat
112                             && (index.row() == 0 || cat != category(*index.model(), index.row() - 1))) {
113                                 painter->save();
114                                 
115                                 // draw unselected background
116                                 QStyle::State state = opt.state;
117                                 opt.state = opt.state & ~QStyle::State_Selected;
118                                 drawBackground(painter, opt, index);
119                                 opt.state = state;
120                                 
121                                 // draw category header
122                                 drawCategoryHeader(painter, opt, 
123                                         category(*index.model(), index.row()));
124
125                                 // move rect down below header
126                                 opt.rect.setTop(opt.rect.top() + headerHeight(opt));
127                                 
128                                 painter->restore();
129                         }
130                 }
131
132                 QItemDelegate::paint(painter, opt, index);
133         }
134         
135         ///
136         void drawDisplay(QPainter * painter, QStyleOptionViewItem const & opt,
137                          const QRect & /*rect*/, const QString & text ) const
138         {
139                 QString utext = underlineFilter(text);
140
141                 // Draw the rich text.
142                 painter->save();
143                 QColor col = opt.palette.text().color();
144                 if (opt.state & QStyle::State_Selected)
145                         col = opt.palette.highlightedText().color();
146                 QAbstractTextDocumentLayout::PaintContext context;
147                 context.palette.setColor(QPalette::Text, col);
148                 
149                 QTextDocument doc;
150                 doc.setDefaultFont(opt.font);
151                 doc.setHtml(utext);
152                 
153                 QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
154                 fmt.setMargin(0);
155                 doc.rootFrame()->setFrameFormat(fmt);
156                 
157                 painter->translate(opt.rect.x() + 5,
158                         opt.rect.y() + (opt.rect.height() - opt.fontMetrics.height()) / 2);
159                 doc.documentLayout()->draw(painter, context);
160                 painter->restore();
161         }
162         
163         ///
164         QSize sizeHint(QStyleOptionViewItem const & opt,
165                 QModelIndex const & index) const
166         {
167                 GuiLayoutBox * combo = static_cast<GuiLayoutBox *>(parent());
168                 QSortFilterProxyModel const * model
169                 = static_cast<QSortFilterProxyModel const *>(index.model());    
170                 QSize size = QItemDelegate::sizeHint(opt, index);
171                 
172                 /// QComboBox uses the first row height to estimate the
173                 /// complete popup height during QComboBox::showPopup().
174                 /// To avoid scrolling we have to sneak in space for the headers.
175                 /// So we tweak this value accordingly. It's not nice, but the
176                 /// only possible way it seems.
177                 if (lyxrc.group_layouts && index.row() == 0 && combo->inShowPopup_) {
178                         int itemHeight = size.height();
179                         
180                         // we have to show \c cats many headers:
181                         unsigned cats = combo->visibleCategories_;
182                         
183                         // and we have \c n items to distribute the needed space over
184                         unsigned n = combo->model()->rowCount();
185                         
186                         // so the needed average height (rounded upwards) is:
187                         size.setHeight((headerHeight(opt) * cats + itemHeight * n + n - 1) / n); 
188                         return size;
189                 }
190
191                 // Add space for the category headers here?
192                 // Not for the standard layout though.
193                 QString stdCat = category(*model->sourceModel(), 0);
194                 QString cat = category(*index.model(), index.row());
195                 if (lyxrc.group_layouts && stdCat != cat
196                     && (index.row() == 0 || cat != category(*index.model(), index.row() - 1))) {
197                         size.setHeight(size.height() + headerHeight(opt));
198                 }
199
200                 return size;
201         }
202         
203 private:
204         ///
205         QString category(QAbstractItemModel const & model, int row) const
206         {
207                 return model.data(model.index(row, 2), Qt::DisplayRole).toString();
208         }
209                 
210         ///
211         int headerHeight(QStyleOptionViewItem const & opt) const
212         {
213                 return opt.fontMetrics.height() * 8 / 10;
214         }
215         ///
216         void drawCategoryHeader(QPainter * painter, QStyleOptionViewItem const & opt,
217                 QString const & category) const
218         {
219                 // slightly blended color
220                 QColor lcol = opt.palette.text().color();
221                 lcol.setAlpha(127);
222                 painter->setPen(lcol);
223                 
224                 // set 80% scaled, bold font
225                 QFont font = opt.font;
226                 font.setBold(true);
227                 font.setWeight(QFont::Black);
228                 font.setPointSize(opt.font.pointSize() * 8 / 10);
229                 painter->setFont(font);
230                 
231                 // draw the centered text
232                 QFontMetrics fm(font);
233                 int w = fm.width(category);
234                 int x = opt.rect.x() + (opt.rect.width() - w) / 2;
235                 int y = opt.rect.y() + fm.ascent();
236                 int left = x;
237                 int right = x + w;
238                 painter->drawText(x, y, category);
239                 
240                 // the vertical position of the line: middle of lower case chars
241                 int ymid = y - 1 - fm.xHeight() / 2; // -1 for the baseline
242                 
243                 // draw the horizontal line
244                 if (!category.isEmpty()) {
245                         painter->drawLine(opt.rect.x(), ymid, left - 1, ymid);
246                         painter->drawLine(right + 1, ymid, opt.rect.right(), ymid);
247                 } else
248                         painter->drawLine(opt.rect.x(), ymid, opt.rect.right(), ymid);
249         }
250
251         
252         ///
253         QString underlineFilter(QString const & s) const
254         {
255                 // get filter
256                 GuiLayoutBox * p = static_cast<GuiLayoutBox *>(parent());
257                 QString const & f = p->filter();
258                 if (f.isEmpty())
259                         return s;
260                 
261                 // step through data item and put "(x)" for every matching character
262                 QString r;
263                 int lastp = -1;
264                 p->filter();
265                 for (int i = 0; i < f.length(); ++i) {
266                         int p = s.indexOf(f[i], lastp + 1, Qt::CaseInsensitive);
267                         LASSERT(p != -1, /**/);
268                         if (lastp == p - 1 && lastp != -1) {
269                                 // remove ")" and append "x)"
270                                 r = r.left(r.length() - 4) + s[p] + "</u>";
271                         } else {
272                                 // append "(x)"
273                                 r += s.mid(lastp + 1, p - lastp - 1);
274                                 r += QString("<u>") + s[p] + "</u>";
275                         }
276                         lastp = p;
277                 }
278                 r += s.mid(lastp + 1);
279                 return r;
280         }
281 };
282
283
284 class GuiLayoutFilterModel : public QSortFilterProxyModel {
285 public:
286         ///
287         GuiLayoutFilterModel(QObject * parent = 0)
288                 : QSortFilterProxyModel(parent)
289         {}
290         
291         ///
292         void triggerLayoutChange()
293         {
294                 layoutAboutToBeChanged();
295                 layoutChanged();
296         }
297 };
298
299
300 GuiLayoutBox::GuiLayoutBox(GuiToolbar * bar, GuiView & owner)
301         : owner_(owner), bar_(bar), lastSel_(-1),
302           layoutItemDelegate_(new LayoutItemDelegate(this)),
303           visibleCategories_(0), inShowPopup_(false)
304 {
305         setSizeAdjustPolicy(QComboBox::AdjustToContents);
306         setFocusPolicy(Qt::ClickFocus);
307         setMinimumWidth(sizeHint().width());
308         setMaxVisibleItems(100);
309
310         // set the layout model with two columns
311         // 1st: translated layout names
312         // 2nd: raw layout names
313         model_ = new QStandardItemModel(0, 2, this);
314         filterModel_ = new GuiLayoutFilterModel(this);
315         filterModel_->setSourceModel(model_);
316         setModel(filterModel_);
317
318         // for the filtering we have to intercept characters
319         view()->installEventFilter(this);
320         view()->setItemDelegateForColumn(0, layoutItemDelegate_);
321         
322         QObject::connect(this, SIGNAL(activated(int)),
323                 this, SLOT(selected(int)));
324         QObject::connect(bar_, SIGNAL(iconSizeChanged(QSize)),
325                 this, SLOT(setIconSize(QSize)));
326
327         owner_.setLayoutDialog(this);
328         updateContents(true);
329 }
330
331
332 void GuiLayoutBox::setFilter(QString const & s)
333 {
334         bool enabled = view()->updatesEnabled();
335         view()->setUpdatesEnabled(false);
336
337         // remember old selection
338         int sel = currentIndex();
339         if (sel != -1)
340                 lastSel_ = filterModel_->mapToSource(filterModel_->index(sel, 0)).row();
341
342         filter_ = s;
343         filterModel_->setFilterRegExp(charFilterRegExp(filter_));
344         countCategories();
345         
346         // restore old selection
347         if (lastSel_ != -1) {
348                 QModelIndex i = filterModel_->mapFromSource(model_->index(lastSel_, 0));
349                 if (i.isValid())
350                         setCurrentIndex(i.row());
351         }
352         
353         // Workaround to resize to content size
354         // FIXME: There must be a better way. The QComboBox::AdjustToContents)
355         //        does not help.
356         if (view()->isVisible()) {
357                 // call QComboBox::showPopup. But set the inShowPopup_ flag to switch on
358                 // the hack in the item delegate to make space for the headers.
359                 // We do not call our implementation of showPopup because that
360                 // would reset the filter again. This is only needed if the user clicks
361                 // on the QComboBox.
362                 LASSERT(!inShowPopup_, /**/);
363                 inShowPopup_ = true;
364                 QComboBox::showPopup();
365                 inShowPopup_ = false;
366
367                 // The item delegate hack is off again. So trigger a relayout of the popup.
368                 filterModel_->triggerLayoutChange();
369                 
370                 if (!s.isEmpty())
371                         owner_.message(bformat(_("Filtering layouts with \"%1$s\". "
372                                                  "Press ESC to remove filter."),
373                                                qstring_to_ucs4(s)));
374                 else
375                         owner_.message(_("Enter characters to filter the layout list."));
376         }
377         
378         view()->setUpdatesEnabled(enabled);
379 }
380
381
382 void GuiLayoutBox::countCategories()
383 {
384         int n = filterModel_->rowCount();
385         visibleCategories_ = 0;
386         if (n == 0 || !lyxrc.group_layouts)
387                 return;
388
389         // skip the "Standard" category
390         QString prevCat = model_->index(0, 2).data().toString(); 
391
392         // count categories
393         for (int i = 0; i < n; ++i) {
394                 QString cat = filterModel_->index(i, 2).data().toString();
395                 if (cat != prevCat)
396                         ++visibleCategories_;
397                 prevCat = cat;
398         }
399 }
400
401
402 QString GuiLayoutBox::charFilterRegExp(QString const & filter)
403 {
404         QString re;
405         for (int i = 0; i < filter.length(); ++i) {
406                 QChar c = filter[i];
407                 if (c.isLower())
408                         re += ".*[" + QRegExp::escape(c) + QRegExp::escape(c.toUpper()) + "]";
409                 else
410                         re += ".*" + QRegExp::escape(c);
411         }
412         return re;
413 }
414
415
416 void GuiLayoutBox::resetFilter()
417 {
418         setFilter(QString());
419 }
420
421
422 void GuiLayoutBox::showPopup()
423 {
424         owner_.message(_("Enter characters to filter the layout list."));
425
426         bool enabled = view()->updatesEnabled();
427         view()->setUpdatesEnabled(false);
428
429         resetFilter();
430
431         // call QComboBox::showPopup. But set the inShowPopup_ flag to switch on
432         // the hack in the item delegate to make space for the headers.
433         LASSERT(!inShowPopup_, /**/);
434         inShowPopup_ = true;
435         QComboBox::showPopup();
436         inShowPopup_ = false;
437         
438         // The item delegate hack is off again. So trigger a relayout of the popup.
439         filterModel_->triggerLayoutChange();
440         
441         view()->setUpdatesEnabled(enabled);
442 }
443
444
445 bool GuiLayoutBox::eventFilter(QObject * o, QEvent * e)
446 {
447         if (e->type() != QEvent::KeyPress)
448                 return QComboBox::eventFilter(o, e);
449
450         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
451         bool modified = (ke->modifiers() == Qt::ControlModifier)
452                 || (ke->modifiers() == Qt::AltModifier)
453                 || (ke->modifiers() == Qt::MetaModifier);
454         
455         switch (ke->key()) {
456         case Qt::Key_Escape:
457                 if (!modified && !filter_.isEmpty()) {
458                         resetFilter();
459                         return true;
460                 }
461                 break;
462         case Qt::Key_Backspace:
463                 if (!modified) {
464                         // cut off one character
465                         setFilter(filter_.left(filter_.length() - 1));
466                 }
467                 break;
468         default:
469                 if (modified || ke->text().isEmpty())
470                         break;
471                 // find chars for the filter string
472                 QString s;
473                 for (int i = 0; i < ke->text().length(); ++i) {
474                         QChar c = ke->text()[i];
475                         if (c.isLetterOrNumber()
476                             || c.isSymbol()
477                             || c.isPunct()
478                             || c.category() == QChar::Separator_Space) {
479                                 s += c;
480                         }
481                 }
482                 if (!s.isEmpty()) {
483                         // append new chars to the filter string
484                         setFilter(filter_ + s);
485                         return true;
486                 }
487                 break;
488         }
489
490         return QComboBox::eventFilter(o, e);
491 }
492
493         
494 void GuiLayoutBox::setIconSize(QSize size)
495 {
496 #ifdef Q_WS_MACX
497         bool small = size.height() < 20;
498         setAttribute(Qt::WA_MacSmallSize, small);
499         setAttribute(Qt::WA_MacNormalSize, !small);
500 #else
501         (void)size; // suppress warning
502 #endif
503 }
504
505
506 void GuiLayoutBox::set(docstring const & layout)
507 {
508         resetFilter();
509         
510         if (!text_class_)
511                 return;
512
513         Layout const & lay = (*text_class_)[layout];
514         QString newLayout = toqstr(lay.name());
515
516         // If the layout is obsolete, use the new one instead.
517         docstring const & obs = lay.obsoleted_by();
518         if (!obs.empty())
519                 newLayout = toqstr(obs);
520
521         int const curItem = currentIndex();
522         QModelIndex const mindex =
523                 filterModel_->mapToSource(filterModel_->index(curItem, 1));
524         QString const & currentLayout = model_->itemFromIndex(mindex)->text();
525         if (newLayout == currentLayout) {
526                 LYXERR(Debug::GUI, "Already had " << newLayout << " selected.");
527                 return;
528         }
529
530         QList<QStandardItem *> r = model_->findItems(newLayout, Qt::MatchExactly, 1);
531         if (r.empty()) {
532                 LYXERR0("Trying to select non existent layout type " << newLayout);
533                 return;
534         }
535
536         setCurrentIndex(filterModel_->mapFromSource(r.first()->index()).row());
537 }
538
539
540 void GuiLayoutBox::addItemSort(docstring const & item, docstring const & category,
541         bool sorted, bool sortedByCat, bool unknown)
542 {
543         QString qitem = toqstr(item);
544         // FIXME This is wrong for RTL, I'd suppose.
545         QString titem = toqstr(translateIfPossible(item) +
546                                (unknown ? _(" (unknown)") : from_ascii("")));
547         QString qcat = toqstr(translateIfPossible(category));
548
549         QList<QStandardItem *> row;
550         row.append(new QStandardItem(titem));
551         row.append(new QStandardItem(qitem));
552         row.append(new QStandardItem(qcat));
553
554         // the first entry is easy
555         int const end = model_->rowCount();
556         if (end == 0) {
557                 model_->appendRow(row);
558                 return;
559         }
560
561         // find category
562         int i = 0;
563         if (sortedByCat) {
564                 while (i < end && model_->item(i, 2)->text() != qcat)
565                         ++i;
566         }
567
568         // skip the Standard layout
569         if (i == 0)
570                 ++i;
571         
572         // the simple unsorted case
573         if (!sorted) {
574                 if (sortedByCat) {
575                         // jump to the end of the category group
576                         while (i < end && model_->item(i, 2)->text() == qcat)
577                                 ++i;
578                         model_->insertRow(i, row);
579                 } else
580                         model_->appendRow(row);
581                 return;
582         }
583
584         // find row to insert the item, after the separator if it exists
585         if (i < end) {
586                 // find alphabetic position
587                 while (i != end
588                        && model_->item(i, 0)->text().localeAwareCompare(titem) < 0 
589                        && (!sortedByCat || model_->item(i, 2)->text() == qcat))
590                         ++i;
591         }
592
593         model_->insertRow(i, row);
594 }
595
596
597 void GuiLayoutBox::updateContents(bool reset)
598 {
599         resetFilter();
600         
601         Buffer const * buffer = owner_.buffer();
602         if (!buffer) {
603                 model_->clear();
604                 setEnabled(false);
605                 text_class_ = 0;
606                 inset_ = 0;
607                 return;
608         }
609
610         // we'll only update the layout list if the text class has changed
611         // or we've moved from one inset to another
612         DocumentClass const * text_class = &buffer->params().documentClass();
613         Inset const * inset = 
614                 &(owner_.view()->cursor().innerText()->inset());
615         if (!reset && text_class_ == text_class && inset_ == inset) {
616                 set(owner_.view()->cursor().innerParagraph().layout().name());
617                 return;
618         }
619
620         inset_ = inset;
621         text_class_ = text_class;
622
623         model_->clear();
624         DocumentClass::const_iterator lit = text_class_->begin();
625         DocumentClass::const_iterator len = text_class_->end();
626
627         for (; lit != len; ++lit) {
628                 docstring const & name = lit->name();
629                 bool const useEmpty = inset_->forcePlainLayout() || inset_->usePlainLayout();
630                 // if this inset requires the empty layout, we skip the default
631                 // layout
632                 if (name == text_class_->defaultLayoutName() && inset_ && useEmpty)
633                         continue;
634                 // if it doesn't require the empty layout, we skip it
635                 if (name == text_class_->plainLayoutName() && inset_ && !useEmpty)
636                         continue;
637                 // obsoleted layouts are skipped as well
638                 if (!lit->obsoleted_by().empty())
639                         continue;
640                 addItemSort(name, lit->category(), lyxrc.sort_layouts, 
641                                 lyxrc.group_layouts, lit->isUnknown());
642         }
643
644         set(owner_.view()->cursor().innerParagraph().layout().name());
645         countCategories();
646         
647         // needed to recalculate size hint
648         hide();
649         setMinimumWidth(sizeHint().width());
650         setEnabled(!buffer->isReadonly() &&
651                 lyx::getStatus(FuncRequest(LFUN_LAYOUT)).enabled());
652         show();
653 }
654
655
656 void GuiLayoutBox::selected(int index)
657 {
658         // get selection
659         QModelIndex mindex = filterModel_->mapToSource(filterModel_->index(index, 1));
660         docstring layoutName = qstring_to_ucs4(model_->itemFromIndex(mindex)->text());
661         owner_.setFocus();
662
663         if (!text_class_) {
664                 updateContents(false);
665                 resetFilter();
666                 return;
667         }
668
669         // find corresponding text class
670         if (text_class_->hasLayout(layoutName)) {
671                 FuncRequest const func(LFUN_LAYOUT, layoutName, FuncRequest::TOOLBAR);
672                 theLyXFunc().setLyXView(&owner_);
673                 lyx::dispatch(func);
674                 updateContents(false);
675                 resetFilter();
676                 return;
677         }
678         LYXERR0("ERROR (layoutSelected): layout " << layoutName << " not found!");
679 }
680
681
682
683 /////////////////////////////////////////////////////////////////////
684 //
685 // GuiToolbar
686 //
687 /////////////////////////////////////////////////////////////////////
688
689
690 GuiToolbar::GuiToolbar(ToolbarInfo const & tbinfo, GuiView & owner)
691         : QToolBar(toqstr(tbinfo.gui_name), &owner), visibility_(0),
692           allowauto_(false), owner_(owner), layout_(0), command_buffer_(0),
693           tbinfo_(tbinfo), filled_(false)
694 {
695         setIconSize(owner.iconSize());
696         connect(&owner, SIGNAL(iconSizeChanged(QSize)), this,
697                 SLOT(setIconSize(QSize)));
698
699         // Toolbar dragging is allowed.
700         setMovable(true);
701         // This is used by QMainWindow::restoreState for proper main window state
702         // restauration.
703         setObjectName(toqstr(tbinfo.name));
704         restoreSession();
705 }
706
707
708 void GuiToolbar::fill()
709 {
710         if (filled_)
711                 return;
712         ToolbarInfo::item_iterator it = tbinfo_.items.begin();
713         ToolbarInfo::item_iterator end = tbinfo_.items.end();
714         for (; it != end; ++it)
715                 add(*it);       
716         filled_ = true;
717 }
718
719
720 void GuiToolbar::showEvent(QShowEvent * ev)
721 {
722         fill();
723         ev->accept();
724 }
725
726
727 void GuiToolbar::setVisibility(int visibility)
728 {
729         visibility_ = visibility;
730         allowauto_ = visibility_ >= Toolbars::MATH;
731 }
732
733
734 Action * GuiToolbar::addItem(ToolbarItem const & item)
735 {
736         QString text = toqstr(item.label_);
737         // Get the keys bound to this action, but keep only the
738         // first one later
739         KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(item.func_);
740         if (bindings.size())
741                 text += " [" + toqstr(bindings.begin()->print(KeySequence::ForGui)) + "]";
742
743         Action * act = new Action(&owner_, getIcon(item.func_, false),
744                 text, item.func_, text, this);
745         actions_.append(act);
746         return act;
747 }
748
749 namespace {
750
751 class PaletteButton : public QToolButton
752 {
753 private:
754         GuiToolbar * bar_;
755         ToolbarItem const & tbitem_;
756         bool initialized_;
757 public:
758         PaletteButton(GuiToolbar * bar, ToolbarItem const & item)
759                 : QToolButton(bar), bar_(bar), tbitem_(item), initialized_(false)
760         {
761                 QString const label = qt_(to_ascii(tbitem_.label_));
762                 setToolTip(label);
763                 setStatusTip(label);
764                 setText(label);
765                 connect(bar_, SIGNAL(iconSizeChanged(QSize)),
766                         this, SLOT(setIconSize(QSize)));
767                 setCheckable(true);
768                 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name_);
769                 if (tbinfo)
770                         // use the icon of first action for the toolbar button
771                         setIcon(getIcon(tbinfo->items.begin()->func_, true));
772         }
773
774         void mousePressEvent(QMouseEvent * e)
775         {
776                 if (initialized_) {
777                         QToolButton::mousePressEvent(e);
778                         return;
779                 }
780
781                 initialized_ = true;
782
783                 ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name_);
784                 if (!tbinfo) {
785                         LYXERR0("Unknown toolbar " << tbitem_.name_);
786                         return;
787                 }
788                 IconPalette * panel = new IconPalette(this);
789                 QString const label = qt_(to_ascii(tbitem_.label_));
790                 panel->setWindowTitle(label);
791                 connect(this, SIGNAL(clicked(bool)), panel, SLOT(setVisible(bool)));
792                 connect(panel, SIGNAL(visible(bool)), this, SLOT(setChecked(bool)));
793                 ToolbarInfo::item_iterator it = tbinfo->items.begin();
794                 ToolbarInfo::item_iterator const end = tbinfo->items.end();
795                 for (; it != end; ++it)
796                         if (!getStatus(it->func_).unknown())
797                                 panel->addButton(bar_->addItem(*it));
798
799                 QToolButton::mousePressEvent(e);
800         }
801 };
802
803 }
804
805
806 MenuButton::MenuButton(GuiToolbar * bar, ToolbarItem const & item, bool const sticky)
807         : QToolButton(bar), bar_(bar), tbitem_(item), initialized_(false)
808 {
809         setPopupMode(QToolButton::InstantPopup);
810         QString const label = qt_(to_ascii(tbitem_.label_));
811         setToolTip(label);
812         setStatusTip(label);
813         setText(label);
814         setIcon(QIcon(getPixmap("images/math/", toqstr(tbitem_.name_), "png")));
815         if (sticky)
816                 connect(this, SIGNAL(triggered(QAction *)),
817                         this, SLOT(actionTriggered(QAction *)));
818         connect(bar, SIGNAL(iconSizeChanged(QSize)),
819                 this, SLOT(setIconSize(QSize)));
820 }
821
822 void MenuButton::mousePressEvent(QMouseEvent * e)
823 {
824         if (initialized_) {
825                 QToolButton::mousePressEvent(e);
826                 return;
827         }
828
829         initialized_ = true;
830
831         QString const label = qt_(to_ascii(tbitem_.label_));
832         ButtonMenu * m = new ButtonMenu(label, this);
833         m->setWindowTitle(label);
834         m->setTearOffEnabled(true);
835         connect(bar_, SIGNAL(updated()), m, SLOT(updateParent()));
836         ToolbarInfo const * tbinfo = guiApp->toolbars().info(tbitem_.name_);
837         if (!tbinfo) {
838                 LYXERR0("Unknown toolbar " << tbitem_.name_);
839                 return;
840         }
841         ToolbarInfo::item_iterator it = tbinfo->items.begin();
842         ToolbarInfo::item_iterator const end = tbinfo->items.end();
843         for (; it != end; ++it)
844                 if (!getStatus(it->func_).unknown())
845                         m->add(bar_->addItem(*it));
846         setMenu(m);
847
848         QToolButton::mousePressEvent(e);
849 }
850
851
852 void MenuButton::actionTriggered(QAction * action)
853 {
854         QToolButton::setDefaultAction(action);
855         setPopupMode(QToolButton::DelayedPopup);
856 }
857
858
859 void GuiToolbar::add(ToolbarItem const & item)
860 {
861         switch (item.type_) {
862         case ToolbarItem::SEPARATOR:
863                 addSeparator();
864                 break;
865         case ToolbarItem::LAYOUTS:
866                 layout_ = new GuiLayoutBox(this, owner_);
867                 addWidget(layout_);
868                 break;
869         case ToolbarItem::MINIBUFFER:
870                 command_buffer_ = new GuiCommandBuffer(&owner_);
871                 addWidget(command_buffer_);
872                 /// \todo find a Qt4 equivalent to setHorizontalStretchable(true);
873                 //setHorizontalStretchable(true);
874                 break;
875         case ToolbarItem::TABLEINSERT: {
876                 QToolButton * tb = new QToolButton;
877                 tb->setCheckable(true);
878                 tb->setIcon(getIcon(FuncRequest(LFUN_TABULAR_INSERT), true));
879                 QString const label = qt_(to_ascii(item.label_));
880                 tb->setToolTip(label);
881                 tb->setStatusTip(label);
882                 tb->setText(label);
883                 InsertTableWidget * iv = new InsertTableWidget(owner_, tb);
884                 connect(tb, SIGNAL(clicked(bool)), iv, SLOT(show(bool)));
885                 connect(iv, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool)));
886                 connect(this, SIGNAL(updated()), iv, SLOT(updateParent()));
887                 addWidget(tb);
888                 break;
889                 }
890         case ToolbarItem::ICONPALETTE:
891                 addWidget(new PaletteButton(this, item));
892                 break;
893
894         case ToolbarItem::POPUPMENU: {
895                 addWidget(new MenuButton(this, item, false));
896                 break;
897                 }
898         case ToolbarItem::STICKYPOPUPMENU: {
899                 addWidget(new MenuButton(this, item, true));
900                 break;
901                 }
902         case ToolbarItem::COMMAND: {
903                 if (!getStatus(item.func_).unknown())
904                         addAction(addItem(item));
905                 break;
906                 }
907         default:
908                 break;
909         }
910 }
911
912
913 void GuiToolbar::update(bool in_math, bool in_table, bool in_review, 
914         bool in_mathmacrotemplate)
915 {
916         if (visibility_ & Toolbars::AUTO) {
917                 bool show_it = (in_math && (visibility_ & Toolbars::MATH))
918                         || (in_table && (visibility_ & Toolbars::TABLE))
919                         || (in_review && (visibility_ & Toolbars::REVIEW))
920                         || (in_mathmacrotemplate && (visibility_ & Toolbars::MATHMACROTEMPLATE));
921                 setVisible(show_it);
922         }
923
924         // update visible toolbars only
925         if (!isVisible())
926                 return;
927
928         // This is a speed bottleneck because this is called on every keypress
929         // and update calls getStatus, which copies the cursor at least two times
930         for (int i = 0; i < actions_.size(); ++i)
931                 actions_[i]->update();
932
933         if (layout_)
934                 layout_->setEnabled(lyx::getStatus(FuncRequest(LFUN_LAYOUT)).enabled());
935
936         // emit signal
937         updated();
938 }
939
940
941 QString GuiToolbar::sessionKey() const
942 {
943         return "views/" + QString::number(owner_.id()) + "/" + objectName();
944 }
945
946
947 void GuiToolbar::saveSession() const
948 {
949         QSettings settings;
950         settings.setValue(sessionKey() + "/visibility", visibility_);
951 }
952
953
954 void GuiToolbar::restoreSession()
955 {
956         QSettings settings;
957         setVisibility(settings.value(sessionKey() + "/visibility").toInt());
958 }
959
960
961 void GuiToolbar::toggle()
962 {
963         docstring state;
964         if (allowauto_) {
965                 if (!(visibility_ & Toolbars::AUTO)) {
966                         visibility_ |= Toolbars::AUTO;
967                         hide();
968                         state = _("auto");
969                 } else {
970                         visibility_ &= ~Toolbars::AUTO;
971                         if (isVisible()) {
972                                 hide();
973                                 state = _("off");
974                         } else {
975                                 show();
976                                 state = _("on");
977                         }
978                 }
979         } else {
980                 if (isVisible()) {
981                         hide();
982                         state = _("off");
983                 } else {
984                         show();
985                         state = _("on");
986                 }
987         }
988
989         owner_.message(bformat(_("Toolbar \"%1$s\" state set to %2$s"),
990                 qstring_to_ucs4(windowTitle()), state));
991 }
992
993 } // namespace frontend
994 } // namespace lyx
995
996 #include "moc_GuiToolbar.cpp"