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