]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiView.cpp
Use <cstdint> instead of <boost/cstdint.hpp>
[lyx.git] / src / frontends / qt4 / GuiView.cpp
1 /**
2  * \file GuiView.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 Abdelrazak Younes
9  * \author Peter Kümmel
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "GuiView.h"
17
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiClickableLabel.h"
23 #include "GuiCommandBuffer.h"
24 #include "GuiCompleter.h"
25 #include "GuiKeySymbol.h"
26 #include "GuiToc.h"
27 #include "GuiToolbar.h"
28 #include "GuiWorkArea.h"
29 #include "GuiProgress.h"
30 #include "LayoutBox.h"
31 #include "Menus.h"
32 #include "TocModel.h"
33
34 #include "qt_helpers.h"
35 #include "support/filetools.h"
36
37 #include "frontends/alert.h"
38 #include "frontends/KeySymbol.h"
39
40 #include "buffer_funcs.h"
41 #include "Buffer.h"
42 #include "BufferList.h"
43 #include "BufferParams.h"
44 #include "BufferView.h"
45 #include "Compare.h"
46 #include "Converter.h"
47 #include "Cursor.h"
48 #include "CutAndPaste.h"
49 #include "Encoding.h"
50 #include "ErrorList.h"
51 #include "Format.h"
52 #include "FuncStatus.h"
53 #include "FuncRequest.h"
54 #include "Intl.h"
55 #include "Language.h"
56 #include "Layout.h"
57 #include "LayoutFile.h"
58 #include "Lexer.h"
59 #include "LyXAction.h"
60 #include "LyX.h"
61 #include "LyXRC.h"
62 #include "LyXVC.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
65 #include "Session.h"
66 #include "TexRow.h"
67 #include "TextClass.h"
68 #include "Text.h"
69 #include "Toolbars.h"
70 #include "version.h"
71
72 #include "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/filetools.h"
77 #include "support/gettext.h"
78 #include "support/filetools.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
88
89 #include <QAction>
90 #include <QApplication>
91 #include <QCloseEvent>
92 #include <QDebug>
93 #include <QDesktopWidget>
94 #include <QDragEnterEvent>
95 #include <QDropEvent>
96 #include <QFuture>
97 #include <QFutureWatcher>
98 #include <QLabel>
99 #include <QList>
100 #include <QMenu>
101 #include <QMenuBar>
102 #include <QMimeData>
103 #include <QMovie>
104 #include <QPainter>
105 #include <QPixmap>
106 #include <QPoint>
107 #include <QPushButton>
108 #include <QScrollBar>
109 #include <QSettings>
110 #include <QShowEvent>
111 #include <QSplitter>
112 #include <QStackedWidget>
113 #include <QStatusBar>
114 #include <QSvgRenderer>
115 #include <QtConcurrentRun>
116 #include <QTime>
117 #include <QTimer>
118 #include <QToolBar>
119 #include <QUrl>
120
121
122
123 // sync with GuiAlert.cpp
124 #define EXPORT_in_THREAD 1
125
126
127 #include "support/bind.h"
128
129 #include <sstream>
130
131 #ifdef HAVE_SYS_TIME_H
132 # include <sys/time.h>
133 #endif
134 #ifdef HAVE_UNISTD_H
135 # include <unistd.h>
136 #endif
137
138
139 using namespace std;
140 using namespace lyx::support;
141
142 namespace lyx {
143
144 using support::addExtension;
145 using support::changeExtension;
146 using support::removeExtension;
147
148 namespace frontend {
149
150 namespace {
151
152 class BackgroundWidget : public QWidget
153 {
154 public:
155         BackgroundWidget(int width, int height)
156                 : width_(width), height_(height)
157         {
158                 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
159                 if (!lyxrc.show_banner)
160                         return;
161                 /// The text to be written on top of the pixmap
162                 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
163                 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
164                 /// The text to be written on top of the pixmap
165                 QString const text = lyx_version ?
166                         qt_("version ") + lyx_version : qt_("unknown version");
167 #if QT_VERSION >= 0x050000
168                 QString imagedir = "images/";
169                 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
170                 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
171                 if (svgRenderer.isValid()) {
172                         splash_ = QPixmap(splashSize());
173                         QPainter painter(&splash_);
174                         svgRenderer.render(&painter);
175                         splash_.setDevicePixelRatio(pixelRatio());
176                 } else {
177                         splash_ = getPixmap("images/", "banner", "png");
178                 }
179 #else
180                 splash_ = getPixmap("images/", "banner", "svgz,png");
181 #endif
182
183                 QPainter pain(&splash_);
184                 pain.setPen(QColor(0, 0, 0));
185                 qreal const fsize = fontSize();
186                 bool ok;
187                 int hfsize = 20;
188                 qreal locscale = htextsize.toFloat(&ok);
189                 if (!ok)
190                         locscale = 1.0;
191                 QPointF const position = textPosition(false);
192                 QPointF const hposition = textPosition(true);
193                 QRectF const hrect(hposition, splashSize());
194                 LYXERR(Debug::GUI,
195                         "widget pixel ratio: " << pixelRatio() <<
196                         " splash pixel ratio: " << splashPixelRatio() <<
197                         " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
198                 QFont font;
199                 // The font used to display the version info
200                 font.setStyleHint(QFont::SansSerif);
201                 font.setWeight(QFont::Bold);
202                 font.setPointSizeF(fsize);
203                 pain.setFont(font);
204                 pain.drawText(position, text);
205                 // The font used to display the version info
206                 font.setStyleHint(QFont::SansSerif);
207                 font.setWeight(QFont::Normal);
208                 font.setPointSizeF(hfsize);
209                 // Check how long the logo gets with the current font
210                 // and adapt if the font is running wider than what
211                 // we assume
212                 QFontMetrics fm(font);
213                 // Split the title into lines to measure the longest line
214                 // in the current l7n.
215                 QStringList titlesegs = htext.split('\n');
216                 int wline = 0;
217                 int hline = fm.height();
218                 QStringList::const_iterator sit;
219                 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
220                         if (fm.width(*sit) > wline)
221                                 wline = fm.width(*sit);
222                 }
223                 // The longest line in the reference font (for English)
224                 // is 180. Calculate scale factor from that.
225                 double const wscale = wline > 0 ? (180.0 / wline) : 1;
226                 // Now do the same for the height (necessary for condensed fonts)
227                 double const hscale = (34.0 / hline);
228                 // take the lower of the two scale factors.
229                 double const scale = min(wscale, hscale);
230                 // Now rescale. Also consider l7n's offset factor.
231                 font.setPointSizeF(hfsize * scale * locscale);
232
233                 pain.setFont(font);
234                 pain.drawText(hrect, Qt::AlignLeft, htext);
235                 setFocusPolicy(Qt::StrongFocus);
236         }
237
238         void paintEvent(QPaintEvent *)
239         {
240                 int const w = width_;
241                 int const h = height_;
242                 int const x = (width() - w) / 2;
243                 int const y = (height() - h) / 2;
244                 LYXERR(Debug::GUI,
245                         "widget pixel ratio: " << pixelRatio() <<
246                         " splash pixel ratio: " << splashPixelRatio() <<
247                         " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
248                 QPainter pain(this);
249                 pain.drawPixmap(x, y, w, h, splash_);
250         }
251
252         void keyPressEvent(QKeyEvent * ev)
253         {
254                 KeySymbol sym;
255                 setKeySymbol(&sym, ev);
256                 if (sym.isOK()) {
257                         guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
258                         ev->accept();
259                 } else {
260                         ev->ignore();
261                 }
262         }
263
264 private:
265         QPixmap splash_;
266         int const width_;
267         int const height_;
268
269         /// Current ratio between physical pixels and device-independent pixels
270         double pixelRatio() const {
271 #if QT_VERSION >= 0x050000
272                 return qt_scale_factor * devicePixelRatio();
273 #else
274                 return 1.0;
275 #endif
276         }
277
278         qreal fontSize() const {
279                 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
280         }
281
282         QPointF textPosition(bool const heading) const {
283                 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
284                                : QPointF(width_/2 - 18, height_/2 + 45);
285         }
286
287         QSize splashSize() const {
288                 return QSize(
289                         static_cast<unsigned int>(width_ * pixelRatio()),
290                         static_cast<unsigned int>(height_ * pixelRatio()));
291         }
292
293         /// Ratio between physical pixels and device-independent pixels of splash image
294         double splashPixelRatio() const {
295 #if QT_VERSION >= 0x050000
296                 return splash_.devicePixelRatio();
297 #else
298                 return 1.0;
299 #endif
300         }
301 };
302
303
304 /// Toolbar store providing access to individual toolbars by name.
305 typedef map<string, GuiToolbar *> ToolbarMap;
306
307 typedef shared_ptr<Dialog> DialogPtr;
308
309 } // namespace
310
311
312 class GuiView::GuiViewPrivate
313 {
314         /// noncopyable
315         GuiViewPrivate(GuiViewPrivate const &);
316         void operator=(GuiViewPrivate const &);
317 public:
318         GuiViewPrivate(GuiView * gv)
319                 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
320                 layout_(0), autosave_timeout_(5000),
321                 in_show_(false)
322         {
323                 // hardcode here the platform specific icon size
324                 smallIconSize = 16;  // scaling problems
325                 normalIconSize = 20; // ok, default if iconsize.png is missing
326                 bigIconSize = 26;       // better for some math icons
327                 hugeIconSize = 32;      // better for hires displays
328                 giantIconSize = 48;
329
330                 // if it exists, use width of iconsize.png as normal size
331                 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
332                 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
333                 if (!fn.empty()) {
334                         QImage image(toqstr(fn.absFileName()));
335                         if (image.width() < int(smallIconSize))
336                                 normalIconSize = smallIconSize;
337                         else if (image.width() > int(giantIconSize))
338                                 normalIconSize = giantIconSize;
339                         else
340                                 normalIconSize = image.width();
341                 }
342
343                 splitter_ = new QSplitter;
344                 bg_widget_ = new BackgroundWidget(400, 250);
345                 stack_widget_ = new QStackedWidget;
346                 stack_widget_->addWidget(bg_widget_);
347                 stack_widget_->addWidget(splitter_);
348                 setBackground();
349
350                 // TODO cleanup, remove the singleton, handle multiple Windows?
351                 progress_ = ProgressInterface::instance();
352                 if (!dynamic_cast<GuiProgress*>(progress_)) {
353                         progress_ = new GuiProgress;  // TODO who deletes it
354                         ProgressInterface::setInstance(progress_);
355                 }
356                 QObject::connect(
357                                 dynamic_cast<GuiProgress*>(progress_),
358                                 SIGNAL(updateStatusBarMessage(QString const&)),
359                                 gv, SLOT(updateStatusBarMessage(QString const&)));
360                 QObject::connect(
361                                 dynamic_cast<GuiProgress*>(progress_),
362                                 SIGNAL(clearMessageText()),
363                                 gv, SLOT(clearMessageText()));
364         }
365
366         ~GuiViewPrivate()
367         {
368                 delete splitter_;
369                 delete bg_widget_;
370                 delete stack_widget_;
371         }
372
373         void setBackground()
374         {
375                 stack_widget_->setCurrentWidget(bg_widget_);
376                 bg_widget_->setUpdatesEnabled(true);
377                 bg_widget_->setFocus();
378         }
379
380         int tabWorkAreaCount()
381         {
382                 return splitter_->count();
383         }
384
385         TabWorkArea * tabWorkArea(int i)
386         {
387                 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
388         }
389
390         TabWorkArea * currentTabWorkArea()
391         {
392                 int areas = tabWorkAreaCount();
393                 if (areas == 1)
394                         // The first TabWorkArea is always the first one, if any.
395                         return tabWorkArea(0);
396
397                 for (int i = 0; i != areas;  ++i) {
398                         TabWorkArea * twa = tabWorkArea(i);
399                         if (current_main_work_area_ == twa->currentWorkArea())
400                                 return twa;
401                 }
402
403                 // None has the focus so we just take the first one.
404                 return tabWorkArea(0);
405         }
406
407         int countWorkAreasOf(Buffer & buf)
408         {
409                 int areas = tabWorkAreaCount();
410                 int count = 0;
411                 for (int i = 0; i != areas;  ++i) {
412                         TabWorkArea * twa = tabWorkArea(i);
413                         if (twa->workArea(buf))
414                                 ++count;
415                 }
416                 return count;
417         }
418
419         void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
420         {
421                 if (processing_thread_watcher_.isRunning()) {
422                         // we prefer to cancel this preview in order to keep a snappy
423                         // interface.
424                         return;
425                 }
426                 processing_thread_watcher_.setFuture(f);
427         }
428
429         QSize iconSize(docstring const & icon_size)
430         {
431                 unsigned int size;
432                 if (icon_size == "small")
433                         size = smallIconSize;
434                 else if (icon_size == "normal")
435                         size = normalIconSize;
436                 else if (icon_size == "big")
437                         size = bigIconSize;
438                 else if (icon_size == "huge")
439                         size = hugeIconSize;
440                 else if (icon_size == "giant")
441                         size = giantIconSize;
442                 else
443                         size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
444
445                 if (size < smallIconSize)
446                         size = smallIconSize;
447
448                 return QSize(size, size);
449         }
450
451         QSize iconSize(QString const & icon_size)
452         {
453                 return iconSize(qstring_to_ucs4(icon_size));
454         }
455
456         string & iconSize(QSize const & qsize)
457         {
458                 LATTEST(qsize.width() == qsize.height());
459
460                 static string icon_size;
461
462                 unsigned int size = qsize.width();
463
464                 if (size < smallIconSize)
465                         size = smallIconSize;
466
467                 if (size == smallIconSize)
468                         icon_size = "small";
469                 else if (size == normalIconSize)
470                         icon_size = "normal";
471                 else if (size == bigIconSize)
472                         icon_size = "big";
473                 else if (size == hugeIconSize)
474                         icon_size = "huge";
475                 else if (size == giantIconSize)
476                         icon_size = "giant";
477                 else
478                         icon_size = convert<string>(size);
479
480                 return icon_size;
481         }
482
483 public:
484         GuiView * gv_;
485         GuiWorkArea * current_work_area_;
486         GuiWorkArea * current_main_work_area_;
487         QSplitter * splitter_;
488         QStackedWidget * stack_widget_;
489         BackgroundWidget * bg_widget_;
490         /// view's toolbars
491         ToolbarMap toolbars_;
492         ProgressInterface* progress_;
493         /// The main layout box.
494         /**
495          * \warning Don't Delete! The layout box is actually owned by
496          * whichever toolbar contains it. All the GuiView class needs is a
497          * means of accessing it.
498          *
499          * FIXME: replace that with a proper model so that we are not limited
500          * to only one dialog.
501          */
502         LayoutBox * layout_;
503
504         ///
505         map<string, DialogPtr> dialogs_;
506
507         unsigned int smallIconSize;
508         unsigned int normalIconSize;
509         unsigned int bigIconSize;
510         unsigned int hugeIconSize;
511         unsigned int giantIconSize;
512         ///
513         QTimer statusbar_timer_;
514         /// auto-saving of buffers
515         Timeout autosave_timeout_;
516         /// flag against a race condition due to multiclicks, see bug #1119
517         bool in_show_;
518
519         ///
520         TocModels toc_models_;
521
522         ///
523         QFutureWatcher<docstring> autosave_watcher_;
524         QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
525         ///
526         string last_export_format;
527         string processing_format;
528
529         static QSet<Buffer const *> busyBuffers;
530         static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
531                         Buffer * buffer, string const & format);
532         static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
533                         Buffer * buffer, string const & format);
534         static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
535                         Buffer * buffer, string const & format);
536         static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
537
538         template<class T>
539         static Buffer::ExportStatus runAndDestroy(const T& func,
540                         Buffer const * orig, Buffer * buffer, string const & format);
541
542         // TODO syncFunc/previewFunc: use bind
543         bool asyncBufferProcessing(string const & argument,
544                                    Buffer const * used_buffer,
545                                    docstring const & msg,
546                                    Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
547                                    Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
548                                    Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
549                                    bool allow_async);
550
551         QVector<GuiWorkArea*> guiWorkAreas();
552 };
553
554 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
555
556
557 GuiView::GuiView(int id)
558         : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
559           command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
560           devel_mode_(false)
561 {
562         connect(this, SIGNAL(bufferViewChanged()),
563                 this, SLOT(onBufferViewChanged()));
564
565         // GuiToolbars *must* be initialised before the menu bar.
566         setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
567         constructToolbars();
568
569         // set ourself as the current view. This is needed for the menu bar
570         // filling, at least for the static special menu item on Mac. Otherwise
571         // they are greyed out.
572         guiApp->setCurrentView(this);
573
574         // Fill up the menu bar.
575         guiApp->menus().fillMenuBar(menuBar(), this, true);
576
577         setCentralWidget(d.stack_widget_);
578
579         // Start autosave timer
580         if (lyxrc.autosave) {
581                 // The connection is closed when this is destroyed.
582                 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
583                 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
584                 d.autosave_timeout_.start();
585         }
586         connect(&d.statusbar_timer_, SIGNAL(timeout()),
587                 this, SLOT(clearMessage()));
588
589         // We don't want to keep the window in memory if it is closed.
590         setAttribute(Qt::WA_DeleteOnClose, true);
591
592 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
593         // QIcon::fromTheme was introduced in Qt 4.6
594 #if (QT_VERSION >= 0x040600)
595         // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
596         // since the icon is provided in the application bundle. We use a themed
597         // version when available and use the bundled one as fallback.
598         setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
599 #else
600         setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
601 #endif
602
603 #endif
604         resetWindowTitle();
605
606         // use tabbed dock area for multiple docks
607         // (such as "source" and "messages")
608         setDockOptions(QMainWindow::ForceTabbedDocks);
609
610         // For Drag&Drop.
611         setAcceptDrops(true);
612
613         // add busy indicator to statusbar
614         GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
615         statusBar()->addPermanentWidget(busylabel);
616         search_mode mode = theGuiApp()->imageSearchMode();
617         QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
618         QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
619         busylabel->setMovie(busyanim);
620         busyanim->start();
621         busylabel->hide();
622
623         connect(&d.processing_thread_watcher_, SIGNAL(started()),
624                 busylabel, SLOT(show()));
625         connect(&d.processing_thread_watcher_, SIGNAL(finished()),
626                 busylabel, SLOT(hide()));
627         connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
628
629         QFontMetrics const fm(statusBar()->fontMetrics());
630         int const iconheight = max(int(d.normalIconSize), fm.height());
631         QSize const iconsize(iconheight, iconheight);
632
633         QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
634         shell_escape_ = new QLabel(statusBar());
635         shell_escape_->setPixmap(shellescape);
636         shell_escape_->setScaledContents(true);
637         shell_escape_->setAlignment(Qt::AlignCenter);
638         shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
639         shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
640                                       "external commands for this document. "
641                                       "Right click to change."));
642         SEMenu * menu = new SEMenu(this);
643         connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
644                 menu, SLOT(showMenu(QPoint)));
645         shell_escape_->hide();
646         statusBar()->addPermanentWidget(shell_escape_);
647
648         QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
649         read_only_ = new QLabel(statusBar());
650         read_only_->setPixmap(readonly);
651         read_only_->setScaledContents(true);
652         read_only_->setAlignment(Qt::AlignCenter);
653         read_only_->hide();
654         statusBar()->addPermanentWidget(read_only_);
655
656         version_control_ = new QLabel(statusBar());
657         version_control_->setAlignment(Qt::AlignCenter);
658         version_control_->setFrameStyle(QFrame::StyledPanel);
659         version_control_->hide();
660         statusBar()->addPermanentWidget(version_control_);
661
662         statusBar()->setSizeGripEnabled(true);
663         updateStatusBar();
664
665         connect(&d.autosave_watcher_, SIGNAL(finished()), this,
666                 SLOT(autoSaveThreadFinished()));
667
668         connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
669                 SLOT(processingThreadStarted()));
670         connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
671                 SLOT(processingThreadFinished()));
672
673         connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
674                 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
675
676         // set custom application bars context menu, e.g. tool bar and menu bar
677         setContextMenuPolicy(Qt::CustomContextMenu);
678         connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
679                 SLOT(toolBarPopup(const QPoint &)));
680
681         // Forbid too small unresizable window because it can happen
682         // with some window manager under X11.
683         setMinimumSize(300, 200);
684
685         if (lyxrc.allow_geometry_session) {
686                 // Now take care of session management.
687                 if (restoreLayout())
688                         return;
689         }
690
691         // no session handling, default to a sane size.
692         setGeometry(50, 50, 690, 510);
693         initToolbars();
694
695         // clear session data if any.
696         QSettings settings;
697         settings.remove("views");
698 }
699
700
701 GuiView::~GuiView()
702 {
703         delete &d;
704 }
705
706
707 void GuiView::disableShellEscape()
708 {
709         BufferView * bv = documentBufferView();
710         if (!bv)
711                 return;
712         theSession().shellescapeFiles().remove(bv->buffer().absFileName());
713         bv->buffer().params().shell_escape = false;
714         bv->processUpdateFlags(Update::Force);
715 }
716
717
718 void GuiView::checkCancelBackground()
719 {
720         docstring const ttl = _("Cancel Export?");
721         docstring const msg = _("Do you want to cancel the background export process?");
722         int const ret =
723                 Alert::prompt(ttl, msg, 1, 1,
724                         _("&Cancel export"), _("Co&ntinue"));
725         if (ret == 0)
726                 Systemcall::killscript();
727 }
728
729
730 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
731 {
732         QVector<GuiWorkArea*> areas;
733         for (int i = 0; i < tabWorkAreaCount(); i++) {
734                 TabWorkArea* ta = tabWorkArea(i);
735                 for (int u = 0; u < ta->count(); u++) {
736                         areas << ta->workArea(u);
737                 }
738         }
739         return areas;
740 }
741
742 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
743         string const & format)
744 {
745         docstring const fmt = theFormats().prettyName(format);
746         docstring msg;
747         switch (status) {
748         case Buffer::ExportSuccess:
749                 msg = bformat(_("Successful export to format: %1$s"), fmt);
750                 break;
751         case Buffer::ExportCancel:
752                 msg = _("Document export cancelled.");
753                 break;
754         case Buffer::ExportError:
755         case Buffer::ExportNoPathToFormat:
756         case Buffer::ExportTexPathHasSpaces:
757         case Buffer::ExportConverterError:
758                 msg = bformat(_("Error while exporting format: %1$s"), fmt);
759                 break;
760         case Buffer::PreviewSuccess:
761                 msg = bformat(_("Successful preview of format: %1$s"), fmt);
762                 break;
763         case Buffer::PreviewError:
764                 msg = bformat(_("Error while previewing format: %1$s"), fmt);
765                 break;
766         case Buffer::ExportKilled:
767                 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
768                 break;
769         }
770         view->message(msg);
771 }
772
773
774 void GuiView::processingThreadStarted()
775 {
776 }
777
778
779 void GuiView::processingThreadFinished()
780 {
781         QFutureWatcher<Buffer::ExportStatus> const * watcher =
782                 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
783
784         Buffer::ExportStatus const status = watcher->result();
785         handleExportStatus(this, status, d.processing_format);
786
787         updateToolbars();
788         BufferView const * const bv = currentBufferView();
789         if (bv && !bv->buffer().errorList("Export").empty()) {
790                 errors("Export");
791                 return;
792         }
793
794         bool const error = (status != Buffer::ExportSuccess &&
795                             status != Buffer::PreviewSuccess &&
796                             status != Buffer::ExportCancel);
797         if (error && bv) {
798                 ErrorList & el = bv->buffer().errorList(d.last_export_format);
799                 // at this point, we do not know if buffer-view or
800                 // master-buffer-view was called. If there was an export error,
801                 // and the current buffer's error log is empty, we guess that
802                 // it must be master-buffer-view that was called so we set
803                 // from_master=true.
804                 errors(d.last_export_format, el.empty());
805         }
806 }
807
808
809 void GuiView::autoSaveThreadFinished()
810 {
811         QFutureWatcher<docstring> const * watcher =
812                 static_cast<QFutureWatcher<docstring> const *>(sender());
813         message(watcher->result());
814         updateToolbars();
815 }
816
817
818 void GuiView::saveLayout() const
819 {
820         QSettings settings;
821         settings.setValue("zoom_ratio", zoom_ratio_);
822         settings.setValue("devel_mode", devel_mode_);
823         settings.beginGroup("views");
824         settings.beginGroup(QString::number(id_));
825 #if defined(Q_WS_X11) || defined(QPA_XCB)
826         settings.setValue("pos", pos());
827         settings.setValue("size", size());
828 #else
829         settings.setValue("geometry", saveGeometry());
830 #endif
831         settings.setValue("layout", saveState(0));
832         settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
833 }
834
835
836 void GuiView::saveUISettings() const
837 {
838         QSettings settings;
839
840         // Save the toolbar private states
841         ToolbarMap::iterator end = d.toolbars_.end();
842         for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
843                 it->second->saveSession(settings);
844         // Now take care of all other dialogs
845         map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
846         for (; it!= d.dialogs_.end(); ++it)
847                 it->second->saveSession(settings);
848 }
849
850
851 bool GuiView::restoreLayout()
852 {
853         QSettings settings;
854         zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
855         // Actual zoom value: default zoom + fractional offset
856         int zoom = lyxrc.defaultZoom * zoom_ratio_;
857         if (zoom < static_cast<int>(zoom_min_))
858                 zoom = zoom_min_;
859         lyxrc.currentZoom = zoom;
860         devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
861         settings.beginGroup("views");
862         settings.beginGroup(QString::number(id_));
863         QString const icon_key = "icon_size";
864         if (!settings.contains(icon_key))
865                 return false;
866
867         //code below is skipped when when ~/.config/LyX is (re)created
868         setIconSize(d.iconSize(settings.value(icon_key).toString()));
869
870 #if defined(Q_WS_X11) || defined(QPA_XCB)
871         QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
872         QSize size = settings.value("size", QSize(690, 510)).toSize();
873         resize(size);
874         move(pos);
875 #else
876         // Work-around for bug #6034: the window ends up in an undetermined
877         // state when trying to restore a maximized window when it is
878         // already maximized.
879         if (!(windowState() & Qt::WindowMaximized))
880                 if (!restoreGeometry(settings.value("geometry").toByteArray()))
881                         setGeometry(50, 50, 690, 510);
882 #endif
883         // Make sure layout is correctly oriented.
884         setLayoutDirection(qApp->layoutDirection());
885
886         // Allow the toc and view-source dock widget to be restored if needed.
887         Dialog * dialog;
888         if ((dialog = findOrBuild("toc", true)))
889                 // see bug 5082. At least setup title and enabled state.
890                 // Visibility will be adjusted by restoreState below.
891                 dialog->prepareView();
892         if ((dialog = findOrBuild("view-source", true)))
893                 dialog->prepareView();
894         if ((dialog = findOrBuild("progress", true)))
895                 dialog->prepareView();
896
897         if (!restoreState(settings.value("layout").toByteArray(), 0))
898                 initToolbars();
899
900         // init the toolbars that have not been restored
901         Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
902         Toolbars::Infos::iterator end = guiApp->toolbars().end();
903         for (; cit != end; ++cit) {
904                 GuiToolbar * tb = toolbar(cit->name);
905                 if (tb && !tb->isRestored())
906                         initToolbar(cit->name);
907         }
908
909         // update lock (all) toolbars positions
910         updateLockToolbars();
911
912         updateDialogs();
913         return true;
914 }
915
916
917 GuiToolbar * GuiView::toolbar(string const & name)
918 {
919         ToolbarMap::iterator it = d.toolbars_.find(name);
920         if (it != d.toolbars_.end())
921                 return it->second;
922
923         LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
924         return 0;
925 }
926
927
928 void GuiView::updateLockToolbars()
929 {
930         toolbarsMovable_ = false;
931         for (ToolbarInfo const & info : guiApp->toolbars()) {
932                 GuiToolbar * tb = toolbar(info.name);
933                 if (tb && tb->isMovable())
934                         toolbarsMovable_ = true;
935         }
936 }
937
938
939 void GuiView::constructToolbars()
940 {
941         ToolbarMap::iterator it = d.toolbars_.begin();
942         for (; it != d.toolbars_.end(); ++it)
943                 delete it->second;
944         d.toolbars_.clear();
945
946         // I don't like doing this here, but the standard toolbar
947         // destroys this object when it's destroyed itself (vfr)
948         d.layout_ = new LayoutBox(*this);
949         d.stack_widget_->addWidget(d.layout_);
950         d.layout_->move(0,0);
951
952         // extracts the toolbars from the backend
953         Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
954         Toolbars::Infos::iterator end = guiApp->toolbars().end();
955         for (; cit != end; ++cit)
956                 d.toolbars_[cit->name] =  new GuiToolbar(*cit, *this);
957 }
958
959
960 void GuiView::initToolbars()
961 {
962         // extracts the toolbars from the backend
963         Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
964         Toolbars::Infos::iterator end = guiApp->toolbars().end();
965         for (; cit != end; ++cit)
966                 initToolbar(cit->name);
967 }
968
969
970 void GuiView::initToolbar(string const & name)
971 {
972         GuiToolbar * tb = toolbar(name);
973         if (!tb)
974                 return;
975         int const visibility = guiApp->toolbars().defaultVisibility(name);
976         bool newline = !(visibility & Toolbars::SAMEROW);
977         tb->setVisible(false);
978         tb->setVisibility(visibility);
979
980         if (visibility & Toolbars::TOP) {
981                 if (newline)
982                         addToolBarBreak(Qt::TopToolBarArea);
983                 addToolBar(Qt::TopToolBarArea, tb);
984         }
985
986         if (visibility & Toolbars::BOTTOM) {
987                 if (newline)
988                         addToolBarBreak(Qt::BottomToolBarArea);
989                 addToolBar(Qt::BottomToolBarArea, tb);
990         }
991
992         if (visibility & Toolbars::LEFT) {
993                 if (newline)
994                         addToolBarBreak(Qt::LeftToolBarArea);
995                 addToolBar(Qt::LeftToolBarArea, tb);
996         }
997
998         if (visibility & Toolbars::RIGHT) {
999                 if (newline)
1000                         addToolBarBreak(Qt::RightToolBarArea);
1001                 addToolBar(Qt::RightToolBarArea, tb);
1002         }
1003
1004         if (visibility & Toolbars::ON)
1005                 tb->setVisible(true);
1006
1007         tb->setMovable(true);
1008 }
1009
1010
1011 TocModels & GuiView::tocModels()
1012 {
1013         return d.toc_models_;
1014 }
1015
1016
1017 void GuiView::setFocus()
1018 {
1019         LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1020         QMainWindow::setFocus();
1021 }
1022
1023
1024 bool GuiView::hasFocus() const
1025 {
1026         if (currentWorkArea())
1027                 return currentWorkArea()->hasFocus();
1028         if (currentMainWorkArea())
1029                 return currentMainWorkArea()->hasFocus();
1030         return d.bg_widget_->hasFocus();
1031 }
1032
1033
1034 void GuiView::focusInEvent(QFocusEvent * e)
1035 {
1036         LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1037         QMainWindow::focusInEvent(e);
1038         // Make sure guiApp points to the correct view.
1039         guiApp->setCurrentView(this);
1040         if (currentWorkArea())
1041                 currentWorkArea()->setFocus();
1042         else if (currentMainWorkArea())
1043                 currentMainWorkArea()->setFocus();
1044         else
1045                 d.bg_widget_->setFocus();
1046 }
1047
1048
1049 void GuiView::showEvent(QShowEvent * e)
1050 {
1051         LYXERR(Debug::GUI, "Passed Geometry "
1052                 << size().height() << "x" << size().width()
1053                 << "+" << pos().x() << "+" << pos().y());
1054
1055         if (d.splitter_->count() == 0)
1056                 // No work area, switch to the background widget.
1057                 d.setBackground();
1058
1059         updateToolbars();
1060         QMainWindow::showEvent(e);
1061 }
1062
1063
1064 bool GuiView::closeScheduled()
1065 {
1066         closing_ = true;
1067         return close();
1068 }
1069
1070
1071 bool GuiView::prepareAllBuffersForLogout()
1072 {
1073         Buffer * first = theBufferList().first();
1074         if (!first)
1075                 return true;
1076
1077         // First, iterate over all buffers and ask the users if unsaved
1078         // changes should be saved.
1079         // We cannot use a for loop as the buffer list cycles.
1080         Buffer * b = first;
1081         do {
1082                 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1083                         return false;
1084                 b = theBufferList().next(b);
1085         } while (b != first);
1086
1087         // Next, save session state
1088         // When a view/window was closed before without quitting LyX, there
1089         // are already entries in the lastOpened list.
1090         theSession().lastOpened().clear();
1091         writeSession();
1092
1093         return true;
1094 }
1095
1096
1097 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1098  ** is responsibility of the container (e.g., dialog)
1099  **/
1100 void GuiView::closeEvent(QCloseEvent * close_event)
1101 {
1102         LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1103
1104         if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1105                 Alert::warning(_("Exit LyX"),
1106                         _("LyX could not be closed because documents are being processed by LyX."));
1107                 close_event->setAccepted(false);
1108                 return;
1109         }
1110
1111         // If the user pressed the x (so we didn't call closeView
1112         // programmatically), we want to clear all existing entries.
1113         if (!closing_)
1114                 theSession().lastOpened().clear();
1115         closing_ = true;
1116
1117         writeSession();
1118
1119         // it can happen that this event arrives without selecting the view,
1120         // e.g. when clicking the close button on a background window.
1121         setFocus();
1122         if (!closeWorkAreaAll()) {
1123                 closing_ = false;
1124                 close_event->ignore();
1125                 return;
1126         }
1127
1128         // Make sure that nothing will use this to be closed View.
1129         guiApp->unregisterView(this);
1130
1131         if (isFullScreen()) {
1132                 // Switch off fullscreen before closing.
1133                 toggleFullScreen();
1134                 updateDialogs();
1135         }
1136
1137         // Make sure the timer time out will not trigger a statusbar update.
1138         d.statusbar_timer_.stop();
1139
1140         // Saving fullscreen requires additional tweaks in the toolbar code.
1141         // It wouldn't also work under linux natively.
1142         if (lyxrc.allow_geometry_session) {
1143                 saveLayout();
1144                 saveUISettings();
1145         }
1146
1147         close_event->accept();
1148 }
1149
1150
1151 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1152 {
1153         if (event->mimeData()->hasUrls())
1154                 event->accept();
1155         /// \todo Ask lyx-devel is this is enough:
1156         /// if (event->mimeData()->hasFormat("text/plain"))
1157         ///     event->acceptProposedAction();
1158 }
1159
1160
1161 void GuiView::dropEvent(QDropEvent * event)
1162 {
1163         QList<QUrl> files = event->mimeData()->urls();
1164         if (files.isEmpty())
1165                 return;
1166
1167         LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1168         for (int i = 0; i != files.size(); ++i) {
1169                 string const file = os::internal_path(fromqstr(
1170                         files.at(i).toLocalFile()));
1171                 if (file.empty())
1172                         continue;
1173
1174                 string const ext = support::getExtension(file);
1175                 vector<const Format *> found_formats;
1176
1177                 // Find all formats that have the correct extension.
1178                 vector<const Format *> const & import_formats
1179                         = theConverters().importableFormats();
1180                 vector<const Format *>::const_iterator it = import_formats.begin();
1181                 for (; it != import_formats.end(); ++it)
1182                         if ((*it)->hasExtension(ext))
1183                                 found_formats.push_back(*it);
1184
1185                 FuncRequest cmd;
1186                 if (found_formats.size() >= 1) {
1187                         if (found_formats.size() > 1) {
1188                                 //FIXME: show a dialog to choose the correct importable format
1189                                 LYXERR(Debug::FILES,
1190                                         "Multiple importable formats found, selecting first");
1191                         }
1192                         string const arg = found_formats[0]->name() + " " + file;
1193                         cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1194                 }
1195                 else {
1196                         //FIXME: do we have to explicitly check whether it's a lyx file?
1197                         LYXERR(Debug::FILES,
1198                                 "No formats found, trying to open it as a lyx file");
1199                         cmd = FuncRequest(LFUN_FILE_OPEN, file);
1200                 }
1201                 // add the functions to the queue
1202                 guiApp->addToFuncRequestQueue(cmd);
1203                 event->accept();
1204         }
1205         // now process the collected functions. We perform the events
1206         // asynchronously. This prevents potential problems in case the
1207         // BufferView is closed within an event.
1208         guiApp->processFuncRequestQueueAsync();
1209 }
1210
1211
1212 void GuiView::message(docstring const & str)
1213 {
1214         if (ForkedProcess::iAmAChild())
1215                 return;
1216
1217         // call is moved to GUI-thread by GuiProgress
1218         d.progress_->appendMessage(toqstr(str));
1219 }
1220
1221
1222 void GuiView::clearMessageText()
1223 {
1224         message(docstring());
1225 }
1226
1227
1228 void GuiView::updateStatusBarMessage(QString const & str)
1229 {
1230         statusBar()->showMessage(str);
1231         d.statusbar_timer_.stop();
1232         d.statusbar_timer_.start(3000);
1233 }
1234
1235
1236 void GuiView::clearMessage()
1237 {
1238         // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1239         // the hasFocus function mostly returns false, even if the focus is on
1240         // a workarea in this view.
1241         //if (!hasFocus())
1242         //      return;
1243         showMessage();
1244         d.statusbar_timer_.stop();
1245 }
1246
1247
1248 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1249 {
1250         if (wa != d.current_work_area_
1251                 || wa->bufferView().buffer().isInternal())
1252                 return;
1253         Buffer const & buf = wa->bufferView().buffer();
1254         // Set the windows title
1255         docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1256         if (buf.notifiesExternalModification()) {
1257                 title = bformat(_("%1$s (modified externally)"), title);
1258                 // If the external modification status has changed, then maybe the status of
1259                 // buffer-save has changed too.
1260                 updateToolbars();
1261         }
1262 #ifndef Q_WS_MAC
1263         title += from_ascii(" - LyX");
1264 #endif
1265         setWindowTitle(toqstr(title));
1266         // Sets the path for the window: this is used by OSX to
1267         // allow a context click on the title bar showing a menu
1268         // with the path up to the file
1269         setWindowFilePath(toqstr(buf.absFileName()));
1270         // Tell Qt whether the current document is changed
1271         setWindowModified(!buf.isClean());
1272
1273         if (buf.params().shell_escape)
1274                 shell_escape_->show();
1275         else
1276                 shell_escape_->hide();
1277
1278         if (buf.hasReadonlyFlag())
1279                 read_only_->show();
1280         else
1281                 read_only_->hide();
1282
1283         if (buf.lyxvc().inUse()) {
1284                 version_control_->show();
1285                 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1286         } else
1287                 version_control_->hide();
1288 }
1289
1290
1291 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1292 {
1293         if (d.current_work_area_)
1294                 // disconnect the current work area from all slots
1295                 QObject::disconnect(d.current_work_area_, 0, this, 0);
1296         disconnectBuffer();
1297         disconnectBufferView();
1298         connectBufferView(wa->bufferView());
1299         connectBuffer(wa->bufferView().buffer());
1300         d.current_work_area_ = wa;
1301         QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1302                          this, SLOT(updateWindowTitle(GuiWorkArea *)));
1303         QObject::connect(wa, SIGNAL(busy(bool)),
1304                          this, SLOT(setBusy(bool)));
1305         // connection of a signal to a signal
1306         QObject::connect(wa, SIGNAL(bufferViewChanged()),
1307                          this, SIGNAL(bufferViewChanged()));
1308         Q_EMIT updateWindowTitle(wa);
1309         Q_EMIT bufferViewChanged();
1310 }
1311
1312
1313 void GuiView::onBufferViewChanged()
1314 {
1315         structureChanged();
1316         // Buffer-dependent dialogs must be updated. This is done here because
1317         // some dialogs require buffer()->text.
1318         updateDialogs();
1319 }
1320
1321
1322 void GuiView::on_lastWorkAreaRemoved()
1323 {
1324         if (closing_)
1325                 // We already are in a close event. Nothing more to do.
1326                 return;
1327
1328         if (d.splitter_->count() > 1)
1329                 // We have a splitter so don't close anything.
1330                 return;
1331
1332         // Reset and updates the dialogs.
1333         Q_EMIT bufferViewChanged();
1334
1335         resetWindowTitle();
1336         updateStatusBar();
1337
1338         if (lyxrc.open_buffers_in_tabs)
1339                 // Nothing more to do, the window should stay open.
1340                 return;
1341
1342         if (guiApp->viewIds().size() > 1) {
1343                 close();
1344                 return;
1345         }
1346
1347 #ifdef Q_OS_MAC
1348         // On Mac we also close the last window because the application stay
1349         // resident in memory. On other platforms we don't close the last
1350         // window because this would quit the application.
1351         close();
1352 #endif
1353 }
1354
1355
1356 void GuiView::updateStatusBar()
1357 {
1358         // let the user see the explicit message
1359         if (d.statusbar_timer_.isActive())
1360                 return;
1361
1362         showMessage();
1363 }
1364
1365
1366 void GuiView::showMessage()
1367 {
1368         if (busy_)
1369                 return;
1370         QString msg = toqstr(theGuiApp()->viewStatusMessage());
1371         if (msg.isEmpty()) {
1372                 BufferView const * bv = currentBufferView();
1373                 if (bv)
1374                         msg = toqstr(bv->cursor().currentState(devel_mode_));
1375                 else
1376                         msg = qt_("Welcome to LyX!");
1377         }
1378         statusBar()->showMessage(msg);
1379 }
1380
1381
1382 bool GuiView::event(QEvent * e)
1383 {
1384         switch (e->type())
1385         {
1386         // Useful debug code:
1387         //case QEvent::ActivationChange:
1388         //case QEvent::WindowDeactivate:
1389         //case QEvent::Paint:
1390         //case QEvent::Enter:
1391         //case QEvent::Leave:
1392         //case QEvent::HoverEnter:
1393         //case QEvent::HoverLeave:
1394         //case QEvent::HoverMove:
1395         //case QEvent::StatusTip:
1396         //case QEvent::DragEnter:
1397         //case QEvent::DragLeave:
1398         //case QEvent::Drop:
1399         //      break;
1400
1401         case QEvent::WindowActivate: {
1402                 GuiView * old_view = guiApp->currentView();
1403                 if (this == old_view) {
1404                         setFocus();
1405                         return QMainWindow::event(e);
1406                 }
1407                 if (old_view && old_view->currentBufferView()) {
1408                         // save current selection to the selection buffer to allow
1409                         // middle-button paste in this window.
1410                         cap::saveSelection(old_view->currentBufferView()->cursor());
1411                 }
1412                 guiApp->setCurrentView(this);
1413                 if (d.current_work_area_)
1414                         on_currentWorkAreaChanged(d.current_work_area_);
1415                 else
1416                         resetWindowTitle();
1417                 setFocus();
1418                 return QMainWindow::event(e);
1419         }
1420
1421         case QEvent::ShortcutOverride: {
1422                 // See bug 4888
1423                 if (isFullScreen() && menuBar()->isHidden()) {
1424                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1425                         // FIXME: we should also try to detect special LyX shortcut such as
1426                         // Alt-P and Alt-M. Right now there is a hack in
1427                         // GuiWorkArea::processKeySym() that hides again the menubar for
1428                         // those cases.
1429                         if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1430                                 menuBar()->show();
1431                                 return QMainWindow::event(e);
1432                         }
1433                 }
1434                 return QMainWindow::event(e);
1435         }
1436
1437         default:
1438                 return QMainWindow::event(e);
1439         }
1440 }
1441
1442 void GuiView::resetWindowTitle()
1443 {
1444         setWindowTitle(qt_("LyX"));
1445 }
1446
1447 bool GuiView::focusNextPrevChild(bool /*next*/)
1448 {
1449         setFocus();
1450         return true;
1451 }
1452
1453
1454 bool GuiView::busy() const
1455 {
1456         return busy_ > 0;
1457 }
1458
1459
1460 void GuiView::setBusy(bool busy)
1461 {
1462         bool const busy_before = busy_ > 0;
1463         busy ? ++busy_ : --busy_;
1464         if ((busy_ > 0) == busy_before)
1465                 // busy state didn't change
1466                 return;
1467
1468         if (busy) {
1469                 QApplication::setOverrideCursor(Qt::WaitCursor);
1470                 return;
1471         }
1472         QApplication::restoreOverrideCursor();
1473         updateLayoutList();
1474 }
1475
1476
1477 void GuiView::resetCommandExecute()
1478 {
1479         command_execute_ = false;
1480         updateToolbars();
1481 }
1482
1483
1484 double GuiView::pixelRatio() const
1485 {
1486 #if QT_VERSION >= 0x050000
1487         return qt_scale_factor * devicePixelRatio();
1488 #else
1489         return 1.0;
1490 #endif
1491 }
1492
1493
1494 GuiWorkArea * GuiView::workArea(int index)
1495 {
1496         if (TabWorkArea * twa = d.currentTabWorkArea())
1497                 if (index < twa->count())
1498                         return twa->workArea(index);
1499         return 0;
1500 }
1501
1502
1503 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1504 {
1505         if (currentWorkArea()
1506                 && &currentWorkArea()->bufferView().buffer() == &buffer)
1507                 return currentWorkArea();
1508         if (TabWorkArea * twa = d.currentTabWorkArea())
1509                 return twa->workArea(buffer);
1510         return 0;
1511 }
1512
1513
1514 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1515 {
1516         // Automatically create a TabWorkArea if there are none yet.
1517         TabWorkArea * tab_widget = d.splitter_->count()
1518                 ? d.currentTabWorkArea() : addTabWorkArea();
1519         return tab_widget->addWorkArea(buffer, *this);
1520 }
1521
1522
1523 TabWorkArea * GuiView::addTabWorkArea()
1524 {
1525         TabWorkArea * twa = new TabWorkArea;
1526         QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1527                 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1528         QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1529                          this, SLOT(on_lastWorkAreaRemoved()));
1530
1531         d.splitter_->addWidget(twa);
1532         d.stack_widget_->setCurrentWidget(d.splitter_);
1533         return twa;
1534 }
1535
1536
1537 GuiWorkArea const * GuiView::currentWorkArea() const
1538 {
1539         return d.current_work_area_;
1540 }
1541
1542
1543 GuiWorkArea * GuiView::currentWorkArea()
1544 {
1545         return d.current_work_area_;
1546 }
1547
1548
1549 GuiWorkArea const * GuiView::currentMainWorkArea() const
1550 {
1551         if (!d.currentTabWorkArea())
1552                 return 0;
1553         return d.currentTabWorkArea()->currentWorkArea();
1554 }
1555
1556
1557 GuiWorkArea * GuiView::currentMainWorkArea()
1558 {
1559         if (!d.currentTabWorkArea())
1560                 return 0;
1561         return d.currentTabWorkArea()->currentWorkArea();
1562 }
1563
1564
1565 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1566 {
1567         LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1568         if (!wa) {
1569                 d.current_work_area_ = 0;
1570                 d.setBackground();
1571                 Q_EMIT bufferViewChanged();
1572                 return;
1573         }
1574
1575         // FIXME: I've no clue why this is here and why it accesses
1576         //  theGuiApp()->currentView, which might be 0 (bug 6464).
1577         //  See also 27525 (vfr).
1578         if (theGuiApp()->currentView() == this
1579                   && theGuiApp()->currentView()->currentWorkArea() == wa)
1580                 return;
1581
1582         if (currentBufferView())
1583                 cap::saveSelection(currentBufferView()->cursor());
1584
1585         theGuiApp()->setCurrentView(this);
1586         d.current_work_area_ = wa;
1587
1588         // We need to reset this now, because it will need to be
1589         // right if the tabWorkArea gets reset in the for loop. We
1590         // will change it back if we aren't in that case.
1591         GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1592         d.current_main_work_area_ = wa;
1593
1594         for (int i = 0; i != d.splitter_->count(); ++i) {
1595                 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1596                         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1597                                 << ", Current main wa: " << currentMainWorkArea());
1598                         return;
1599                 }
1600         }
1601
1602         d.current_main_work_area_ = old_cmwa;
1603
1604         LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1605         on_currentWorkAreaChanged(wa);
1606         BufferView & bv = wa->bufferView();
1607         bv.cursor().fixIfBroken();
1608         bv.updateMetrics();
1609         wa->setUpdatesEnabled(true);
1610         LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1611 }
1612
1613
1614 void GuiView::removeWorkArea(GuiWorkArea * wa)
1615 {
1616         LASSERT(wa, return);
1617         if (wa == d.current_work_area_) {
1618                 disconnectBuffer();
1619                 disconnectBufferView();
1620                 d.current_work_area_ = 0;
1621                 d.current_main_work_area_ = 0;
1622         }
1623
1624         bool found_twa = false;
1625         for (int i = 0; i != d.splitter_->count(); ++i) {
1626                 TabWorkArea * twa = d.tabWorkArea(i);
1627                 if (twa->removeWorkArea(wa)) {
1628                         // Found in this tab group, and deleted the GuiWorkArea.
1629                         found_twa = true;
1630                         if (twa->count() != 0) {
1631                                 if (d.current_work_area_ == 0)
1632                                         // This means that we are closing the current GuiWorkArea, so
1633                                         // switch to the next GuiWorkArea in the found TabWorkArea.
1634                                         setCurrentWorkArea(twa->currentWorkArea());
1635                         } else {
1636                                 // No more WorkAreas in this tab group, so delete it.
1637                                 delete twa;
1638                         }
1639                         break;
1640                 }
1641         }
1642
1643         // It is not a tabbed work area (i.e., the search work area), so it
1644         // should be deleted by other means.
1645         LASSERT(found_twa, return);
1646
1647         if (d.current_work_area_ == 0) {
1648                 if (d.splitter_->count() != 0) {
1649                         TabWorkArea * twa = d.currentTabWorkArea();
1650                         setCurrentWorkArea(twa->currentWorkArea());
1651                 } else {
1652                         // No more work areas, switch to the background widget.
1653                         setCurrentWorkArea(0);
1654                 }
1655         }
1656 }
1657
1658
1659 LayoutBox * GuiView::getLayoutDialog() const
1660 {
1661         return d.layout_;
1662 }
1663
1664
1665 void GuiView::updateLayoutList()
1666 {
1667         if (d.layout_)
1668                 d.layout_->updateContents(false);
1669 }
1670
1671
1672 void GuiView::updateToolbars()
1673 {
1674         ToolbarMap::iterator end = d.toolbars_.end();
1675         if (d.current_work_area_) {
1676                 int context = 0;
1677                 if (d.current_work_area_->bufferView().cursor().inMathed()
1678                         && !d.current_work_area_->bufferView().cursor().inRegexped())
1679                         context |= Toolbars::MATH;
1680                 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1681                         context |= Toolbars::TABLE;
1682                 if (currentBufferView()->buffer().areChangesPresent()
1683                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1684                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1685                     || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1686                         && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1687                         context |= Toolbars::REVIEW;
1688                 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1689                         context |= Toolbars::MATHMACROTEMPLATE;
1690                 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1691                         context |= Toolbars::IPA;
1692                 if (command_execute_)
1693                         context |= Toolbars::MINIBUFFER;
1694                 if (minibuffer_focus_) {
1695                         context |= Toolbars::MINIBUFFER_FOCUS;
1696                         minibuffer_focus_ = false;
1697                 }
1698
1699                 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1700                         it->second->update(context);
1701         } else
1702                 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1703                         it->second->update();
1704 }
1705
1706
1707 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1708 {
1709         LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1710         LASSERT(newBuffer, return);
1711
1712         GuiWorkArea * wa = workArea(*newBuffer);
1713         if (wa == 0) {
1714                 setBusy(true);
1715                 newBuffer->masterBuffer()->updateBuffer();
1716                 setBusy(false);
1717                 wa = addWorkArea(*newBuffer);
1718                 // scroll to the position when the BufferView was last closed
1719                 if (lyxrc.use_lastfilepos) {
1720                         LastFilePosSection::FilePos filepos =
1721                                 theSession().lastFilePos().load(newBuffer->fileName());
1722                         wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1723                 }
1724         } else {
1725                 //Disconnect the old buffer...there's no new one.
1726                 disconnectBuffer();
1727         }
1728         connectBuffer(*newBuffer);
1729         connectBufferView(wa->bufferView());
1730         if (switch_to)
1731                 setCurrentWorkArea(wa);
1732 }
1733
1734
1735 void GuiView::connectBuffer(Buffer & buf)
1736 {
1737         buf.setGuiDelegate(this);
1738 }
1739
1740
1741 void GuiView::disconnectBuffer()
1742 {
1743         if (d.current_work_area_)
1744                 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1745 }
1746
1747
1748 void GuiView::connectBufferView(BufferView & bv)
1749 {
1750         bv.setGuiDelegate(this);
1751 }
1752
1753
1754 void GuiView::disconnectBufferView()
1755 {
1756         if (d.current_work_area_)
1757                 d.current_work_area_->bufferView().setGuiDelegate(0);
1758 }
1759
1760
1761 void GuiView::errors(string const & error_type, bool from_master)
1762 {
1763         BufferView const * const bv = currentBufferView();
1764         if (!bv)
1765                 return;
1766
1767         ErrorList const & el = from_master ?
1768                 bv->buffer().masterBuffer()->errorList(error_type) :
1769                 bv->buffer().errorList(error_type);
1770
1771         if (el.empty())
1772                 return;
1773
1774         string err = error_type;
1775         if (from_master)
1776                 err = "from_master|" + error_type;
1777         showDialog("errorlist", err);
1778 }
1779
1780
1781 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1782 {
1783         d.toc_models_.updateItem(toqstr(type), dit);
1784 }
1785
1786
1787 void GuiView::structureChanged()
1788 {
1789         // This is called from the Buffer, which has no way to ensure that cursors
1790         // in BufferView remain valid.
1791         if (documentBufferView())
1792                 documentBufferView()->cursor().sanitize();
1793         // FIXME: This is slightly expensive, though less than the tocBackend update
1794         // (#9880). This also resets the view in the Toc Widget (#6675).
1795         d.toc_models_.reset(documentBufferView());
1796         // Navigator needs more than a simple update in this case. It needs to be
1797         // rebuilt.
1798         updateDialog("toc", "");
1799 }
1800
1801
1802 void GuiView::updateDialog(string const & name, string const & sdata)
1803 {
1804         if (!isDialogVisible(name))
1805                 return;
1806
1807         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1808         if (it == d.dialogs_.end())
1809                 return;
1810
1811         Dialog * const dialog = it->second.get();
1812         if (dialog->isVisibleView())
1813                 dialog->initialiseParams(sdata);
1814 }
1815
1816
1817 BufferView * GuiView::documentBufferView()
1818 {
1819         return currentMainWorkArea()
1820                 ? &currentMainWorkArea()->bufferView()
1821                 : 0;
1822 }
1823
1824
1825 BufferView const * GuiView::documentBufferView() const
1826 {
1827         return currentMainWorkArea()
1828                 ? &currentMainWorkArea()->bufferView()
1829                 : 0;
1830 }
1831
1832
1833 BufferView * GuiView::currentBufferView()
1834 {
1835         return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1836 }
1837
1838
1839 BufferView const * GuiView::currentBufferView() const
1840 {
1841         return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1842 }
1843
1844
1845 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1846         Buffer const * orig, Buffer * clone)
1847 {
1848         bool const success = clone->autoSave();
1849         delete clone;
1850         busyBuffers.remove(orig);
1851         return success
1852                 ? _("Automatic save done.")
1853                 : _("Automatic save failed!");
1854 }
1855
1856
1857 void GuiView::autoSave()
1858 {
1859         LYXERR(Debug::INFO, "Running autoSave()");
1860
1861         Buffer * buffer = documentBufferView()
1862                 ? &documentBufferView()->buffer() : 0;
1863         if (!buffer) {
1864                 resetAutosaveTimers();
1865                 return;
1866         }
1867
1868         GuiViewPrivate::busyBuffers.insert(buffer);
1869         QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1870                 buffer, buffer->cloneBufferOnly());
1871         d.autosave_watcher_.setFuture(f);
1872         resetAutosaveTimers();
1873 }
1874
1875
1876 void GuiView::resetAutosaveTimers()
1877 {
1878         if (lyxrc.autosave)
1879                 d.autosave_timeout_.restart();
1880 }
1881
1882
1883 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1884 {
1885         bool enable = true;
1886         Buffer * buf = currentBufferView()
1887                 ? &currentBufferView()->buffer() : 0;
1888         Buffer * doc_buffer = documentBufferView()
1889                 ? &(documentBufferView()->buffer()) : 0;
1890
1891 #ifdef Q_OS_MAC
1892         /* In LyX/Mac, when a dialog is open, the menus of the
1893            application can still be accessed without giving focus to
1894            the main window. In this case, we want to disable the menu
1895            entries that are buffer-related.
1896            This code must not be used on Linux and Windows, since it
1897            would disable buffer-related entries when hovering over the
1898            menu (see bug #9574).
1899          */
1900         if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1901                 buf = 0;
1902                 doc_buffer = 0;
1903         }
1904 #endif
1905
1906         // Check whether we need a buffer
1907         if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1908                 // no, exit directly
1909                 flag.message(from_utf8(N_("Command not allowed with"
1910                                         "out any document open")));
1911                 flag.setEnabled(false);
1912                 return true;
1913         }
1914
1915         if (cmd.origin() == FuncRequest::TOC) {
1916                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1917                 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1918                         flag.setEnabled(false);
1919                 return true;
1920         }
1921
1922         switch(cmd.action()) {
1923         case LFUN_BUFFER_IMPORT:
1924                 break;
1925
1926         case LFUN_MASTER_BUFFER_EXPORT:
1927                 enable = doc_buffer
1928                         && (doc_buffer->parent() != 0
1929                             || doc_buffer->hasChildren())
1930                         && !d.processing_thread_watcher_.isRunning()
1931                         // this launches a dialog, which would be in the wrong Buffer
1932                         && !(::lyx::operator==(cmd.argument(), "custom"));
1933                 break;
1934
1935         case LFUN_MASTER_BUFFER_UPDATE:
1936         case LFUN_MASTER_BUFFER_VIEW:
1937                 enable = doc_buffer
1938                         && (doc_buffer->parent() != 0
1939                             || doc_buffer->hasChildren())
1940                         && !d.processing_thread_watcher_.isRunning();
1941                 break;
1942
1943         case LFUN_BUFFER_UPDATE:
1944         case LFUN_BUFFER_VIEW: {
1945                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1946                         enable = false;
1947                         break;
1948                 }
1949                 string format = to_utf8(cmd.argument());
1950                 if (cmd.argument().empty())
1951                         format = doc_buffer->params().getDefaultOutputFormat();
1952                 enable = doc_buffer->params().isExportable(format, true);
1953                 break;
1954         }
1955
1956         case LFUN_BUFFER_RELOAD:
1957                 enable = doc_buffer && !doc_buffer->isUnnamed()
1958                         && doc_buffer->fileName().exists()
1959                         && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1960                 break;
1961
1962         case LFUN_BUFFER_CHILD_OPEN:
1963                 enable = doc_buffer != 0;
1964                 break;
1965
1966         case LFUN_BUFFER_WRITE:
1967                 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1968                 break;
1969
1970         //FIXME: This LFUN should be moved to GuiApplication.
1971         case LFUN_BUFFER_WRITE_ALL: {
1972                 // We enable the command only if there are some modified buffers
1973                 Buffer * first = theBufferList().first();
1974                 enable = false;
1975                 if (!first)
1976                         break;
1977                 Buffer * b = first;
1978                 // We cannot use a for loop as the buffer list is a cycle.
1979                 do {
1980                         if (!b->isClean()) {
1981                                 enable = true;
1982                                 break;
1983                         }
1984                         b = theBufferList().next(b);
1985                 } while (b != first);
1986                 break;
1987         }
1988
1989         case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1990                 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1991                 break;
1992
1993         case LFUN_BUFFER_EXPORT: {
1994                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1995                         enable = false;
1996                         break;
1997                 }
1998                 return doc_buffer->getStatus(cmd, flag);
1999                 break;
2000         }
2001
2002         case LFUN_BUFFER_EXPORT_AS:
2003                 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2004                         enable = false;
2005                         break;
2006                 }
2007                 // fall through
2008         case LFUN_BUFFER_WRITE_AS:
2009         case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2010                 enable = doc_buffer != 0;
2011                 break;
2012
2013         case LFUN_EXPORT_CANCEL:
2014                 enable = d.processing_thread_watcher_.isRunning();
2015                 break;
2016
2017         case LFUN_BUFFER_CLOSE:
2018         case LFUN_VIEW_CLOSE:
2019                 enable = doc_buffer != 0;
2020                 break;
2021
2022         case LFUN_BUFFER_CLOSE_ALL:
2023                 enable = theBufferList().last() != theBufferList().first();
2024                 break;
2025
2026         case LFUN_BUFFER_CHKTEX: {
2027                 // hide if we have no checktex command
2028                 if (lyxrc.chktex_command.empty()) {
2029                         flag.setUnknown(true);
2030                         enable = false;
2031                         break;
2032                 }
2033                 if (!doc_buffer || !doc_buffer->params().isLatex()
2034                     || d.processing_thread_watcher_.isRunning()) {
2035                         // grey out, don't hide
2036                         enable = false;
2037                         break;
2038                 }
2039                 enable = true;
2040                 break;
2041         }
2042
2043         case LFUN_VIEW_SPLIT:
2044                 if (cmd.getArg(0) == "vertical")
2045                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2046                                          d.splitter_->orientation() == Qt::Vertical);
2047                 else
2048                         enable = doc_buffer && (d.splitter_->count() == 1 ||
2049                                          d.splitter_->orientation() == Qt::Horizontal);
2050                 break;
2051
2052         case LFUN_TAB_GROUP_CLOSE:
2053                 enable = d.tabWorkAreaCount() > 1;
2054                 break;
2055
2056         case LFUN_DEVEL_MODE_TOGGLE:
2057                 flag.setOnOff(devel_mode_);
2058                 break;
2059
2060         case LFUN_TOOLBAR_TOGGLE: {
2061                 string const name = cmd.getArg(0);
2062                 if (GuiToolbar * t = toolbar(name))
2063                         flag.setOnOff(t->isVisible());
2064                 else {
2065                         enable = false;
2066                         docstring const msg =
2067                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2068                         flag.message(msg);
2069                 }
2070                 break;
2071         }
2072
2073         case LFUN_TOOLBAR_MOVABLE: {
2074                 string const name = cmd.getArg(0);
2075                 // use negation since locked == !movable
2076                 if (name == "*")
2077                         // toolbar name * locks all toolbars
2078                         flag.setOnOff(!toolbarsMovable_);
2079                 else if (GuiToolbar * t = toolbar(name))
2080                         flag.setOnOff(!(t->isMovable()));
2081                 else {
2082                         enable = false;
2083                         docstring const msg =
2084                                 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2085                         flag.message(msg);
2086                 }
2087                 break;
2088         }
2089
2090         case LFUN_ICON_SIZE:
2091                 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2092                 break;
2093
2094         case LFUN_DROP_LAYOUTS_CHOICE:
2095                 enable = buf != 0;
2096                 break;
2097
2098         case LFUN_UI_TOGGLE:
2099                 flag.setOnOff(isFullScreen());
2100                 break;
2101
2102         case LFUN_DIALOG_DISCONNECT_INSET:
2103                 break;
2104
2105         case LFUN_DIALOG_HIDE:
2106                 // FIXME: should we check if the dialog is shown?
2107                 break;
2108
2109         case LFUN_DIALOG_TOGGLE:
2110                 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2111                 // to set "enable"
2112                 // fall through
2113         case LFUN_DIALOG_SHOW: {
2114                 string const name = cmd.getArg(0);
2115                 if (!doc_buffer)
2116                         enable = name == "aboutlyx"
2117                                 || name == "file" //FIXME: should be removed.
2118                                 || name == "lyxfiles"
2119                                 || name == "prefs"
2120                                 || name == "texinfo"
2121                                 || name == "progress"
2122                                 || name == "compare";
2123                 else if (name == "character" || name == "symbols"
2124                         || name == "mathdelimiter" || name == "mathmatrix") {
2125                         if (!buf || buf->isReadonly())
2126                                 enable = false;
2127                         else {
2128                                 Cursor const & cur = currentBufferView()->cursor();
2129                                 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2130                         }
2131                 }
2132                 else if (name == "latexlog")
2133                         enable = FileName(doc_buffer->logName()).isReadableFile();
2134                 else if (name == "spellchecker")
2135                         enable = theSpellChecker()
2136                                 && !doc_buffer->isReadonly()
2137                                 && !doc_buffer->text().empty();
2138                 else if (name == "vclog")
2139                         enable = doc_buffer->lyxvc().inUse();
2140                 break;
2141         }
2142
2143         case LFUN_DIALOG_UPDATE: {
2144                 string const name = cmd.getArg(0);
2145                 if (!buf)
2146                         enable = name == "prefs";
2147                 break;
2148         }
2149
2150         case LFUN_COMMAND_EXECUTE:
2151         case LFUN_MESSAGE:
2152         case LFUN_MENU_OPEN:
2153                 // Nothing to check.
2154                 break;
2155
2156         case LFUN_COMPLETION_INLINE:
2157                 if (!d.current_work_area_
2158                         || !d.current_work_area_->completer().inlinePossible(
2159                         currentBufferView()->cursor()))
2160                         enable = false;
2161                 break;
2162
2163         case LFUN_COMPLETION_POPUP:
2164                 if (!d.current_work_area_
2165                         || !d.current_work_area_->completer().popupPossible(
2166                         currentBufferView()->cursor()))
2167                         enable = false;
2168                 break;
2169
2170         case LFUN_COMPLETE:
2171                 if (!d.current_work_area_
2172                         || !d.current_work_area_->completer().inlinePossible(
2173                         currentBufferView()->cursor()))
2174                         enable = false;
2175                 break;
2176
2177         case LFUN_COMPLETION_ACCEPT:
2178                 if (!d.current_work_area_
2179                         || (!d.current_work_area_->completer().popupVisible()
2180                         && !d.current_work_area_->completer().inlineVisible()
2181                         && !d.current_work_area_->completer().completionAvailable()))
2182                         enable = false;
2183                 break;
2184
2185         case LFUN_COMPLETION_CANCEL:
2186                 if (!d.current_work_area_
2187                         || (!d.current_work_area_->completer().popupVisible()
2188                         && !d.current_work_area_->completer().inlineVisible()))
2189                         enable = false;
2190                 break;
2191
2192         case LFUN_BUFFER_ZOOM_OUT:
2193         case LFUN_BUFFER_ZOOM_IN: {
2194                 // only diff between these two is that the default for ZOOM_OUT
2195                 // is a neg. number
2196                 bool const neg_zoom =
2197                         convert<int>(cmd.argument()) < 0 ||
2198                         (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2199                 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2200                         docstring const msg =
2201                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2202                         flag.message(msg);
2203                         enable = false;
2204                 } else
2205                         enable = doc_buffer;
2206                 break;
2207         }
2208
2209         case LFUN_BUFFER_ZOOM: {
2210                 bool const less_than_min_zoom =
2211                         !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2212                 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2213                         docstring const msg =
2214                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2215                         flag.message(msg);
2216                         enable = false;
2217                 }
2218                 else
2219                         enable = doc_buffer;
2220                 break;
2221         }
2222
2223         case LFUN_BUFFER_MOVE_NEXT:
2224         case LFUN_BUFFER_MOVE_PREVIOUS:
2225                 // we do not cycle when moving
2226         case LFUN_BUFFER_NEXT:
2227         case LFUN_BUFFER_PREVIOUS:
2228                 // because we cycle, it doesn't matter whether on first or last
2229                 enable = (d.currentTabWorkArea()->count() > 1);
2230                 break;
2231         case LFUN_BUFFER_SWITCH:
2232                 // toggle on the current buffer, but do not toggle off
2233                 // the other ones (is that a good idea?)
2234                 if (doc_buffer
2235                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2236                         flag.setOnOff(true);
2237                 break;
2238
2239         case LFUN_VC_REGISTER:
2240                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2241                 break;
2242         case LFUN_VC_RENAME:
2243                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2244                 break;
2245         case LFUN_VC_COPY:
2246                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2247                 break;
2248         case LFUN_VC_CHECK_IN:
2249                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2250                 break;
2251         case LFUN_VC_CHECK_OUT:
2252                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2253                 break;
2254         case LFUN_VC_LOCKING_TOGGLE:
2255                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2256                         && doc_buffer->lyxvc().lockingToggleEnabled();
2257                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2258                 break;
2259         case LFUN_VC_REVERT:
2260                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2261                         && !doc_buffer->hasReadonlyFlag();
2262                 break;
2263         case LFUN_VC_UNDO_LAST:
2264                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2265                 break;
2266         case LFUN_VC_REPO_UPDATE:
2267                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2268                 break;
2269         case LFUN_VC_COMMAND: {
2270                 if (cmd.argument().empty())
2271                         enable = false;
2272                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2273                         enable = false;
2274                 break;
2275         }
2276         case LFUN_VC_COMPARE:
2277                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2278                 break;
2279
2280         case LFUN_SERVER_GOTO_FILE_ROW:
2281         case LFUN_LYX_ACTIVATE:
2282                 break;
2283         case LFUN_FORWARD_SEARCH:
2284                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2285                 break;
2286
2287         case LFUN_FILE_INSERT_PLAINTEXT:
2288         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2289                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2290                 break;
2291
2292         case LFUN_SPELLING_CONTINUOUSLY:
2293                 flag.setOnOff(lyxrc.spellcheck_continuously);
2294                 break;
2295
2296         default:
2297                 return false;
2298         }
2299
2300         if (!enable)
2301                 flag.setEnabled(false);
2302
2303         return true;
2304 }
2305
2306
2307 static FileName selectTemplateFile()
2308 {
2309         FileDialog dlg(qt_("Select template file"));
2310         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2311         dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2312
2313         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2314                                  QStringList(qt_("LyX Documents (*.lyx)")));
2315
2316         if (result.first == FileDialog::Later)
2317                 return FileName();
2318         if (result.second.isEmpty())
2319                 return FileName();
2320         return FileName(fromqstr(result.second));
2321 }
2322
2323
2324 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2325 {
2326         setBusy(true);
2327
2328         Buffer * newBuffer = 0;
2329         try {
2330                 newBuffer = checkAndLoadLyXFile(filename);
2331         } catch (ExceptionMessage const & e) {
2332                 setBusy(false);
2333                 throw(e);
2334         }
2335         setBusy(false);
2336
2337         if (!newBuffer) {
2338                 message(_("Document not loaded."));
2339                 return 0;
2340         }
2341
2342         setBuffer(newBuffer);
2343         newBuffer->errors("Parse");
2344
2345         if (tolastfiles) {
2346                 theSession().lastFiles().add(filename);
2347                 theSession().writeFile();
2348   }
2349
2350         return newBuffer;
2351 }
2352
2353
2354 void GuiView::openDocument(string const & fname)
2355 {
2356         string initpath = lyxrc.document_path;
2357
2358         if (documentBufferView()) {
2359                 string const trypath = documentBufferView()->buffer().filePath();
2360                 // If directory is writeable, use this as default.
2361                 if (FileName(trypath).isDirWritable())
2362                         initpath = trypath;
2363         }
2364
2365         string filename;
2366
2367         if (fname.empty()) {
2368                 FileDialog dlg(qt_("Select document to open"));
2369                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2370                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2371
2372                 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2373                 FileDialog::Result result =
2374                         dlg.open(toqstr(initpath), filter);
2375
2376                 if (result.first == FileDialog::Later)
2377                         return;
2378
2379                 filename = fromqstr(result.second);
2380
2381                 // check selected filename
2382                 if (filename.empty()) {
2383                         message(_("Canceled."));
2384                         return;
2385                 }
2386         } else
2387                 filename = fname;
2388
2389         // get absolute path of file and add ".lyx" to the filename if
2390         // necessary.
2391         FileName const fullname =
2392                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2393         if (!fullname.empty())
2394                 filename = fullname.absFileName();
2395
2396         if (!fullname.onlyPath().isDirectory()) {
2397                 Alert::warning(_("Invalid filename"),
2398                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2399                                 from_utf8(fullname.absFileName())));
2400                 return;
2401         }
2402
2403         // if the file doesn't exist and isn't already open (bug 6645),
2404         // let the user create one
2405         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2406             !LyXVC::file_not_found_hook(fullname)) {
2407                 // the user specifically chose this name. Believe him.
2408                 Buffer * const b = newFile(filename, string(), true);
2409                 if (b)
2410                         setBuffer(b);
2411                 return;
2412         }
2413
2414         docstring const disp_fn = makeDisplayPath(filename);
2415         message(bformat(_("Opening document %1$s..."), disp_fn));
2416
2417         docstring str2;
2418         Buffer * buf = loadDocument(fullname);
2419         if (buf) {
2420                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2421                 if (buf->lyxvc().inUse())
2422                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2423                                 " " + _("Version control detected.");
2424         } else {
2425                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2426         }
2427         message(str2);
2428 }
2429
2430 // FIXME: clean that
2431 static bool import(GuiView * lv, FileName const & filename,
2432         string const & format, ErrorList & errorList)
2433 {
2434         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2435
2436         string loader_format;
2437         vector<string> loaders = theConverters().loaders();
2438         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2439                 vector<string>::const_iterator it = loaders.begin();
2440                 vector<string>::const_iterator en = loaders.end();
2441                 for (; it != en; ++it) {
2442                         if (!theConverters().isReachable(format, *it))
2443                                 continue;
2444
2445                         string const tofile =
2446                                 support::changeExtension(filename.absFileName(),
2447                                 theFormats().extension(*it));
2448                         if (theConverters().convert(0, filename, FileName(tofile),
2449                                 filename, format, *it, errorList) != Converters::SUCCESS)
2450                                 return false;
2451                         loader_format = *it;
2452                         break;
2453                 }
2454                 if (loader_format.empty()) {
2455                         frontend::Alert::error(_("Couldn't import file"),
2456                                          bformat(_("No information for importing the format %1$s."),
2457                                          theFormats().prettyName(format)));
2458                         return false;
2459                 }
2460         } else
2461                 loader_format = format;
2462
2463         if (loader_format == "lyx") {
2464                 Buffer * buf = lv->loadDocument(lyxfile);
2465                 if (!buf)
2466                         return false;
2467         } else {
2468                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2469                 if (!b)
2470                         return false;
2471                 lv->setBuffer(b);
2472                 bool as_paragraphs = loader_format == "textparagraph";
2473                 string filename2 = (loader_format == format) ? filename.absFileName()
2474                         : support::changeExtension(filename.absFileName(),
2475                                           theFormats().extension(loader_format));
2476                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2477                         as_paragraphs);
2478                 guiApp->setCurrentView(lv);
2479                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2480         }
2481
2482         return true;
2483 }
2484
2485
2486 void GuiView::importDocument(string const & argument)
2487 {
2488         string format;
2489         string filename = split(argument, format, ' ');
2490
2491         LYXERR(Debug::INFO, format << " file: " << filename);
2492
2493         // need user interaction
2494         if (filename.empty()) {
2495                 string initpath = lyxrc.document_path;
2496                 if (documentBufferView()) {
2497                         string const trypath = documentBufferView()->buffer().filePath();
2498                         // If directory is writeable, use this as default.
2499                         if (FileName(trypath).isDirWritable())
2500                                 initpath = trypath;
2501                 }
2502
2503                 docstring const text = bformat(_("Select %1$s file to import"),
2504                         theFormats().prettyName(format));
2505
2506                 FileDialog dlg(toqstr(text));
2507                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2508                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2509
2510                 docstring filter = theFormats().prettyName(format);
2511                 filter += " (*.{";
2512                 // FIXME UNICODE
2513                 filter += from_utf8(theFormats().extensions(format));
2514                 filter += "})";
2515
2516                 FileDialog::Result result =
2517                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2518
2519                 if (result.first == FileDialog::Later)
2520                         return;
2521
2522                 filename = fromqstr(result.second);
2523
2524                 // check selected filename
2525                 if (filename.empty())
2526                         message(_("Canceled."));
2527         }
2528
2529         if (filename.empty())
2530                 return;
2531
2532         // get absolute path of file
2533         FileName const fullname(support::makeAbsPath(filename));
2534
2535         // Can happen if the user entered a path into the dialog
2536         // (see bug #7437)
2537         if (fullname.onlyFileName().empty()) {
2538                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2539                                           "Aborting import."),
2540                                         from_utf8(fullname.absFileName()));
2541                 frontend::Alert::error(_("File name error"), msg);
2542                 message(_("Canceled."));
2543                 return;
2544         }
2545
2546
2547         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2548
2549         // Check if the document already is open
2550         Buffer * buf = theBufferList().getBuffer(lyxfile);
2551         if (buf) {
2552                 setBuffer(buf);
2553                 if (!closeBuffer()) {
2554                         message(_("Canceled."));
2555                         return;
2556                 }
2557         }
2558
2559         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2560
2561         // if the file exists already, and we didn't do
2562         // -i lyx thefile.lyx, warn
2563         if (lyxfile.exists() && fullname != lyxfile) {
2564
2565                 docstring text = bformat(_("The document %1$s already exists.\n\n"
2566                         "Do you want to overwrite that document?"), displaypath);
2567                 int const ret = Alert::prompt(_("Overwrite document?"),
2568                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
2569
2570                 if (ret == 1) {
2571                         message(_("Canceled."));
2572                         return;
2573                 }
2574         }
2575
2576         message(bformat(_("Importing %1$s..."), displaypath));
2577         ErrorList errorList;
2578         if (import(this, fullname, format, errorList))
2579                 message(_("imported."));
2580         else
2581                 message(_("file not imported!"));
2582
2583         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2584 }
2585
2586
2587 void GuiView::newDocument(string const & filename, string templatefile,
2588                           bool from_template)
2589 {
2590         FileName initpath(lyxrc.document_path);
2591         if (documentBufferView()) {
2592                 FileName const trypath(documentBufferView()->buffer().filePath());
2593                 // If directory is writeable, use this as default.
2594                 if (trypath.isDirWritable())
2595                         initpath = trypath;
2596         }
2597
2598         if (from_template) {
2599                 if (templatefile.empty())
2600                         templatefile =  selectTemplateFile().absFileName();
2601                 if (templatefile.empty())
2602                         return;
2603         }
2604
2605         Buffer * b;
2606         if (filename.empty())
2607                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2608         else
2609                 b = newFile(filename, templatefile, true);
2610
2611         if (b)
2612                 setBuffer(b);
2613
2614         // If no new document could be created, it is unsure
2615         // whether there is a valid BufferView.
2616         if (currentBufferView())
2617                 // Ensure the cursor is correctly positioned on screen.
2618                 currentBufferView()->showCursor();
2619 }
2620
2621
2622 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2623 {
2624         BufferView * bv = documentBufferView();
2625         if (!bv)
2626                 return;
2627
2628         // FIXME UNICODE
2629         FileName filename(to_utf8(fname));
2630         if (filename.empty()) {
2631                 // Launch a file browser
2632                 // FIXME UNICODE
2633                 string initpath = lyxrc.document_path;
2634                 string const trypath = bv->buffer().filePath();
2635                 // If directory is writeable, use this as default.
2636                 if (FileName(trypath).isDirWritable())
2637                         initpath = trypath;
2638
2639                 // FIXME UNICODE
2640                 FileDialog dlg(qt_("Select LyX document to insert"));
2641                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2642                 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2643
2644                 FileDialog::Result result = dlg.open(toqstr(initpath),
2645                                          QStringList(qt_("LyX Documents (*.lyx)")));
2646
2647                 if (result.first == FileDialog::Later)
2648                         return;
2649
2650                 // FIXME UNICODE
2651                 filename.set(fromqstr(result.second));
2652
2653                 // check selected filename
2654                 if (filename.empty()) {
2655                         // emit message signal.
2656                         message(_("Canceled."));
2657                         return;
2658                 }
2659         }
2660
2661         bv->insertLyXFile(filename, ignorelang);
2662         bv->buffer().errors("Parse");
2663 }
2664
2665
2666 string const GuiView::getTemplatesPath(Buffer & b)
2667 {
2668         // We start off with the user's templates path
2669         string result = addPath(package().user_support().absFileName(), "templates");
2670         // Check for the document language
2671         string const langcode = b.params().language->code();
2672         string const shortcode = langcode.substr(0, 2);
2673         if (!langcode.empty() && shortcode != "en") {
2674                 string subpath = addPath(result, shortcode);
2675                 string subpath_long = addPath(result, langcode);
2676                 // If we have a subdirectory for the language already,
2677                 // navigate there
2678                 FileName sp = FileName(subpath);
2679                 if (sp.isDirectory())
2680                         result = subpath;
2681                 else if (FileName(subpath_long).isDirectory())
2682                         result = subpath_long;
2683                 else {
2684                         // Ask whether we should create such a subdirectory
2685                         docstring const text =
2686                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2687                                           "appropriate to the document language (%1$s).\n"
2688                                           "This subdirectory does not exists yet.\n"
2689                                           "Do you want to create it?"),
2690                                         _(b.params().language->display()));
2691                         if (Alert::prompt(_("Create Language Directory?"),
2692                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2693                                 // If the user agreed, we try to create it and report if this failed.
2694                                 if (!sp.createDirectory(0777))
2695                                         Alert::error(_("Subdirectory creation failed!"),
2696                                                      _("Could not create subdirectory.\n"
2697                                                        "The template will be saved in the parent directory."));
2698                                 else
2699                                         result = subpath;
2700                         }
2701                 }
2702         }
2703         // Do we have a layout category?
2704         string const cat = b.params().baseClass() ?
2705                                 b.params().baseClass()->category()
2706                               : string();
2707         if (!cat.empty()) {
2708                 string subpath = addPath(result, cat);
2709                 // If we have a subdirectory for the category already,
2710                 // navigate there
2711                 FileName sp = FileName(subpath);
2712                 if (sp.isDirectory())
2713                         result = subpath;
2714                 else {
2715                         // Ask whether we should create such a subdirectory
2716                         docstring const text =
2717                                 bformat(_("It is suggested to save the template in a subdirectory\n"
2718                                           "appropriate to the layout category (%1$s).\n"
2719                                           "This subdirectory does not exists yet.\n"
2720                                           "Do you want to create it?"),
2721                                         _(cat));
2722                         if (Alert::prompt(_("Create Category Directory?"),
2723                                           text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2724                                 // If the user agreed, we try to create it and report if this failed.
2725                                 if (!sp.createDirectory(0777))
2726                                         Alert::error(_("Subdirectory creation failed!"),
2727                                                      _("Could not create subdirectory.\n"
2728                                                        "The template will be saved in the parent directory."));
2729                                 else
2730                                         result = subpath;
2731                         }
2732                 }
2733         }
2734         return result;
2735 }
2736
2737
2738 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2739 {
2740         FileName fname = b.fileName();
2741         FileName const oldname = fname;
2742         bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2743
2744         if (!newname.empty()) {
2745                 // FIXME UNICODE
2746                 if (as_template)
2747                         fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2748                 else
2749                         fname = support::makeAbsPath(to_utf8(newname),
2750                                                      oldname.onlyPath().absFileName());
2751         } else {
2752                 // Switch to this Buffer.
2753                 setBuffer(&b);
2754
2755                 // No argument? Ask user through dialog.
2756                 // FIXME UNICODE
2757                 QString const title = as_template ? qt_("Choose a filename to save template as")
2758                                                   : qt_("Choose a filename to save document as");
2759                 FileDialog dlg(title);
2760                 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2761                 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2762
2763                 if (!isLyXFileName(fname.absFileName()))
2764                         fname.changeExtension(".lyx");
2765
2766                 string const path = as_template ?
2767                                         getTemplatesPath(b)
2768                                       : fname.onlyPath().absFileName();
2769                 FileDialog::Result result =
2770                         dlg.save(toqstr(path),
2771                                    QStringList(qt_("LyX Documents (*.lyx)")),
2772                                          toqstr(fname.onlyFileName()));
2773
2774                 if (result.first == FileDialog::Later)
2775                         return false;
2776
2777                 fname.set(fromqstr(result.second));
2778
2779                 if (fname.empty())
2780                         return false;
2781
2782                 if (!isLyXFileName(fname.absFileName()))
2783                         fname.changeExtension(".lyx");
2784         }
2785
2786         // fname is now the new Buffer location.
2787
2788         // if there is already a Buffer open with this name, we do not want
2789         // to have another one. (the second test makes sure we're not just
2790         // trying to overwrite ourselves, which is fine.)
2791         if (theBufferList().exists(fname) && fname != oldname
2792                   && theBufferList().getBuffer(fname) != &b) {
2793                 docstring const text =
2794                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
2795                             "Please close it before attempting to overwrite it.\n"
2796                             "Do you want to choose a new filename?"),
2797                                 from_utf8(fname.absFileName()));
2798                 int const ret = Alert::prompt(_("Chosen File Already Open"),
2799                         text, 0, 1, _("&Rename"), _("&Cancel"));
2800                 switch (ret) {
2801                 case 0: return renameBuffer(b, docstring(), kind);
2802                 case 1: return false;
2803                 }
2804                 //return false;
2805         }
2806
2807         bool const existsLocal = fname.exists();
2808         bool const existsInVC = LyXVC::fileInVC(fname);
2809         if (existsLocal || existsInVC) {
2810                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2811                 if (kind != LV_WRITE_AS && existsInVC) {
2812                         // renaming to a name that is already in VC
2813                         // would not work
2814                         docstring text = bformat(_("The document %1$s "
2815                                         "is already registered.\n\n"
2816                                         "Do you want to choose a new name?"),
2817                                 file);
2818                         docstring const title = (kind == LV_VC_RENAME) ?
2819                                 _("Rename document?") : _("Copy document?");
2820                         docstring const button = (kind == LV_VC_RENAME) ?
2821                                 _("&Rename") : _("&Copy");
2822                         int const ret = Alert::prompt(title, text, 0, 1,
2823                                 button, _("&Cancel"));
2824                         switch (ret) {
2825                         case 0: return renameBuffer(b, docstring(), kind);
2826                         case 1: return false;
2827                         }
2828                 }
2829
2830                 if (existsLocal) {
2831                         docstring text = bformat(_("The document %1$s "
2832                                         "already exists.\n\n"
2833                                         "Do you want to overwrite that document?"),
2834                                 file);
2835                         int const ret = Alert::prompt(_("Overwrite document?"),
2836                                         text, 0, 2, _("&Overwrite"),
2837                                         _("&Rename"), _("&Cancel"));
2838                         switch (ret) {
2839                         case 0: break;
2840                         case 1: return renameBuffer(b, docstring(), kind);
2841                         case 2: return false;
2842                         }
2843                 }
2844         }
2845
2846         switch (kind) {
2847         case LV_VC_RENAME: {
2848                 string msg = b.lyxvc().rename(fname);
2849                 if (msg.empty())
2850                         return false;
2851                 message(from_utf8(msg));
2852                 break;
2853         }
2854         case LV_VC_COPY: {
2855                 string msg = b.lyxvc().copy(fname);
2856                 if (msg.empty())
2857                         return false;
2858                 message(from_utf8(msg));
2859                 break;
2860         }
2861         case LV_WRITE_AS:
2862         case LV_WRITE_AS_TEMPLATE:
2863                 break;
2864         }
2865         // LyXVC created the file already in case of LV_VC_RENAME or
2866         // LV_VC_COPY, but call saveBuffer() nevertheless to get
2867         // relative paths of included stuff right if we moved e.g. from
2868         // /a/b.lyx to /a/c/b.lyx.
2869
2870         bool const saved = saveBuffer(b, fname);
2871         if (saved)
2872                 b.reload();
2873         return saved;
2874 }
2875
2876
2877 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2878 {
2879         FileName fname = b.fileName();
2880
2881         FileDialog dlg(qt_("Choose a filename to export the document as"));
2882         dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2883
2884         QStringList types;
2885         QString const anyformat = qt_("Guess from extension (*.*)");
2886         types << anyformat;
2887
2888         vector<Format const *> export_formats;
2889         for (Format const & f : theFormats())
2890                 if (f.documentFormat())
2891                         export_formats.push_back(&f);
2892         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2893         map<QString, string> fmap;
2894         QString filter;
2895         string ext;
2896         for (Format const * f : export_formats) {
2897                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2898                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2899                                                      loc_prettyname,
2900                                                      from_ascii(f->extension())));
2901                 types << loc_filter;
2902                 fmap[loc_filter] = f->name();
2903                 if (from_ascii(f->name()) == iformat) {
2904                         filter = loc_filter;
2905                         ext = f->extension();
2906                 }
2907         }
2908         string ofname = fname.onlyFileName();
2909         if (!ext.empty())
2910                 ofname = support::changeExtension(ofname, ext);
2911         FileDialog::Result result =
2912                 dlg.save(toqstr(fname.onlyPath().absFileName()),
2913                          types,
2914                          toqstr(ofname),
2915                          &filter);
2916         if (result.first != FileDialog::Chosen)
2917                 return false;
2918
2919         string fmt_name;
2920         fname.set(fromqstr(result.second));
2921         if (filter == anyformat)
2922                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2923         else
2924                 fmt_name = fmap[filter];
2925         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2926                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2927
2928         if (fmt_name.empty() || fname.empty())
2929                 return false;
2930
2931         // fname is now the new Buffer location.
2932         if (FileName(fname).exists()) {
2933                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2934                 docstring text = bformat(_("The document %1$s already "
2935                                            "exists.\n\nDo you want to "
2936                                            "overwrite that document?"),
2937                                          file);
2938                 int const ret = Alert::prompt(_("Overwrite document?"),
2939                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2940                 switch (ret) {
2941                 case 0: break;
2942                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2943                 case 2: return false;
2944                 }
2945         }
2946
2947         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2948         DispatchResult dr;
2949         dispatch(cmd, dr);
2950         return dr.dispatched();
2951 }
2952
2953
2954 bool GuiView::saveBuffer(Buffer & b)
2955 {
2956         return saveBuffer(b, FileName());
2957 }
2958
2959
2960 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2961 {
2962         if (workArea(b) && workArea(b)->inDialogMode())
2963                 return true;
2964
2965         if (fn.empty() && b.isUnnamed())
2966                 return renameBuffer(b, docstring());
2967
2968         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2969         if (success) {
2970                 theSession().lastFiles().add(b.fileName());
2971                 theSession().writeFile();
2972                 return true;
2973         }
2974
2975         // Switch to this Buffer.
2976         setBuffer(&b);
2977
2978         // FIXME: we don't tell the user *WHY* the save failed !!
2979         docstring const file = makeDisplayPath(b.absFileName(), 30);
2980         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2981                                    "Do you want to rename the document and "
2982                                    "try again?"), file);
2983         int const ret = Alert::prompt(_("Rename and save?"),
2984                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2985         switch (ret) {
2986         case 0:
2987                 if (!renameBuffer(b, docstring()))
2988                         return false;
2989                 break;
2990         case 1:
2991                 break;
2992         case 2:
2993                 return false;
2994         }
2995
2996         return saveBuffer(b, fn);
2997 }
2998
2999
3000 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3001 {
3002         return closeWorkArea(wa, false);
3003 }
3004
3005
3006 // We only want to close the buffer if it is not visible in other workareas
3007 // of the same view, nor in other views, and if this is not a child
3008 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3009 {
3010         Buffer & buf = wa->bufferView().buffer();
3011
3012         bool last_wa = d.countWorkAreasOf(buf) == 1
3013                 && !inOtherView(buf) && !buf.parent();
3014
3015         bool close_buffer = last_wa;
3016
3017         if (last_wa) {
3018                 if (lyxrc.close_buffer_with_last_view == "yes")
3019                         ; // Nothing to do
3020                 else if (lyxrc.close_buffer_with_last_view == "no")
3021                         close_buffer = false;
3022                 else {
3023                         docstring file;
3024                         if (buf.isUnnamed())
3025                                 file = from_utf8(buf.fileName().onlyFileName());
3026                         else
3027                                 file = buf.fileName().displayName(30);
3028                         docstring const text = bformat(
3029                                 _("Last view on document %1$s is being closed.\n"
3030                                   "Would you like to close or hide the document?\n"
3031                                   "\n"
3032                                   "Hidden documents can be displayed back through\n"
3033                                   "the menu: View->Hidden->...\n"
3034                                   "\n"
3035                                   "To remove this question, set your preference in:\n"
3036                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
3037                                 ), file);
3038                         int ret = Alert::prompt(_("Close or hide document?"),
3039                                 text, 0, 1, _("&Close"), _("&Hide"));
3040                         close_buffer = (ret == 0);
3041                 }
3042         }
3043
3044         return closeWorkArea(wa, close_buffer);
3045 }
3046
3047
3048 bool GuiView::closeBuffer()
3049 {
3050         GuiWorkArea * wa = currentMainWorkArea();
3051         // coverity complained about this
3052         // it seems unnecessary, but perhaps is worth the check
3053         LASSERT(wa, return false);
3054
3055         setCurrentWorkArea(wa);
3056         Buffer & buf = wa->bufferView().buffer();
3057         return closeWorkArea(wa, !buf.parent());
3058 }
3059
3060
3061 void GuiView::writeSession() const {
3062         GuiWorkArea const * active_wa = currentMainWorkArea();
3063         for (int i = 0; i < d.splitter_->count(); ++i) {
3064                 TabWorkArea * twa = d.tabWorkArea(i);
3065                 for (int j = 0; j < twa->count(); ++j) {
3066                         GuiWorkArea * wa = twa->workArea(j);
3067                         Buffer & buf = wa->bufferView().buffer();
3068                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3069                 }
3070         }
3071 }
3072
3073
3074 bool GuiView::closeBufferAll()
3075 {
3076         // Close the workareas in all other views
3077         QList<int> const ids = guiApp->viewIds();
3078         for (int i = 0; i != ids.size(); ++i) {
3079                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3080                         return false;
3081         }
3082
3083         // Close our own workareas
3084         if (!closeWorkAreaAll())
3085                 return false;
3086
3087         // Now close the hidden buffers. We prevent hidden buffers from being
3088         // dirty, so we can just close them.
3089         theBufferList().closeAll();
3090         return true;
3091 }
3092
3093
3094 bool GuiView::closeWorkAreaAll()
3095 {
3096         setCurrentWorkArea(currentMainWorkArea());
3097
3098         // We might be in a situation that there is still a tabWorkArea, but
3099         // there are no tabs anymore. This can happen when we get here after a
3100         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3101         // many TabWorkArea's have no documents anymore.
3102         int empty_twa = 0;
3103
3104         // We have to call count() each time, because it can happen that
3105         // more than one splitter will disappear in one iteration (bug 5998).
3106         while (d.splitter_->count() > empty_twa) {
3107                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3108
3109                 if (twa->count() == 0)
3110                         ++empty_twa;
3111                 else {
3112                         setCurrentWorkArea(twa->currentWorkArea());
3113                         if (!closeTabWorkArea(twa))
3114                                 return false;
3115                 }
3116         }
3117         return true;
3118 }
3119
3120
3121 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3122 {
3123         if (!wa)
3124                 return false;
3125
3126         Buffer & buf = wa->bufferView().buffer();
3127
3128         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3129                 Alert::warning(_("Close document"),
3130                         _("Document could not be closed because it is being processed by LyX."));
3131                 return false;
3132         }
3133
3134         if (close_buffer)
3135                 return closeBuffer(buf);
3136         else {
3137                 if (!inMultiTabs(wa))
3138                         if (!saveBufferIfNeeded(buf, true))
3139                                 return false;
3140                 removeWorkArea(wa);
3141                 return true;
3142         }
3143 }
3144
3145
3146 bool GuiView::closeBuffer(Buffer & buf)
3147 {
3148         bool success = true;
3149         ListOfBuffers clist = buf.getChildren();
3150         ListOfBuffers::const_iterator it = clist.begin();
3151         ListOfBuffers::const_iterator const bend = clist.end();
3152         for (; it != bend; ++it) {
3153                 Buffer * child_buf = *it;
3154                 if (theBufferList().isOthersChild(&buf, child_buf)) {
3155                         child_buf->setParent(0);
3156                         continue;
3157                 }
3158
3159                 // FIXME: should we look in other tabworkareas?
3160                 // ANSWER: I don't think so. I've tested, and if the child is
3161                 // open in some other window, it closes without a problem.
3162                 GuiWorkArea * child_wa = workArea(*child_buf);
3163                 if (child_wa) {
3164                         if (closing_)
3165                                 // If we are in a close_event all children will be closed in some time,
3166                                 // so no need to do it here. This will ensure that the children end up
3167                                 // in the session file in the correct order. If we close the master
3168                                 // buffer, we can close or release the child buffers here too.
3169                                 continue;
3170                         success = closeWorkArea(child_wa, true);
3171                         if (!success)
3172                                 break;
3173                 } else {
3174                         // In this case the child buffer is open but hidden.
3175                         // Even in this case, children can be dirty (e.g.,
3176                         // after a label change in the master, see #11405).
3177                         // Therefore, check this
3178                         if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3179                                 // If we are in a close_event all children will be closed in some time,
3180                                 // so no need to do it here. This will ensure that the children end up
3181                                 // in the session file in the correct order. If we close the master
3182                                 // buffer, we can close or release the child buffers here too.
3183                                 continue;
3184                         // Save dirty buffers also if closing_!
3185                         if (saveBufferIfNeeded(*child_buf, false)) {
3186                                 child_buf->removeAutosaveFile();
3187                                 theBufferList().release(child_buf);
3188                         } else {
3189                                 // Saving of dirty children has been cancelled.
3190                                 // Cancel the whole process.
3191                                 success = false;
3192                                 break;
3193                         }
3194                 }
3195         }
3196         if (success) {
3197                 // goto bookmark to update bookmark pit.
3198                 // FIXME: we should update only the bookmarks related to this buffer!
3199                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3200                 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3201                         guiApp->gotoBookmark(i+1, false, false);
3202
3203                 if (saveBufferIfNeeded(buf, false)) {
3204                         buf.removeAutosaveFile();
3205                         theBufferList().release(&buf);
3206                         return true;
3207                 }
3208         }
3209         // open all children again to avoid a crash because of dangling
3210         // pointers (bug 6603)
3211         buf.updateBuffer();
3212         return false;
3213 }
3214
3215
3216 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3217 {
3218         while (twa == d.currentTabWorkArea()) {
3219                 twa->setCurrentIndex(twa->count() - 1);
3220
3221                 GuiWorkArea * wa = twa->currentWorkArea();
3222                 Buffer & b = wa->bufferView().buffer();
3223
3224                 // We only want to close the buffer if the same buffer is not visible
3225                 // in another view, and if this is not a child and if we are closing
3226                 // a view (not a tabgroup).
3227                 bool const close_buffer =
3228                         !inOtherView(b) && !b.parent() && closing_;
3229
3230                 if (!closeWorkArea(wa, close_buffer))
3231                         return false;
3232         }
3233         return true;
3234 }
3235
3236
3237 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3238 {
3239         if (buf.isClean() || buf.paragraphs().empty())
3240                 return true;
3241
3242         // Switch to this Buffer.
3243         setBuffer(&buf);
3244
3245         docstring file;
3246         bool exists;
3247         // FIXME: Unicode?
3248         if (buf.isUnnamed()) {
3249                 file = from_utf8(buf.fileName().onlyFileName());
3250                 exists = false;
3251         } else {
3252                 FileName filename = buf.fileName();
3253                 filename.refresh();
3254                 file = filename.displayName(30);
3255                 exists = filename.exists();
3256         }
3257
3258         // Bring this window to top before asking questions.
3259         raise();
3260         activateWindow();
3261
3262         int ret;
3263         if (hiding && buf.isUnnamed()) {
3264                 docstring const text = bformat(_("The document %1$s has not been "
3265                                                  "saved yet.\n\nDo you want to save "
3266                                                  "the document?"), file);
3267                 ret = Alert::prompt(_("Save new document?"),
3268                         text, 0, 1, _("&Save"), _("&Cancel"));
3269                 if (ret == 1)
3270                         ++ret;
3271         } else {
3272                 docstring const text = exists ?
3273                         bformat(_("The document %1$s has unsaved changes."
3274                                   "\n\nDo you want to save the document or "
3275                                   "discard the changes?"), file) :
3276                         bformat(_("The document %1$s has not been saved yet."
3277                                   "\n\nDo you want to save the document or "
3278                                   "discard it entirely?"), file);
3279                 docstring const title = exists ?
3280                         _("Save changed document?") : _("Save document?");
3281                 ret = Alert::prompt(title, text, 0, 2,
3282                                     _("&Save"), _("&Discard"), _("&Cancel"));
3283         }
3284
3285         switch (ret) {
3286         case 0:
3287                 if (!saveBuffer(buf))
3288                         return false;
3289                 break;
3290         case 1:
3291                 // If we crash after this we could have no autosave file
3292                 // but I guess this is really improbable (Jug).
3293                 // Sometimes improbable things happen:
3294                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3295                 // buf.removeAutosaveFile();
3296                 if (hiding)
3297                         // revert all changes
3298                         reloadBuffer(buf);
3299                 buf.markClean();
3300                 break;
3301         case 2:
3302                 return false;
3303         }
3304         return true;
3305 }
3306
3307
3308 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3309 {
3310         Buffer & buf = wa->bufferView().buffer();
3311
3312         for (int i = 0; i != d.splitter_->count(); ++i) {
3313                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3314                 if (wa_ && wa_ != wa)
3315                         return true;
3316         }
3317         return inOtherView(buf);
3318 }
3319
3320
3321 bool GuiView::inOtherView(Buffer & buf)
3322 {
3323         QList<int> const ids = guiApp->viewIds();
3324
3325         for (int i = 0; i != ids.size(); ++i) {
3326                 if (id_ == ids[i])
3327                         continue;
3328
3329                 if (guiApp->view(ids[i]).workArea(buf))
3330                         return true;
3331         }
3332         return false;
3333 }
3334
3335
3336 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3337 {
3338         if (!documentBufferView())
3339                 return;
3340
3341         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3342                 Buffer * const curbuf = &documentBufferView()->buffer();
3343                 int nwa = twa->count();
3344                 for (int i = 0; i < nwa; ++i) {
3345                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3346                                 int next_index;
3347                                 if (np == NEXTBUFFER)
3348                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3349                                 else
3350                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3351                                 if (move)
3352                                         twa->moveTab(i, next_index);
3353                                 else
3354                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3355                                 break;
3356                         }
3357                 }
3358         }
3359 }
3360
3361
3362 /// make sure the document is saved
3363 static bool ensureBufferClean(Buffer * buffer)
3364 {
3365         LASSERT(buffer, return false);
3366         if (buffer->isClean() && !buffer->isUnnamed())
3367                 return true;
3368
3369         docstring const file = buffer->fileName().displayName(30);
3370         docstring title;
3371         docstring text;
3372         if (!buffer->isUnnamed()) {
3373                 text = bformat(_("The document %1$s has unsaved "
3374                                                  "changes.\n\nDo you want to save "
3375                                                  "the document?"), file);
3376                 title = _("Save changed document?");
3377
3378         } else {
3379                 text = bformat(_("The document %1$s has not been "
3380                                                  "saved yet.\n\nDo you want to save "
3381                                                  "the document?"), file);
3382                 title = _("Save new document?");
3383         }
3384         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3385
3386         if (ret == 0)
3387                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3388
3389         return buffer->isClean() && !buffer->isUnnamed();
3390 }
3391
3392
3393 bool GuiView::reloadBuffer(Buffer & buf)
3394 {
3395         currentBufferView()->cursor().reset();
3396         Buffer::ReadStatus status = buf.reload();
3397         return status == Buffer::ReadSuccess;
3398 }
3399
3400
3401 void GuiView::checkExternallyModifiedBuffers()
3402 {
3403         BufferList::iterator bit = theBufferList().begin();
3404         BufferList::iterator const bend = theBufferList().end();
3405         for (; bit != bend; ++bit) {
3406                 Buffer * buf = *bit;
3407                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3408                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3409                                         " Reload now? Any local changes will be lost."),
3410                                         from_utf8(buf->absFileName()));
3411                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3412                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3413                         if (!ret)
3414                                 reloadBuffer(*buf);
3415                 }
3416         }
3417 }
3418
3419
3420 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3421 {
3422         Buffer * buffer = documentBufferView()
3423                 ? &(documentBufferView()->buffer()) : 0;
3424
3425         switch (cmd.action()) {
3426         case LFUN_VC_REGISTER:
3427                 if (!buffer || !ensureBufferClean(buffer))
3428                         break;
3429                 if (!buffer->lyxvc().inUse()) {
3430                         if (buffer->lyxvc().registrer()) {
3431                                 reloadBuffer(*buffer);
3432                                 dr.clearMessageUpdate();
3433                         }
3434                 }
3435                 break;
3436
3437         case LFUN_VC_RENAME:
3438         case LFUN_VC_COPY: {
3439                 if (!buffer || !ensureBufferClean(buffer))
3440                         break;
3441                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3442                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3443                                 // Some changes are not yet committed.
3444                                 // We test here and not in getStatus(), since
3445                                 // this test is expensive.
3446                                 string log;
3447                                 LyXVC::CommandResult ret =
3448                                         buffer->lyxvc().checkIn(log);
3449                                 dr.setMessage(log);
3450                                 if (ret == LyXVC::ErrorCommand ||
3451                                     ret == LyXVC::VCSuccess)
3452                                         reloadBuffer(*buffer);
3453                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3454                                         frontend::Alert::error(
3455                                                 _("Revision control error."),
3456                                                 _("Document could not be checked in."));
3457                                         break;
3458                                 }
3459                         }
3460                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3461                                 LV_VC_RENAME : LV_VC_COPY;
3462                         renameBuffer(*buffer, cmd.argument(), kind);
3463                 }
3464                 break;
3465         }
3466
3467         case LFUN_VC_CHECK_IN:
3468                 if (!buffer || !ensureBufferClean(buffer))
3469                         break;
3470                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3471                         string log;
3472                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3473                         dr.setMessage(log);
3474                         // Only skip reloading if the checkin was cancelled or
3475                         // an error occurred before the real checkin VCS command
3476                         // was executed, since the VCS might have changed the
3477                         // file even if it could not checkin successfully.
3478                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3479                                 reloadBuffer(*buffer);
3480                 }
3481                 break;
3482
3483         case LFUN_VC_CHECK_OUT:
3484                 if (!buffer || !ensureBufferClean(buffer))
3485                         break;
3486                 if (buffer->lyxvc().inUse()) {
3487                         dr.setMessage(buffer->lyxvc().checkOut());
3488                         reloadBuffer(*buffer);
3489                 }
3490                 break;
3491
3492         case LFUN_VC_LOCKING_TOGGLE:
3493                 LASSERT(buffer, return);
3494                 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3495                         break;
3496                 if (buffer->lyxvc().inUse()) {
3497                         string res = buffer->lyxvc().lockingToggle();
3498                         if (res.empty()) {
3499                                 frontend::Alert::error(_("Revision control error."),
3500                                 _("Error when setting the locking property."));
3501                         } else {
3502                                 dr.setMessage(res);
3503                                 reloadBuffer(*buffer);
3504                         }
3505                 }
3506                 break;
3507
3508         case LFUN_VC_REVERT:
3509                 LASSERT(buffer, return);
3510                 if (buffer->lyxvc().revert()) {
3511                         reloadBuffer(*buffer);
3512                         dr.clearMessageUpdate();
3513                 }
3514                 break;
3515
3516         case LFUN_VC_UNDO_LAST:
3517                 LASSERT(buffer, return);
3518                 buffer->lyxvc().undoLast();
3519                 reloadBuffer(*buffer);
3520                 dr.clearMessageUpdate();
3521                 break;
3522
3523         case LFUN_VC_REPO_UPDATE:
3524                 LASSERT(buffer, return);
3525                 if (ensureBufferClean(buffer)) {
3526                         dr.setMessage(buffer->lyxvc().repoUpdate());
3527                         checkExternallyModifiedBuffers();
3528                 }
3529                 break;
3530
3531         case LFUN_VC_COMMAND: {
3532                 string flag = cmd.getArg(0);
3533                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3534                         break;
3535                 docstring message;
3536                 if (contains(flag, 'M')) {
3537                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3538                                 break;
3539                 }
3540                 string path = cmd.getArg(1);
3541                 if (contains(path, "$$p") && buffer)
3542                         path = subst(path, "$$p", buffer->filePath());
3543                 LYXERR(Debug::LYXVC, "Directory: " << path);
3544                 FileName pp(path);
3545                 if (!pp.isReadableDirectory()) {
3546                         lyxerr << _("Directory is not accessible.") << endl;
3547                         break;
3548                 }
3549                 support::PathChanger p(pp);
3550
3551                 string command = cmd.getArg(2);
3552                 if (command.empty())
3553                         break;
3554                 if (buffer) {
3555                         command = subst(command, "$$i", buffer->absFileName());
3556                         command = subst(command, "$$p", buffer->filePath());
3557                 }
3558                 command = subst(command, "$$m", to_utf8(message));
3559                 LYXERR(Debug::LYXVC, "Command: " << command);
3560                 Systemcall one;
3561                 one.startscript(Systemcall::Wait, command);
3562
3563                 if (!buffer)
3564                         break;
3565                 if (contains(flag, 'I'))
3566                         buffer->markDirty();
3567                 if (contains(flag, 'R'))
3568                         reloadBuffer(*buffer);
3569
3570                 break;
3571                 }
3572
3573         case LFUN_VC_COMPARE: {
3574                 if (cmd.argument().empty()) {
3575                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3576                         break;
3577                 }
3578
3579                 string rev1 = cmd.getArg(0);
3580                 string f1, f2;
3581                 LATTEST(buffer)
3582
3583                 // f1
3584                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3585                         break;
3586
3587                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3588                         f2 = buffer->absFileName();
3589                 } else {
3590                         string rev2 = cmd.getArg(1);
3591                         if (rev2.empty())
3592                                 break;
3593                         // f2
3594                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3595                                 break;
3596                 }
3597
3598                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3599                                         f1 << "\n"  << f2 << "\n" );
3600                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3601                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3602                 break;
3603         }
3604
3605         default:
3606                 break;
3607         }
3608 }
3609
3610
3611 void GuiView::openChildDocument(string const & fname)
3612 {
3613         LASSERT(documentBufferView(), return);
3614         Buffer & buffer = documentBufferView()->buffer();
3615         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3616         documentBufferView()->saveBookmark(false);
3617         Buffer * child = 0;
3618         if (theBufferList().exists(filename)) {
3619                 child = theBufferList().getBuffer(filename);
3620                 setBuffer(child);
3621         } else {
3622                 message(bformat(_("Opening child document %1$s..."),
3623                         makeDisplayPath(filename.absFileName())));
3624                 child = loadDocument(filename, false);
3625         }
3626         // Set the parent name of the child document.
3627         // This makes insertion of citations and references in the child work,
3628         // when the target is in the parent or another child document.
3629         if (child)
3630                 child->setParent(&buffer);
3631 }
3632
3633
3634 bool GuiView::goToFileRow(string const & argument)
3635 {
3636         string file_name;
3637         int row = -1;
3638         size_t i = argument.find_last_of(' ');
3639         if (i != string::npos) {
3640                 file_name = os::internal_path(trim(argument.substr(0, i)));
3641                 istringstream is(argument.substr(i + 1));
3642                 is >> row;
3643                 if (is.fail())
3644                         i = string::npos;
3645         }
3646         if (i == string::npos) {
3647                 LYXERR0("Wrong argument: " << argument);
3648                 return false;
3649         }
3650         Buffer * buf = 0;
3651         string const abstmp = package().temp_dir().absFileName();
3652         string const realtmp = package().temp_dir().realPath();
3653         // We have to use os::path_prefix_is() here, instead of
3654         // simply prefixIs(), because the file name comes from
3655         // an external application and may need case adjustment.
3656         if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3657                 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3658                 // Needed by inverse dvi search. If it is a file
3659                 // in tmpdir, call the apropriated function.
3660                 // If tmpdir is a symlink, we may have the real
3661                 // path passed back, so we correct for that.
3662                 if (!prefixIs(file_name, abstmp))
3663                         file_name = subst(file_name, realtmp, abstmp);
3664                 buf = theBufferList().getBufferFromTmp(file_name);
3665         } else {
3666                 // Must replace extension of the file to be .lyx
3667                 // and get full path
3668                 FileName const s = fileSearch(string(),
3669                                                   support::changeExtension(file_name, ".lyx"), "lyx");
3670                 // Either change buffer or load the file
3671                 if (theBufferList().exists(s))
3672                         buf = theBufferList().getBuffer(s);
3673                 else if (s.exists()) {
3674                         buf = loadDocument(s);
3675                         if (!buf)
3676                                 return false;
3677                 } else {
3678                         message(bformat(
3679                                         _("File does not exist: %1$s"),
3680                                         makeDisplayPath(file_name)));
3681                         return false;
3682                 }
3683         }
3684         if (!buf) {
3685                 message(bformat(
3686                         _("No buffer for file: %1$s."),
3687                         makeDisplayPath(file_name))
3688                 );
3689                 return false;
3690         }
3691         setBuffer(buf);
3692         bool success = documentBufferView()->setCursorFromRow(row);
3693         if (!success) {
3694                 LYXERR(Debug::LATEX,
3695                        "setCursorFromRow: invalid position for row " << row);
3696                 frontend::Alert::error(_("Inverse Search Failed"),
3697                                        _("Invalid position requested by inverse search.\n"
3698                                          "You may need to update the viewed document."));
3699         }
3700         return success;
3701 }
3702
3703
3704 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3705 {
3706         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3707         menu->exec(QCursor::pos());
3708 }
3709
3710
3711 template<class T>
3712 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3713                 Buffer const * orig, Buffer * clone, string const & format)
3714 {
3715         Buffer::ExportStatus const status = func(format);
3716
3717         // the cloning operation will have produced a clone of the entire set of
3718         // documents, starting from the master. so we must delete those.
3719         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3720         delete mbuf;
3721         busyBuffers.remove(orig);
3722         return status;
3723 }
3724
3725
3726 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3727                 Buffer const * orig, Buffer * clone, string const & format)
3728 {
3729         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3730                         &Buffer::doExport;
3731         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3732 }
3733
3734
3735 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3736                 Buffer const * orig, Buffer * clone, string const & format)
3737 {
3738         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3739                         &Buffer::doExport;
3740         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3741 }
3742
3743
3744 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3745                 Buffer const * orig, Buffer * clone, string const & format)
3746 {
3747         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3748                         &Buffer::preview;
3749         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3750 }
3751
3752
3753 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3754                            string const & argument,
3755                            Buffer const * used_buffer,
3756                            docstring const & msg,
3757                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3758                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3759                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3760                            bool allow_async)
3761 {
3762         if (!used_buffer)
3763                 return false;
3764
3765         string format = argument;
3766         if (format.empty())
3767                 format = used_buffer->params().getDefaultOutputFormat();
3768         processing_format = format;
3769         if (!msg.empty()) {
3770                 progress_->clearMessages();
3771                 gv_->message(msg);
3772         }
3773 #if EXPORT_in_THREAD
3774         if (allow_async) {
3775                 GuiViewPrivate::busyBuffers.insert(used_buffer);
3776                 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3777                 if (!cloned_buffer) {
3778                         Alert::error(_("Export Error"),
3779                                      _("Error cloning the Buffer."));
3780                         return false;
3781                 }
3782                 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3783                                         asyncFunc,
3784                                         used_buffer,
3785                                         cloned_buffer,
3786                                         format);
3787                 setPreviewFuture(f);
3788                 last_export_format = used_buffer->params().bufferFormat();
3789                 (void) syncFunc;
3790                 (void) previewFunc;
3791                 // We are asynchronous, so we don't know here anything about the success
3792                 return true;
3793         } else {
3794                 Buffer::ExportStatus status;
3795                 if (syncFunc) {
3796                         status = (used_buffer->*syncFunc)(format, false);
3797                 } else if (previewFunc) {
3798                         status = (used_buffer->*previewFunc)(format);
3799                 } else
3800                         return false;
3801                 handleExportStatus(gv_, status, format);
3802                 (void) asyncFunc;
3803                 return (status == Buffer::ExportSuccess
3804                                 || status == Buffer::PreviewSuccess);
3805         }
3806 #else
3807         (void) allow_async;
3808         Buffer::ExportStatus status;
3809         if (syncFunc) {
3810                 status = (used_buffer->*syncFunc)(format, true);
3811         } else if (previewFunc) {
3812                 status = (used_buffer->*previewFunc)(format);
3813         } else
3814                 return false;
3815         handleExportStatus(gv_, status, format);
3816         (void) asyncFunc;
3817         return (status == Buffer::ExportSuccess
3818                         || status == Buffer::PreviewSuccess);
3819 #endif
3820 }
3821
3822 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3823 {
3824         BufferView * bv = currentBufferView();
3825         LASSERT(bv, return);
3826
3827         // Let the current BufferView dispatch its own actions.
3828         bv->dispatch(cmd, dr);
3829         if (dr.dispatched()) {
3830                 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3831                         updateDialog("document", "");
3832                 return;
3833         }
3834
3835         // Try with the document BufferView dispatch if any.
3836         BufferView * doc_bv = documentBufferView();
3837         if (doc_bv && doc_bv != bv) {
3838                 doc_bv->dispatch(cmd, dr);
3839                 if (dr.dispatched()) {
3840                         if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3841                                 updateDialog("document", "");
3842                         return;
3843                 }
3844         }
3845
3846         // Then let the current Cursor dispatch its own actions.
3847         bv->cursor().dispatch(cmd);
3848
3849         // update completion. We do it here and not in
3850         // processKeySym to avoid another redraw just for a
3851         // changed inline completion
3852         if (cmd.origin() == FuncRequest::KEYBOARD) {
3853                 if (cmd.action() == LFUN_SELF_INSERT
3854                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3855                         updateCompletion(bv->cursor(), true, true);
3856                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3857                         updateCompletion(bv->cursor(), false, true);
3858                 else
3859                         updateCompletion(bv->cursor(), false, false);
3860         }
3861
3862         dr = bv->cursor().result();
3863 }
3864
3865
3866 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3867 {
3868         BufferView * bv = currentBufferView();
3869         // By default we won't need any update.
3870         dr.screenUpdate(Update::None);
3871         // assume cmd will be dispatched
3872         dr.dispatched(true);
3873
3874         Buffer * doc_buffer = documentBufferView()
3875                 ? &(documentBufferView()->buffer()) : 0;
3876
3877         if (cmd.origin() == FuncRequest::TOC) {
3878                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3879                 // FIXME: do we need to pass a DispatchResult object here?
3880                 toc->doDispatch(bv->cursor(), cmd);
3881                 return;
3882         }
3883
3884         string const argument = to_utf8(cmd.argument());
3885
3886         switch(cmd.action()) {
3887                 case LFUN_BUFFER_CHILD_OPEN:
3888                         openChildDocument(to_utf8(cmd.argument()));
3889                         break;
3890
3891                 case LFUN_BUFFER_IMPORT:
3892                         importDocument(to_utf8(cmd.argument()));
3893                         break;
3894
3895                 case LFUN_MASTER_BUFFER_EXPORT:
3896                         if (doc_buffer)
3897                                 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3898                         // fall through
3899                 case LFUN_BUFFER_EXPORT: {
3900                         if (!doc_buffer)
3901                                 break;
3902                         // GCC only sees strfwd.h when building merged
3903                         if (::lyx::operator==(cmd.argument(), "custom")) {
3904                                 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3905                                 // so the following test should not be needed.
3906                                 // In principle, we could try to switch to such a view...
3907                                 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3908                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3909                                 break;
3910                         }
3911
3912                         string const dest = cmd.getArg(1);
3913                         FileName target_dir;
3914                         if (!dest.empty() && FileName::isAbsolute(dest))
3915                                 target_dir = FileName(support::onlyPath(dest));
3916                         else
3917                                 target_dir = doc_buffer->fileName().onlyPath();
3918
3919                         string const format = (argument.empty() || argument == "default") ?
3920                                 doc_buffer->params().getDefaultOutputFormat() : argument;
3921
3922                         if ((dest.empty() && doc_buffer->isUnnamed())
3923                             || !target_dir.isDirWritable()) {
3924                                 exportBufferAs(*doc_buffer, from_utf8(format));
3925                                 break;
3926                         }
3927                         /* TODO/Review: Is it a problem to also export the children?
3928                                         See the update_unincluded flag */
3929                         d.asyncBufferProcessing(format,
3930                                                 doc_buffer,
3931                                                 _("Exporting ..."),
3932                                                 &GuiViewPrivate::exportAndDestroy,
3933                                                 &Buffer::doExport,
3934                                                 0, cmd.allowAsync());
3935                         // TODO Inform user about success
3936                         break;
3937                 }
3938
3939                 case LFUN_BUFFER_EXPORT_AS: {
3940                         LASSERT(doc_buffer, break);
3941                         docstring f = cmd.argument();
3942                         if (f.empty())
3943                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3944                         exportBufferAs(*doc_buffer, f);
3945                         break;
3946                 }
3947
3948                 case LFUN_BUFFER_UPDATE: {
3949                         d.asyncBufferProcessing(argument,
3950                                                 doc_buffer,
3951                                                 _("Exporting ..."),
3952                                                 &GuiViewPrivate::compileAndDestroy,
3953                                                 &Buffer::doExport,
3954                                                 0, cmd.allowAsync());
3955                         break;
3956                 }
3957                 case LFUN_BUFFER_VIEW: {
3958                         d.asyncBufferProcessing(argument,
3959                                                 doc_buffer,
3960                                                 _("Previewing ..."),
3961                                                 &GuiViewPrivate::previewAndDestroy,
3962                                                 0,
3963                                                 &Buffer::preview, cmd.allowAsync());
3964                         break;
3965                 }
3966                 case LFUN_MASTER_BUFFER_UPDATE: {
3967                         d.asyncBufferProcessing(argument,
3968                                                 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3969                                                 docstring(),
3970                                                 &GuiViewPrivate::compileAndDestroy,
3971                                                 &Buffer::doExport,
3972                                                 0, cmd.allowAsync());
3973                         break;
3974                 }
3975                 case LFUN_MASTER_BUFFER_VIEW: {
3976                         d.asyncBufferProcessing(argument,
3977                                                 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3978                                                 docstring(),
3979                                                 &GuiViewPrivate::previewAndDestroy,
3980                                                 0, &Buffer::preview, cmd.allowAsync());
3981                         break;
3982                 }
3983                 case LFUN_EXPORT_CANCEL: {
3984                         Systemcall::killscript();
3985                         break;
3986                 }
3987                 case LFUN_BUFFER_SWITCH: {
3988                         string const file_name = to_utf8(cmd.argument());
3989                         if (!FileName::isAbsolute(file_name)) {
3990                                 dr.setError(true);
3991                                 dr.setMessage(_("Absolute filename expected."));
3992                                 break;
3993                         }
3994
3995                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3996                         if (!buffer) {
3997                                 dr.setError(true);
3998                                 dr.setMessage(_("Document not loaded"));
3999                                 break;
4000                         }
4001
4002                         // Do we open or switch to the buffer in this view ?
4003                         if (workArea(*buffer)
4004                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4005                                 setBuffer(buffer);
4006                                 break;
4007                         }
4008
4009                         // Look for the buffer in other views
4010                         QList<int> const ids = guiApp->viewIds();
4011                         int i = 0;
4012                         for (; i != ids.size(); ++i) {
4013                                 GuiView & gv = guiApp->view(ids[i]);
4014                                 if (gv.workArea(*buffer)) {
4015                                         gv.raise();
4016                                         gv.activateWindow();
4017                                         gv.setFocus();
4018                                         gv.setBuffer(buffer);
4019                                         break;
4020                                 }
4021                         }
4022
4023                         // If necessary, open a new window as a last resort
4024                         if (i == ids.size()) {
4025                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4026                                 lyx::dispatch(cmd);
4027                         }
4028                         break;
4029                 }
4030
4031                 case LFUN_BUFFER_NEXT:
4032                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4033                         break;
4034
4035                 case LFUN_BUFFER_MOVE_NEXT:
4036                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4037                         break;
4038
4039                 case LFUN_BUFFER_PREVIOUS:
4040                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
4041                         break;
4042
4043                 case LFUN_BUFFER_MOVE_PREVIOUS:
4044                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
4045                         break;
4046
4047                 case LFUN_BUFFER_CHKTEX:
4048                         LASSERT(doc_buffer, break);
4049                         doc_buffer->runChktex();
4050                         break;
4051
4052                 case LFUN_COMMAND_EXECUTE: {
4053                         command_execute_ = true;
4054                         minibuffer_focus_ = true;
4055                         break;
4056                 }
4057                 case LFUN_DROP_LAYOUTS_CHOICE:
4058                         d.layout_->showPopup();
4059                         break;
4060
4061                 case LFUN_MENU_OPEN:
4062                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4063                                 menu->exec(QCursor::pos());
4064                         break;
4065
4066                 case LFUN_FILE_INSERT: {
4067                         if (cmd.getArg(1) == "ignorelang")
4068                                 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4069                         else
4070                                 insertLyXFile(cmd.argument());
4071                         break;
4072                 }
4073
4074                 case LFUN_FILE_INSERT_PLAINTEXT:
4075                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4076                         string const fname = to_utf8(cmd.argument());
4077                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
4078                                 dr.setMessage(_("Absolute filename expected."));
4079                                 break;
4080                         }
4081
4082                         FileName filename(fname);
4083                         if (fname.empty()) {
4084                                 FileDialog dlg(qt_("Select file to insert"));
4085
4086                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4087                                         QStringList(qt_("All Files (*)")));
4088
4089                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4090                                         dr.setMessage(_("Canceled."));
4091                                         break;
4092                                 }
4093
4094                                 filename.set(fromqstr(result.second));
4095                         }
4096
4097                         if (bv) {
4098                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4099                                 bv->dispatch(new_cmd, dr);
4100                         }
4101                         break;
4102                 }
4103
4104                 case LFUN_BUFFER_RELOAD: {
4105                         LASSERT(doc_buffer, break);
4106
4107                         // drop changes?
4108                         bool drop = (cmd.argument() == "dump");
4109
4110                         int ret = 0;
4111                         if (!drop && !doc_buffer->isClean()) {
4112                                 docstring const file =
4113                                         makeDisplayPath(doc_buffer->absFileName(), 20);
4114                                 if (doc_buffer->notifiesExternalModification()) {
4115                                         docstring text = _("The current version will be lost. "
4116                                             "Are you sure you want to load the version on disk "
4117                                             "of the document %1$s?");
4118                                         ret = Alert::prompt(_("Reload saved document?"),
4119                                                             bformat(text, file), 1, 1,
4120                                                             _("&Reload"), _("&Cancel"));
4121                                 } else {
4122                                         docstring text = _("Any changes will be lost. "
4123                                             "Are you sure you want to revert to the saved version "
4124                                             "of the document %1$s?");
4125                                         ret = Alert::prompt(_("Revert to saved document?"),
4126                                                             bformat(text, file), 1, 1,
4127                                                             _("&Revert"), _("&Cancel"));
4128                                 }
4129                         }
4130
4131                         if (ret == 0) {
4132                                 doc_buffer->markClean();
4133                                 reloadBuffer(*doc_buffer);
4134                                 dr.forceBufferUpdate();
4135                         }
4136                         break;
4137                 }
4138
4139                 case LFUN_BUFFER_WRITE:
4140                         LASSERT(doc_buffer, break);
4141                         saveBuffer(*doc_buffer);
4142                         break;
4143
4144                 case LFUN_BUFFER_WRITE_AS:
4145                         LASSERT(doc_buffer, break);
4146                         renameBuffer(*doc_buffer, cmd.argument());
4147                         break;
4148
4149                 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4150                         LASSERT(doc_buffer, break);
4151                         renameBuffer(*doc_buffer, cmd.argument(),
4152                                      LV_WRITE_AS_TEMPLATE);
4153                         break;
4154
4155                 case LFUN_BUFFER_WRITE_ALL: {
4156                         Buffer * first = theBufferList().first();
4157                         if (!first)
4158                                 break;
4159                         message(_("Saving all documents..."));
4160                         // We cannot use a for loop as the buffer list cycles.
4161                         Buffer * b = first;
4162                         do {
4163                                 if (!b->isClean()) {
4164                                         saveBuffer(*b);
4165                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4166                                 }
4167                                 b = theBufferList().next(b);
4168                         } while (b != first);
4169                         dr.setMessage(_("All documents saved."));
4170                         break;
4171                 }
4172
4173                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4174                         LASSERT(doc_buffer, break);
4175                         doc_buffer->clearExternalModification();
4176                         break;
4177
4178                 case LFUN_BUFFER_CLOSE:
4179                         closeBuffer();
4180                         break;
4181
4182                 case LFUN_BUFFER_CLOSE_ALL:
4183                         closeBufferAll();
4184                         break;
4185
4186                 case LFUN_DEVEL_MODE_TOGGLE:
4187                         devel_mode_ = !devel_mode_;
4188                         if (devel_mode_)
4189                                 dr.setMessage(_("Developer mode is now enabled."));
4190                         else
4191                                 dr.setMessage(_("Developer mode is now disabled."));
4192                         break;
4193
4194                 case LFUN_TOOLBAR_TOGGLE: {
4195                         string const name = cmd.getArg(0);
4196                         if (GuiToolbar * t = toolbar(name))
4197                                 t->toggle();
4198                         break;
4199                 }
4200
4201                 case LFUN_TOOLBAR_MOVABLE: {
4202                         string const name = cmd.getArg(0);
4203                         if (name == "*") {
4204                                 // toggle (all) toolbars movablility
4205                                 toolbarsMovable_ = !toolbarsMovable_;
4206                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4207                                         GuiToolbar * tb = toolbar(ti.name);
4208                                         if (tb && tb->isMovable() != toolbarsMovable_)
4209                                                 // toggle toolbar movablity if it does not fit lock
4210                                                 // (all) toolbars positions state silent = true, since
4211                                                 // status bar notifications are slow
4212                                                 tb->movable(true);
4213                                 }
4214                                 if (toolbarsMovable_)
4215                                         dr.setMessage(_("Toolbars unlocked."));
4216                                 else
4217                                         dr.setMessage(_("Toolbars locked."));
4218                         } else if (GuiToolbar * t = toolbar(name)) {
4219                                 // toggle current toolbar movablity
4220                                 t->movable();
4221                                 // update lock (all) toolbars positions
4222                                 updateLockToolbars();
4223                         }
4224                         break;
4225                 }
4226
4227                 case LFUN_ICON_SIZE: {
4228                         QSize size = d.iconSize(cmd.argument());
4229                         setIconSize(size);
4230                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4231                                                 size.width(), size.height()));
4232                         break;
4233                 }
4234
4235                 case LFUN_DIALOG_UPDATE: {
4236                         string const name = to_utf8(cmd.argument());
4237                         if (name == "prefs" || name == "document")
4238                                 updateDialog(name, string());
4239                         else if (name == "paragraph")
4240                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4241                         else if (currentBufferView()) {
4242                                 Inset * inset = currentBufferView()->editedInset(name);
4243                                 // Can only update a dialog connected to an existing inset
4244                                 if (inset) {
4245                                         // FIXME: get rid of this indirection; GuiView ask the inset
4246                                         // if he is kind enough to update itself...
4247                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4248                                         //FIXME: pass DispatchResult here?
4249                                         inset->dispatch(currentBufferView()->cursor(), fr);
4250                                 }
4251                         }
4252                         break;
4253                 }
4254
4255                 case LFUN_DIALOG_TOGGLE: {
4256                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4257                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4258                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
4259                         break;
4260                 }
4261
4262                 case LFUN_DIALOG_DISCONNECT_INSET:
4263                         disconnectDialog(to_utf8(cmd.argument()));
4264                         break;
4265
4266                 case LFUN_DIALOG_HIDE: {
4267                         guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4268                         break;
4269                 }
4270
4271                 case LFUN_DIALOG_SHOW: {
4272                         string const name = cmd.getArg(0);
4273                         string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4274
4275                         if (name == "latexlog") {
4276                                 // gettatus checks that
4277                                 LATTEST(doc_buffer);
4278                                 Buffer::LogType type;
4279                                 string const logfile = doc_buffer->logName(&type);
4280                                 switch (type) {
4281                                 case Buffer::latexlog:
4282                                         sdata = "latex ";
4283                                         break;
4284                                 case Buffer::buildlog:
4285                                         sdata = "literate ";
4286                                         break;
4287                                 }
4288                                 sdata += Lexer::quoteString(logfile);
4289                                 showDialog("log", sdata);
4290                         } else if (name == "vclog") {
4291                                 // getStatus checks that
4292                                 LATTEST(doc_buffer);
4293                                 string const sdata2 = "vc " +
4294                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4295                                 showDialog("log", sdata2);
4296                         } else if (name == "symbols") {
4297                                 sdata = bv->cursor().getEncoding()->name();
4298                                 if (!sdata.empty())
4299                                         showDialog("symbols", sdata);
4300                         // bug 5274
4301                         } else if (name == "prefs" && isFullScreen()) {
4302                                 lfunUiToggle("fullscreen");
4303                                 showDialog("prefs", sdata);
4304                         } else
4305                                 showDialog(name, sdata);
4306                         break;
4307                 }
4308
4309                 case LFUN_MESSAGE:
4310                         dr.setMessage(cmd.argument());
4311                         break;
4312
4313                 case LFUN_UI_TOGGLE: {
4314                         string arg = cmd.getArg(0);
4315                         if (!lfunUiToggle(arg)) {
4316                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4317                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4318                         }
4319                         // Make sure the keyboard focus stays in the work area.
4320                         setFocus();
4321                         break;
4322                 }
4323
4324                 case LFUN_VIEW_SPLIT: {
4325                         LASSERT(doc_buffer, break);
4326                         string const orientation = cmd.getArg(0);
4327                         d.splitter_->setOrientation(orientation == "vertical"
4328                                 ? Qt::Vertical : Qt::Horizontal);
4329                         TabWorkArea * twa = addTabWorkArea();
4330                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4331                         setCurrentWorkArea(wa);
4332                         break;
4333                 }
4334                 case LFUN_TAB_GROUP_CLOSE:
4335                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4336                                 closeTabWorkArea(twa);
4337                                 d.current_work_area_ = 0;
4338                                 twa = d.currentTabWorkArea();
4339                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4340                                 if (twa) {
4341                                         // Make sure the work area is up to date.
4342                                         setCurrentWorkArea(twa->currentWorkArea());
4343                                 } else {
4344                                         setCurrentWorkArea(0);
4345                                 }
4346                         }
4347                         break;
4348
4349                 case LFUN_VIEW_CLOSE:
4350                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4351                                 closeWorkArea(twa->currentWorkArea());
4352                                 d.current_work_area_ = 0;
4353                                 twa = d.currentTabWorkArea();
4354                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4355                                 if (twa) {
4356                                         // Make sure the work area is up to date.
4357                                         setCurrentWorkArea(twa->currentWorkArea());
4358                                 } else {
4359                                         setCurrentWorkArea(0);
4360                                 }
4361                         }
4362                         break;
4363
4364                 case LFUN_COMPLETION_INLINE:
4365                         if (d.current_work_area_)
4366                                 d.current_work_area_->completer().showInline();
4367                         break;
4368
4369                 case LFUN_COMPLETION_POPUP:
4370                         if (d.current_work_area_)
4371                                 d.current_work_area_->completer().showPopup();
4372                         break;
4373
4374
4375                 case LFUN_COMPLETE:
4376                         if (d.current_work_area_)
4377                                 d.current_work_area_->completer().tab();
4378                         break;
4379
4380                 case LFUN_COMPLETION_CANCEL:
4381                         if (d.current_work_area_) {
4382                                 if (d.current_work_area_->completer().popupVisible())
4383                                         d.current_work_area_->completer().hidePopup();
4384                                 else
4385                                         d.current_work_area_->completer().hideInline();
4386                         }
4387                         break;
4388
4389                 case LFUN_COMPLETION_ACCEPT:
4390                         if (d.current_work_area_)
4391                                 d.current_work_area_->completer().activate();
4392                         break;
4393
4394                 case LFUN_BUFFER_ZOOM_IN:
4395                 case LFUN_BUFFER_ZOOM_OUT:
4396                 case LFUN_BUFFER_ZOOM: {
4397                         if (cmd.argument().empty()) {
4398                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4399                                         zoom_ratio_ = 1.0;
4400                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4401                                         zoom_ratio_ += 0.1;
4402                                 else
4403                                         zoom_ratio_ -= 0.1;
4404                         } else {
4405                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4406                                         zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4407                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4408                                         zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4409                                 else
4410                                         zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4411                         }
4412
4413                         // Actual zoom value: default zoom + fractional extra value
4414                         int zoom = lyxrc.defaultZoom * zoom_ratio_;
4415                         if (zoom < static_cast<int>(zoom_min_))
4416                                 zoom = zoom_min_;
4417
4418                         lyxrc.currentZoom = zoom;
4419
4420                         dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4421                                               lyxrc.currentZoom, lyxrc.defaultZoom));
4422
4423                         guiApp->fontLoader().update();
4424                         dr.screenUpdate(Update::Force | Update::FitCursor);
4425                         break;
4426                 }
4427
4428                 case LFUN_VC_REGISTER:
4429                 case LFUN_VC_RENAME:
4430                 case LFUN_VC_COPY:
4431                 case LFUN_VC_CHECK_IN:
4432                 case LFUN_VC_CHECK_OUT:
4433                 case LFUN_VC_REPO_UPDATE:
4434                 case LFUN_VC_LOCKING_TOGGLE:
4435                 case LFUN_VC_REVERT:
4436                 case LFUN_VC_UNDO_LAST:
4437                 case LFUN_VC_COMMAND:
4438                 case LFUN_VC_COMPARE:
4439                         dispatchVC(cmd, dr);
4440                         break;
4441
4442                 case LFUN_SERVER_GOTO_FILE_ROW:
4443                         if(goToFileRow(to_utf8(cmd.argument())))
4444                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4445                         break;
4446
4447                 case LFUN_LYX_ACTIVATE:
4448                         activateWindow();
4449                         break;
4450
4451                 case LFUN_FORWARD_SEARCH: {
4452                         // it seems safe to assume we have a document buffer, since
4453                         // getStatus wants one.
4454                         LATTEST(doc_buffer);
4455                         Buffer const * doc_master = doc_buffer->masterBuffer();
4456                         FileName const path(doc_master->temppath());
4457                         string const texname = doc_master->isChild(doc_buffer)
4458                                 ? DocFileName(changeExtension(
4459                                         doc_buffer->absFileName(),
4460                                                 "tex")).mangledFileName()
4461                                 : doc_buffer->latexName();
4462                         string const fulltexname =
4463                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4464                         string const mastername =
4465                                 removeExtension(doc_master->latexName());
4466                         FileName const dviname(addName(path.absFileName(),
4467                                         addExtension(mastername, "dvi")));
4468                         FileName const pdfname(addName(path.absFileName(),
4469                                         addExtension(mastername, "pdf")));
4470                         bool const have_dvi = dviname.exists();
4471                         bool const have_pdf = pdfname.exists();
4472                         if (!have_dvi && !have_pdf) {
4473                                 dr.setMessage(_("Please, preview the document first."));
4474                                 break;
4475                         }
4476                         string outname = dviname.onlyFileName();
4477                         string command = lyxrc.forward_search_dvi;
4478                         if (!have_dvi || (have_pdf &&
4479                             pdfname.lastModified() > dviname.lastModified())) {
4480                                 outname = pdfname.onlyFileName();
4481                                 command = lyxrc.forward_search_pdf;
4482                         }
4483
4484                         DocIterator cur = bv->cursor();
4485                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4486                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4487                                    << " cur:" << cur);
4488                         if (row == -1 || command.empty()) {
4489                                 dr.setMessage(_("Couldn't proceed."));
4490                                 break;
4491                         }
4492                         string texrow = convert<string>(row);
4493
4494                         command = subst(command, "$$n", texrow);
4495                         command = subst(command, "$$f", fulltexname);
4496                         command = subst(command, "$$t", texname);
4497                         command = subst(command, "$$o", outname);
4498
4499                         PathChanger p(path);
4500                         Systemcall one;
4501                         one.startscript(Systemcall::DontWait, command);
4502                         break;
4503                 }
4504
4505                 case LFUN_SPELLING_CONTINUOUSLY:
4506                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4507                         dr.screenUpdate(Update::Force);
4508                         break;
4509
4510                 default:
4511                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4512                         // let's try that:
4513                         dispatchToBufferView(cmd, dr);
4514                         break;
4515         }
4516
4517         // Part of automatic menu appearance feature.
4518         if (isFullScreen()) {
4519                 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4520                         menuBar()->hide();
4521         }
4522
4523         // Need to update bv because many LFUNs here might have destroyed it
4524         bv = currentBufferView();
4525
4526         // Clear non-empty selections
4527         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4528         if (bv) {
4529                 Cursor & cur = bv->cursor();
4530                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4531                         cur.clearSelection();
4532                 }
4533         }
4534 }
4535
4536
4537 bool GuiView::lfunUiToggle(string const & ui_component)
4538 {
4539         if (ui_component == "scrollbar") {
4540                 // hide() is of no help
4541                 if (d.current_work_area_->verticalScrollBarPolicy() ==
4542                         Qt::ScrollBarAlwaysOff)
4543
4544                         d.current_work_area_->setVerticalScrollBarPolicy(
4545                                 Qt::ScrollBarAsNeeded);
4546                 else
4547                         d.current_work_area_->setVerticalScrollBarPolicy(
4548                                 Qt::ScrollBarAlwaysOff);
4549         } else if (ui_component == "statusbar") {
4550                 statusBar()->setVisible(!statusBar()->isVisible());
4551         } else if (ui_component == "menubar") {
4552                 menuBar()->setVisible(!menuBar()->isVisible());
4553         } else
4554         if (ui_component == "frame") {
4555                 int l, t, r, b;
4556                 getContentsMargins(&l, &t, &r, &b);
4557                 //are the frames in default state?
4558                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4559                 if (l == 0) {
4560                         setContentsMargins(-2, -2, -2, -2);
4561                 } else {
4562                         setContentsMargins(0, 0, 0, 0);
4563                 }
4564         } else
4565         if (ui_component == "fullscreen") {
4566                 toggleFullScreen();
4567         } else
4568                 return false;
4569         return true;
4570 }
4571
4572
4573 void GuiView::toggleFullScreen()
4574 {
4575         if (isFullScreen()) {
4576                 for (int i = 0; i != d.splitter_->count(); ++i)
4577                         d.tabWorkArea(i)->setFullScreen(false);
4578                 setContentsMargins(0, 0, 0, 0);
4579                 setWindowState(windowState() ^ Qt::WindowFullScreen);
4580                 restoreLayout();
4581                 menuBar()->show();
4582                 statusBar()->show();
4583         } else {
4584                 // bug 5274
4585                 hideDialogs("prefs", 0);
4586                 for (int i = 0; i != d.splitter_->count(); ++i)
4587                         d.tabWorkArea(i)->setFullScreen(true);
4588                 setContentsMargins(-2, -2, -2, -2);
4589                 saveLayout();
4590                 setWindowState(windowState() ^ Qt::WindowFullScreen);
4591                 if (lyxrc.full_screen_statusbar)
4592                         statusBar()->hide();
4593                 if (lyxrc.full_screen_menubar)
4594                         menuBar()->hide();
4595                 if (lyxrc.full_screen_toolbars) {
4596                         ToolbarMap::iterator end = d.toolbars_.end();
4597                         for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4598                                 it->second->hide();
4599                 }
4600         }
4601
4602         // give dialogs like the TOC a chance to adapt
4603         updateDialogs();
4604 }
4605
4606
4607 Buffer const * GuiView::updateInset(Inset const * inset)
4608 {
4609         if (!inset)
4610                 return 0;
4611
4612         Buffer const * inset_buffer = &(inset->buffer());
4613
4614         for (int i = 0; i != d.splitter_->count(); ++i) {
4615                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4616                 if (!wa)
4617                         continue;
4618                 Buffer const * buffer = &(wa->bufferView().buffer());
4619                 if (inset_buffer == buffer)
4620                         wa->scheduleRedraw(true);
4621         }
4622         return inset_buffer;
4623 }
4624
4625
4626 void GuiView::restartCaret()
4627 {
4628         /* When we move around, or type, it's nice to be able to see
4629          * the caret immediately after the keypress.
4630          */
4631         if (d.current_work_area_)
4632                 d.current_work_area_->startBlinkingCaret();
4633
4634         // Take this occasion to update the other GUI elements.
4635         updateDialogs();
4636         updateStatusBar();
4637 }
4638
4639
4640 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4641 {
4642         if (d.current_work_area_)
4643                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4644 }
4645
4646 namespace {
4647
4648 // This list should be kept in sync with the list of insets in
4649 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
4650 // dialog should have the same name as the inset.
4651 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4652 // docs in LyXAction.cpp.
4653
4654 char const * const dialognames[] = {
4655
4656 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4657 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4658 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4659 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4660 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4661 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4662 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4663 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4664
4665 char const * const * const end_dialognames =
4666         dialognames + (sizeof(dialognames) / sizeof(char *));
4667
4668 class cmpCStr {
4669 public:
4670         cmpCStr(char const * name) : name_(name) {}
4671         bool operator()(char const * other) {
4672                 return strcmp(other, name_) == 0;
4673         }
4674 private:
4675         char const * name_;
4676 };
4677
4678
4679 bool isValidName(string const & name)
4680 {
4681         return find_if(dialognames, end_dialognames,
4682                                 cmpCStr(name.c_str())) != end_dialognames;
4683 }
4684
4685 } // namespace
4686
4687
4688 void GuiView::resetDialogs()
4689 {
4690         // Make sure that no LFUN uses any GuiView.
4691         guiApp->setCurrentView(0);
4692         saveLayout();
4693         saveUISettings();
4694         menuBar()->clear();
4695         constructToolbars();
4696         guiApp->menus().fillMenuBar(menuBar(), this, false);
4697         d.layout_->updateContents(true);
4698         // Now update controls with current buffer.
4699         guiApp->setCurrentView(this);
4700         restoreLayout();
4701         restartCaret();
4702 }
4703
4704
4705 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4706 {
4707         if (!isValidName(name))
4708                 return 0;
4709
4710         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4711
4712         if (it != d.dialogs_.end()) {
4713                 if (hide_it)
4714                         it->second->hideView();
4715                 return it->second.get();
4716         }
4717
4718         Dialog * dialog = build(name);
4719         d.dialogs_[name].reset(dialog);
4720         if (lyxrc.allow_geometry_session)
4721                 dialog->restoreSession();
4722         if (hide_it)
4723                 dialog->hideView();
4724         return dialog;
4725 }
4726
4727
4728 void GuiView::showDialog(string const & name, string const & sdata,
4729         Inset * inset)
4730 {
4731         triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4732 }
4733
4734
4735 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4736         Inset * inset)
4737 {
4738         if (d.in_show_)
4739                 return;
4740
4741         const string name = fromqstr(qname);
4742         const string sdata = fromqstr(qdata);
4743
4744         d.in_show_ = true;
4745         try {
4746                 Dialog * dialog = findOrBuild(name, false);
4747                 if (dialog) {
4748                         bool const visible = dialog->isVisibleView();
4749                         dialog->showData(sdata);
4750                         if (currentBufferView())
4751                                 currentBufferView()->editInset(name, inset);
4752                         // We only set the focus to the new dialog if it was not yet
4753                         // visible in order not to change the existing previous behaviour
4754                         if (visible) {
4755                                 // activateWindow is needed for floating dockviews
4756                                 dialog->asQWidget()->raise();
4757                                 dialog->asQWidget()->activateWindow();
4758                                 dialog->asQWidget()->setFocus();
4759                         }
4760                 }
4761         }
4762         catch (ExceptionMessage const & ex) {
4763                 d.in_show_ = false;
4764                 throw ex;
4765         }
4766         d.in_show_ = false;
4767 }
4768
4769
4770 bool GuiView::isDialogVisible(string const & name) const
4771 {
4772         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4773         if (it == d.dialogs_.end())
4774                 return false;
4775         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4776 }
4777
4778
4779 void GuiView::hideDialog(string const & name, Inset * inset)
4780 {
4781         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4782         if (it == d.dialogs_.end())
4783                 return;
4784
4785         if (inset) {
4786                 if (!currentBufferView())
4787                         return;
4788                 if (inset != currentBufferView()->editedInset(name))
4789                         return;
4790         }
4791
4792         Dialog * const dialog = it->second.get();
4793         if (dialog->isVisibleView())
4794                 dialog->hideView();
4795         if (currentBufferView())
4796                 currentBufferView()->editInset(name, 0);
4797 }
4798
4799
4800 void GuiView::disconnectDialog(string const & name)
4801 {
4802         if (!isValidName(name))
4803                 return;
4804         if (currentBufferView())
4805                 currentBufferView()->editInset(name, 0);
4806 }
4807
4808
4809 void GuiView::hideAll() const
4810 {
4811         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
4812         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4813
4814         for(; it != end; ++it)
4815                 it->second->hideView();
4816 }
4817
4818
4819 void GuiView::updateDialogs()
4820 {
4821         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
4822         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4823
4824         for(; it != end; ++it) {
4825                 Dialog * dialog = it->second.get();
4826                 if (dialog) {
4827                         if (dialog->needBufferOpen() && !documentBufferView())
4828                                 hideDialog(fromqstr(dialog->name()), 0);
4829                         else if (dialog->isVisibleView())
4830                                 dialog->checkStatus();
4831                 }
4832         }
4833         updateToolbars();
4834         updateLayoutList();
4835 }
4836
4837 Dialog * createDialog(GuiView & lv, string const & name);
4838
4839 // will be replaced by a proper factory...
4840 Dialog * createGuiAbout(GuiView & lv);
4841 Dialog * createGuiBibtex(GuiView & lv);
4842 Dialog * createGuiChanges(GuiView & lv);
4843 Dialog * createGuiCharacter(GuiView & lv);
4844 Dialog * createGuiCitation(GuiView & lv);
4845 Dialog * createGuiCompare(GuiView & lv);
4846 Dialog * createGuiCompareHistory(GuiView & lv);
4847 Dialog * createGuiDelimiter(GuiView & lv);
4848 Dialog * createGuiDocument(GuiView & lv);
4849 Dialog * createGuiErrorList(GuiView & lv);
4850 Dialog * createGuiExternal(GuiView & lv);
4851 Dialog * createGuiGraphics(GuiView & lv);
4852 Dialog * createGuiInclude(GuiView & lv);
4853 Dialog * createGuiIndex(GuiView & lv);
4854 Dialog * createGuiListings(GuiView & lv);
4855 Dialog * createGuiLog(GuiView & lv);
4856 Dialog * createGuiLyXFiles(GuiView & lv);
4857 Dialog * createGuiMathMatrix(GuiView & lv);
4858 Dialog * createGuiNote(GuiView & lv);
4859 Dialog * createGuiParagraph(GuiView & lv);
4860 Dialog * createGuiPhantom(GuiView & lv);
4861 Dialog * createGuiPreferences(GuiView & lv);
4862 Dialog * createGuiPrint(GuiView & lv);
4863 Dialog * createGuiPrintindex(GuiView & lv);
4864 Dialog * createGuiRef(GuiView & lv);
4865 Dialog * createGuiSearch(GuiView & lv);
4866 Dialog * createGuiSearchAdv(GuiView & lv);
4867 Dialog * createGuiSendTo(GuiView & lv);
4868 Dialog * createGuiShowFile(GuiView & lv);
4869 Dialog * createGuiSpellchecker(GuiView & lv);
4870 Dialog * createGuiSymbols(GuiView & lv);
4871 Dialog * createGuiTabularCreate(GuiView & lv);
4872 Dialog * createGuiTexInfo(GuiView & lv);
4873 Dialog * createGuiToc(GuiView & lv);
4874 Dialog * createGuiThesaurus(GuiView & lv);
4875 Dialog * createGuiViewSource(GuiView & lv);
4876 Dialog * createGuiWrap(GuiView & lv);
4877 Dialog * createGuiProgressView(GuiView & lv);
4878
4879
4880
4881 Dialog * GuiView::build(string const & name)
4882 {
4883         LASSERT(isValidName(name), return 0);
4884
4885         Dialog * dialog = createDialog(*this, name);
4886         if (dialog)
4887                 return dialog;
4888
4889         if (name == "aboutlyx")
4890                 return createGuiAbout(*this);
4891         if (name == "bibtex")
4892                 return createGuiBibtex(*this);
4893         if (name == "changes")
4894                 return createGuiChanges(*this);
4895         if (name == "character")
4896                 return createGuiCharacter(*this);
4897         if (name == "citation")
4898                 return createGuiCitation(*this);
4899         if (name == "compare")
4900                 return createGuiCompare(*this);
4901         if (name == "comparehistory")
4902                 return createGuiCompareHistory(*this);
4903         if (name == "document")
4904                 return createGuiDocument(*this);
4905         if (name == "errorlist")
4906                 return createGuiErrorList(*this);
4907         if (name == "external")
4908                 return createGuiExternal(*this);
4909         if (name == "file")
4910                 return createGuiShowFile(*this);
4911         if (name == "findreplace")
4912                 return createGuiSearch(*this);
4913         if (name == "findreplaceadv")
4914                 return createGuiSearchAdv(*this);
4915         if (name == "graphics")
4916                 return createGuiGraphics(*this);
4917         if (name == "include")
4918                 return createGuiInclude(*this);
4919         if (name == "index")
4920                 return createGuiIndex(*this);
4921         if (name == "index_print")
4922                 return createGuiPrintindex(*this);
4923         if (name == "listings")
4924                 return createGuiListings(*this);
4925         if (name == "log")
4926                 return createGuiLog(*this);
4927         if (name == "lyxfiles")
4928                 return createGuiLyXFiles(*this);
4929         if (name == "mathdelimiter")
4930                 return createGuiDelimiter(*this);
4931         if (name == "mathmatrix")
4932                 return createGuiMathMatrix(*this);
4933         if (name == "note")
4934                 return createGuiNote(*this);
4935         if (name == "paragraph")
4936                 return createGuiParagraph(*this);
4937         if (name == "phantom")
4938                 return createGuiPhantom(*this);
4939         if (name == "prefs")
4940                 return createGuiPreferences(*this);
4941         if (name == "ref")
4942                 return createGuiRef(*this);
4943         if (name == "sendto")
4944                 return createGuiSendTo(*this);
4945         if (name == "spellchecker")
4946                 return createGuiSpellchecker(*this);
4947         if (name == "symbols")
4948                 return createGuiSymbols(*this);
4949         if (name == "tabularcreate")
4950                 return createGuiTabularCreate(*this);
4951         if (name == "texinfo")
4952                 return createGuiTexInfo(*this);
4953         if (name == "thesaurus")
4954                 return createGuiThesaurus(*this);
4955         if (name == "toc")
4956                 return createGuiToc(*this);
4957         if (name == "view-source")
4958                 return createGuiViewSource(*this);
4959         if (name == "wrap")
4960                 return createGuiWrap(*this);
4961         if (name == "progress")
4962                 return createGuiProgressView(*this);
4963
4964         return 0;
4965 }
4966
4967
4968 SEMenu::SEMenu(QWidget * parent)
4969 {
4970         QAction * action = addAction(qt_("Disable Shell Escape"));
4971         connect(action, SIGNAL(triggered()),
4972                 parent, SLOT(disableShellEscape()));
4973 }
4974
4975 } // namespace frontend
4976 } // namespace lyx
4977
4978 #include "moc_GuiView.cpp"