]> git.lyx.org Git - features.git/blob - src/frontends/qt4/GuiView.cpp
Revert "Mark some intentional fall-throughs (in a way understandable to gcc)"
[features.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                 // fall through to set "enable"
1993         case LFUN_DIALOG_SHOW: {
1994                 string const name = cmd.getArg(0);
1995                 if (!doc_buffer)
1996                         enable = name == "aboutlyx"
1997                                 || name == "file" //FIXME: should be removed.
1998                                 || name == "prefs"
1999                                 || name == "texinfo"
2000                                 || name == "progress"
2001                                 || name == "compare";
2002                 else if (name == "character" || name == "symbols"
2003                         || name == "mathdelimiter" || name == "mathmatrix") {
2004                         if (!buf || buf->isReadonly())
2005                                 enable = false;
2006                         else {
2007                                 Cursor const & cur = currentBufferView()->cursor();
2008                                 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2009                         }
2010                 }
2011                 else if (name == "latexlog")
2012                         enable = FileName(doc_buffer->logName()).isReadableFile();
2013                 else if (name == "spellchecker")
2014                         enable = theSpellChecker()
2015                                 && !doc_buffer->isReadonly()
2016                                 && !doc_buffer->text().empty();
2017                 else if (name == "vclog")
2018                         enable = doc_buffer->lyxvc().inUse();
2019                 break;
2020         }
2021
2022         case LFUN_DIALOG_UPDATE: {
2023                 string const name = cmd.getArg(0);
2024                 if (!buf)
2025                         enable = name == "prefs";
2026                 break;
2027         }
2028
2029         case LFUN_COMMAND_EXECUTE:
2030         case LFUN_MESSAGE:
2031         case LFUN_MENU_OPEN:
2032                 // Nothing to check.
2033                 break;
2034
2035         case LFUN_COMPLETION_INLINE:
2036                 if (!d.current_work_area_
2037                         || !d.current_work_area_->completer().inlinePossible(
2038                         currentBufferView()->cursor()))
2039                         enable = false;
2040                 break;
2041
2042         case LFUN_COMPLETION_POPUP:
2043                 if (!d.current_work_area_
2044                         || !d.current_work_area_->completer().popupPossible(
2045                         currentBufferView()->cursor()))
2046                         enable = false;
2047                 break;
2048
2049         case LFUN_COMPLETE:
2050                 if (!d.current_work_area_
2051                         || !d.current_work_area_->completer().inlinePossible(
2052                         currentBufferView()->cursor()))
2053                         enable = false;
2054                 break;
2055
2056         case LFUN_COMPLETION_ACCEPT:
2057                 if (!d.current_work_area_
2058                         || (!d.current_work_area_->completer().popupVisible()
2059                         && !d.current_work_area_->completer().inlineVisible()
2060                         && !d.current_work_area_->completer().completionAvailable()))
2061                         enable = false;
2062                 break;
2063
2064         case LFUN_COMPLETION_CANCEL:
2065                 if (!d.current_work_area_
2066                         || (!d.current_work_area_->completer().popupVisible()
2067                         && !d.current_work_area_->completer().inlineVisible()))
2068                         enable = false;
2069                 break;
2070
2071         case LFUN_BUFFER_ZOOM_OUT:
2072         case LFUN_BUFFER_ZOOM_IN: {
2073                 // only diff between these two is that the default for ZOOM_OUT
2074                 // is a neg. number
2075                 bool const neg_zoom =
2076                         convert<int>(cmd.argument()) < 0 ||
2077                         (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2078                 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2079                         docstring const msg =
2080                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2081                         flag.message(msg);
2082                         enable = false;
2083                 } else
2084                         enable = doc_buffer;
2085                 break;
2086         }
2087
2088         case LFUN_BUFFER_ZOOM: {
2089                 bool const less_than_min_zoom =
2090                         !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2091                 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2092                         docstring const msg =
2093                                 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2094                         flag.message(msg);
2095                         enable = false;
2096                 }
2097                 else
2098                         enable = doc_buffer;
2099                 break;
2100         }
2101
2102         case LFUN_BUFFER_MOVE_NEXT:
2103         case LFUN_BUFFER_MOVE_PREVIOUS:
2104                 // we do not cycle when moving
2105         case LFUN_BUFFER_NEXT:
2106         case LFUN_BUFFER_PREVIOUS:
2107                 // because we cycle, it doesn't matter whether on first or last
2108                 enable = (d.currentTabWorkArea()->count() > 1);
2109                 break;
2110         case LFUN_BUFFER_SWITCH:
2111                 // toggle on the current buffer, but do not toggle off
2112                 // the other ones (is that a good idea?)
2113                 if (doc_buffer
2114                         && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2115                         flag.setOnOff(true);
2116                 break;
2117
2118         case LFUN_VC_REGISTER:
2119                 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2120                 break;
2121         case LFUN_VC_RENAME:
2122                 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2123                 break;
2124         case LFUN_VC_COPY:
2125                 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2126                 break;
2127         case LFUN_VC_CHECK_IN:
2128                 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2129                 break;
2130         case LFUN_VC_CHECK_OUT:
2131                 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2132                 break;
2133         case LFUN_VC_LOCKING_TOGGLE:
2134                 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2135                         && doc_buffer->lyxvc().lockingToggleEnabled();
2136                 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2137                 break;
2138         case LFUN_VC_REVERT:
2139                 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2140                         && !doc_buffer->hasReadonlyFlag();
2141                 break;
2142         case LFUN_VC_UNDO_LAST:
2143                 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2144                 break;
2145         case LFUN_VC_REPO_UPDATE:
2146                 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2147                 break;
2148         case LFUN_VC_COMMAND: {
2149                 if (cmd.argument().empty())
2150                         enable = false;
2151                 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2152                         enable = false;
2153                 break;
2154         }
2155         case LFUN_VC_COMPARE:
2156                 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2157                 break;
2158
2159         case LFUN_SERVER_GOTO_FILE_ROW:
2160         case LFUN_LYX_ACTIVATE:
2161                 break;
2162         case LFUN_FORWARD_SEARCH:
2163                 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2164                 break;
2165
2166         case LFUN_FILE_INSERT_PLAINTEXT:
2167         case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2168                 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2169                 break;
2170
2171         case LFUN_SPELLING_CONTINUOUSLY:
2172                 flag.setOnOff(lyxrc.spellcheck_continuously);
2173                 break;
2174
2175         default:
2176                 return false;
2177         }
2178
2179         if (!enable)
2180                 flag.setEnabled(false);
2181
2182         return true;
2183 }
2184
2185
2186 static FileName selectTemplateFile()
2187 {
2188         FileDialog dlg(qt_("Select template file"));
2189         dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2190         dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2191
2192         FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2193                                  QStringList(qt_("LyX Documents (*.lyx)")));
2194
2195         if (result.first == FileDialog::Later)
2196                 return FileName();
2197         if (result.second.isEmpty())
2198                 return FileName();
2199         return FileName(fromqstr(result.second));
2200 }
2201
2202
2203 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2204 {
2205         setBusy(true);
2206
2207         Buffer * newBuffer = 0;
2208         try {
2209                 newBuffer = checkAndLoadLyXFile(filename);
2210         } catch (ExceptionMessage const & e) {
2211                 setBusy(false);
2212                 throw(e);
2213         }
2214         setBusy(false);
2215
2216         if (!newBuffer) {
2217                 message(_("Document not loaded."));
2218                 return 0;
2219         }
2220
2221         setBuffer(newBuffer);
2222         newBuffer->errors("Parse");
2223
2224         if (tolastfiles)
2225                 theSession().lastFiles().add(filename);
2226
2227         return newBuffer;
2228 }
2229
2230
2231 void GuiView::openDocument(string const & fname)
2232 {
2233         string initpath = lyxrc.document_path;
2234
2235         if (documentBufferView()) {
2236                 string const trypath = documentBufferView()->buffer().filePath();
2237                 // If directory is writeable, use this as default.
2238                 if (FileName(trypath).isDirWritable())
2239                         initpath = trypath;
2240         }
2241
2242         string filename;
2243
2244         if (fname.empty()) {
2245                 FileDialog dlg(qt_("Select document to open"));
2246                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2247                 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2248
2249                 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2250                 FileDialog::Result result =
2251                         dlg.open(toqstr(initpath), filter);
2252
2253                 if (result.first == FileDialog::Later)
2254                         return;
2255
2256                 filename = fromqstr(result.second);
2257
2258                 // check selected filename
2259                 if (filename.empty()) {
2260                         message(_("Canceled."));
2261                         return;
2262                 }
2263         } else
2264                 filename = fname;
2265
2266         // get absolute path of file and add ".lyx" to the filename if
2267         // necessary.
2268         FileName const fullname =
2269                         fileSearch(string(), filename, "lyx", support::may_not_exist);
2270         if (!fullname.empty())
2271                 filename = fullname.absFileName();
2272
2273         if (!fullname.onlyPath().isDirectory()) {
2274                 Alert::warning(_("Invalid filename"),
2275                                 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2276                                 from_utf8(fullname.absFileName())));
2277                 return;
2278         }
2279
2280         // if the file doesn't exist and isn't already open (bug 6645),
2281         // let the user create one
2282         if (!fullname.exists() && !theBufferList().exists(fullname) &&
2283             !LyXVC::file_not_found_hook(fullname)) {
2284                 // the user specifically chose this name. Believe him.
2285                 Buffer * const b = newFile(filename, string(), true);
2286                 if (b)
2287                         setBuffer(b);
2288                 return;
2289         }
2290
2291         docstring const disp_fn = makeDisplayPath(filename);
2292         message(bformat(_("Opening document %1$s..."), disp_fn));
2293
2294         docstring str2;
2295         Buffer * buf = loadDocument(fullname);
2296         if (buf) {
2297                 str2 = bformat(_("Document %1$s opened."), disp_fn);
2298                 if (buf->lyxvc().inUse())
2299                         str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2300                                 " " + _("Version control detected.");
2301         } else {
2302                 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2303         }
2304         message(str2);
2305 }
2306
2307 // FIXME: clean that
2308 static bool import(GuiView * lv, FileName const & filename,
2309         string const & format, ErrorList & errorList)
2310 {
2311         FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2312
2313         string loader_format;
2314         vector<string> loaders = theConverters().loaders();
2315         if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2316                 vector<string>::const_iterator it = loaders.begin();
2317                 vector<string>::const_iterator en = loaders.end();
2318                 for (; it != en; ++it) {
2319                         if (!theConverters().isReachable(format, *it))
2320                                 continue;
2321
2322                         string const tofile =
2323                                 support::changeExtension(filename.absFileName(),
2324                                 theFormats().extension(*it));
2325                         if (!theConverters().convert(0, filename, FileName(tofile),
2326                                 filename, format, *it, errorList))
2327                                 return false;
2328                         loader_format = *it;
2329                         break;
2330                 }
2331                 if (loader_format.empty()) {
2332                         frontend::Alert::error(_("Couldn't import file"),
2333                                          bformat(_("No information for importing the format %1$s."),
2334                                          theFormats().prettyName(format)));
2335                         return false;
2336                 }
2337         } else
2338                 loader_format = format;
2339
2340         if (loader_format == "lyx") {
2341                 Buffer * buf = lv->loadDocument(lyxfile);
2342                 if (!buf)
2343                         return false;
2344         } else {
2345                 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2346                 if (!b)
2347                         return false;
2348                 lv->setBuffer(b);
2349                 bool as_paragraphs = loader_format == "textparagraph";
2350                 string filename2 = (loader_format == format) ? filename.absFileName()
2351                         : support::changeExtension(filename.absFileName(),
2352                                           theFormats().extension(loader_format));
2353                 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2354                         as_paragraphs);
2355                 guiApp->setCurrentView(lv);
2356                 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2357         }
2358
2359         return true;
2360 }
2361
2362
2363 void GuiView::importDocument(string const & argument)
2364 {
2365         string format;
2366         string filename = split(argument, format, ' ');
2367
2368         LYXERR(Debug::INFO, format << " file: " << filename);
2369
2370         // need user interaction
2371         if (filename.empty()) {
2372                 string initpath = lyxrc.document_path;
2373                 if (documentBufferView()) {
2374                         string const trypath = documentBufferView()->buffer().filePath();
2375                         // If directory is writeable, use this as default.
2376                         if (FileName(trypath).isDirWritable())
2377                                 initpath = trypath;
2378                 }
2379
2380                 docstring const text = bformat(_("Select %1$s file to import"),
2381                         theFormats().prettyName(format));
2382
2383                 FileDialog dlg(toqstr(text));
2384                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2385                 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2386
2387                 docstring filter = theFormats().prettyName(format);
2388                 filter += " (*.{";
2389                 // FIXME UNICODE
2390                 filter += from_utf8(theFormats().extensions(format));
2391                 filter += "})";
2392
2393                 FileDialog::Result result =
2394                         dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2395
2396                 if (result.first == FileDialog::Later)
2397                         return;
2398
2399                 filename = fromqstr(result.second);
2400
2401                 // check selected filename
2402                 if (filename.empty())
2403                         message(_("Canceled."));
2404         }
2405
2406         if (filename.empty())
2407                 return;
2408
2409         // get absolute path of file
2410         FileName const fullname(support::makeAbsPath(filename));
2411
2412         // Can happen if the user entered a path into the dialog
2413         // (see bug #7437)
2414         if (fullname.onlyFileName().empty()) {
2415                 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2416                                           "Aborting import."),
2417                                         from_utf8(fullname.absFileName()));
2418                 frontend::Alert::error(_("File name error"), msg);
2419                 message(_("Canceled."));
2420                 return;
2421         }
2422
2423
2424         FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2425
2426         // Check if the document already is open
2427         Buffer * buf = theBufferList().getBuffer(lyxfile);
2428         if (buf) {
2429                 setBuffer(buf);
2430                 if (!closeBuffer()) {
2431                         message(_("Canceled."));
2432                         return;
2433                 }
2434         }
2435
2436         docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2437
2438         // if the file exists already, and we didn't do
2439         // -i lyx thefile.lyx, warn
2440         if (lyxfile.exists() && fullname != lyxfile) {
2441
2442                 docstring text = bformat(_("The document %1$s already exists.\n\n"
2443                         "Do you want to overwrite that document?"), displaypath);
2444                 int const ret = Alert::prompt(_("Overwrite document?"),
2445                         text, 0, 1, _("&Overwrite"), _("&Cancel"));
2446
2447                 if (ret == 1) {
2448                         message(_("Canceled."));
2449                         return;
2450                 }
2451         }
2452
2453         message(bformat(_("Importing %1$s..."), displaypath));
2454         ErrorList errorList;
2455         if (import(this, fullname, format, errorList))
2456                 message(_("imported."));
2457         else
2458                 message(_("file not imported!"));
2459
2460         // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2461 }
2462
2463
2464 void GuiView::newDocument(string const & filename, bool from_template)
2465 {
2466         FileName initpath(lyxrc.document_path);
2467         if (documentBufferView()) {
2468                 FileName const trypath(documentBufferView()->buffer().filePath());
2469                 // If directory is writeable, use this as default.
2470                 if (trypath.isDirWritable())
2471                         initpath = trypath;
2472         }
2473
2474         string templatefile;
2475         if (from_template) {
2476                 templatefile = selectTemplateFile().absFileName();
2477                 if (templatefile.empty())
2478                         return;
2479         }
2480
2481         Buffer * b;
2482         if (filename.empty())
2483                 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2484         else
2485                 b = newFile(filename, templatefile, true);
2486
2487         if (b)
2488                 setBuffer(b);
2489
2490         // If no new document could be created, it is unsure
2491         // whether there is a valid BufferView.
2492         if (currentBufferView())
2493                 // Ensure the cursor is correctly positioned on screen.
2494                 currentBufferView()->showCursor();
2495 }
2496
2497
2498 void GuiView::insertLyXFile(docstring const & fname)
2499 {
2500         BufferView * bv = documentBufferView();
2501         if (!bv)
2502                 return;
2503
2504         // FIXME UNICODE
2505         FileName filename(to_utf8(fname));
2506         if (filename.empty()) {
2507                 // Launch a file browser
2508                 // FIXME UNICODE
2509                 string initpath = lyxrc.document_path;
2510                 string const trypath = bv->buffer().filePath();
2511                 // If directory is writeable, use this as default.
2512                 if (FileName(trypath).isDirWritable())
2513                         initpath = trypath;
2514
2515                 // FIXME UNICODE
2516                 FileDialog dlg(qt_("Select LyX document to insert"));
2517                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2518                 dlg.setButton2(qt_("Examples|#E#e"), toqstr(lyxrc.example_path));
2519
2520                 FileDialog::Result result = dlg.open(toqstr(initpath),
2521                                          QStringList(qt_("LyX Documents (*.lyx)")));
2522
2523                 if (result.first == FileDialog::Later)
2524                         return;
2525
2526                 // FIXME UNICODE
2527                 filename.set(fromqstr(result.second));
2528
2529                 // check selected filename
2530                 if (filename.empty()) {
2531                         // emit message signal.
2532                         message(_("Canceled."));
2533                         return;
2534                 }
2535         }
2536
2537         bv->insertLyXFile(filename);
2538         bv->buffer().errors("Parse");
2539 }
2540
2541
2542 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2543 {
2544         FileName fname = b.fileName();
2545         FileName const oldname = fname;
2546
2547         if (!newname.empty()) {
2548                 // FIXME UNICODE
2549                 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2550         } else {
2551                 // Switch to this Buffer.
2552                 setBuffer(&b);
2553
2554                 // No argument? Ask user through dialog.
2555                 // FIXME UNICODE
2556                 FileDialog dlg(qt_("Choose a filename to save document as"));
2557                 dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2558                 dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path));
2559
2560                 if (!isLyXFileName(fname.absFileName()))
2561                         fname.changeExtension(".lyx");
2562
2563                 FileDialog::Result result =
2564                         dlg.save(toqstr(fname.onlyPath().absFileName()),
2565                                    QStringList(qt_("LyX Documents (*.lyx)")),
2566                                          toqstr(fname.onlyFileName()));
2567
2568                 if (result.first == FileDialog::Later)
2569                         return false;
2570
2571                 fname.set(fromqstr(result.second));
2572
2573                 if (fname.empty())
2574                         return false;
2575
2576                 if (!isLyXFileName(fname.absFileName()))
2577                         fname.changeExtension(".lyx");
2578         }
2579
2580         // fname is now the new Buffer location.
2581
2582         // if there is already a Buffer open with this name, we do not want
2583         // to have another one. (the second test makes sure we're not just
2584         // trying to overwrite ourselves, which is fine.)
2585         if (theBufferList().exists(fname) && fname != oldname
2586                   && theBufferList().getBuffer(fname) != &b) {
2587                 docstring const text =
2588                         bformat(_("The file\n%1$s\nis already open in your current session.\n"
2589                             "Please close it before attempting to overwrite it.\n"
2590                             "Do you want to choose a new filename?"),
2591                                 from_utf8(fname.absFileName()));
2592                 int const ret = Alert::prompt(_("Chosen File Already Open"),
2593                         text, 0, 1, _("&Rename"), _("&Cancel"));
2594                 switch (ret) {
2595                 case 0: return renameBuffer(b, docstring(), kind);
2596                 case 1: return false;
2597                 }
2598                 //return false;
2599         }
2600
2601         bool const existsLocal = fname.exists();
2602         bool const existsInVC = LyXVC::fileInVC(fname);
2603         if (existsLocal || existsInVC) {
2604                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2605                 if (kind != LV_WRITE_AS && existsInVC) {
2606                         // renaming to a name that is already in VC
2607                         // would not work
2608                         docstring text = bformat(_("The document %1$s "
2609                                         "is already registered.\n\n"
2610                                         "Do you want to choose a new name?"),
2611                                 file);
2612                         docstring const title = (kind == LV_VC_RENAME) ?
2613                                 _("Rename document?") : _("Copy document?");
2614                         docstring const button = (kind == LV_VC_RENAME) ?
2615                                 _("&Rename") : _("&Copy");
2616                         int const ret = Alert::prompt(title, text, 0, 1,
2617                                 button, _("&Cancel"));
2618                         switch (ret) {
2619                         case 0: return renameBuffer(b, docstring(), kind);
2620                         case 1: return false;
2621                         }
2622                 }
2623
2624                 if (existsLocal) {
2625                         docstring text = bformat(_("The document %1$s "
2626                                         "already exists.\n\n"
2627                                         "Do you want to overwrite that document?"),
2628                                 file);
2629                         int const ret = Alert::prompt(_("Overwrite document?"),
2630                                         text, 0, 2, _("&Overwrite"),
2631                                         _("&Rename"), _("&Cancel"));
2632                         switch (ret) {
2633                         case 0: break;
2634                         case 1: return renameBuffer(b, docstring(), kind);
2635                         case 2: return false;
2636                         }
2637                 }
2638         }
2639
2640         switch (kind) {
2641         case LV_VC_RENAME: {
2642                 string msg = b.lyxvc().rename(fname);
2643                 if (msg.empty())
2644                         return false;
2645                 message(from_utf8(msg));
2646                 break;
2647         }
2648         case LV_VC_COPY: {
2649                 string msg = b.lyxvc().copy(fname);
2650                 if (msg.empty())
2651                         return false;
2652                 message(from_utf8(msg));
2653                 break;
2654         }
2655         case LV_WRITE_AS:
2656                 break;
2657         }
2658         // LyXVC created the file already in case of LV_VC_RENAME or
2659         // LV_VC_COPY, but call saveBuffer() nevertheless to get
2660         // relative paths of included stuff right if we moved e.g. from
2661         // /a/b.lyx to /a/c/b.lyx.
2662
2663         bool const saved = saveBuffer(b, fname);
2664         if (saved)
2665                 b.reload();
2666         return saved;
2667 }
2668
2669
2670 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2671 {
2672         FileName fname = b.fileName();
2673
2674         FileDialog dlg(qt_("Choose a filename to export the document as"));
2675         dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path));
2676
2677         QStringList types;
2678         QString const anyformat = qt_("Guess from extension (*.*)");
2679         types << anyformat;
2680
2681         vector<Format const *> export_formats;
2682         for (Format const & f : theFormats())
2683                 if (f.documentFormat())
2684                         export_formats.push_back(&f);
2685         sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2686         map<QString, string> fmap;
2687         QString filter;
2688         string ext;
2689         for (Format const * f : export_formats) {
2690                 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2691                 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2692                                                      loc_prettyname,
2693                                                      from_ascii(f->extension())));
2694                 types << loc_filter;
2695                 fmap[loc_filter] = f->name();
2696                 if (from_ascii(f->name()) == iformat) {
2697                         filter = loc_filter;
2698                         ext = f->extension();
2699                 }
2700         }
2701         string ofname = fname.onlyFileName();
2702         if (!ext.empty())
2703                 ofname = support::changeExtension(ofname, ext);
2704         FileDialog::Result result =
2705                 dlg.save(toqstr(fname.onlyPath().absFileName()),
2706                          types,
2707                          toqstr(ofname),
2708                          &filter);
2709         if (result.first != FileDialog::Chosen)
2710                 return false;
2711
2712         string fmt_name;
2713         fname.set(fromqstr(result.second));
2714         if (filter == anyformat)
2715                 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2716         else
2717                 fmt_name = fmap[filter];
2718         LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2719                << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2720
2721         if (fmt_name.empty() || fname.empty())
2722                 return false;
2723
2724         // fname is now the new Buffer location.
2725         if (FileName(fname).exists()) {
2726                 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2727                 docstring text = bformat(_("The document %1$s already "
2728                                            "exists.\n\nDo you want to "
2729                                            "overwrite that document?"),
2730                                          file);
2731                 int const ret = Alert::prompt(_("Overwrite document?"),
2732                         text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2733                 switch (ret) {
2734                 case 0: break;
2735                 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2736                 case 2: return false;
2737                 }
2738         }
2739
2740         FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2741         DispatchResult dr;
2742         dispatch(cmd, dr);
2743         return dr.dispatched();
2744 }
2745
2746
2747 bool GuiView::saveBuffer(Buffer & b)
2748 {
2749         return saveBuffer(b, FileName());
2750 }
2751
2752
2753 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2754 {
2755         if (workArea(b) && workArea(b)->inDialogMode())
2756                 return true;
2757
2758         if (fn.empty() && b.isUnnamed())
2759                 return renameBuffer(b, docstring());
2760
2761         bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2762         if (success) {
2763                 theSession().lastFiles().add(b.fileName());
2764                 return true;
2765         }
2766
2767         // Switch to this Buffer.
2768         setBuffer(&b);
2769
2770         // FIXME: we don't tell the user *WHY* the save failed !!
2771         docstring const file = makeDisplayPath(b.absFileName(), 30);
2772         docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2773                                    "Do you want to rename the document and "
2774                                    "try again?"), file);
2775         int const ret = Alert::prompt(_("Rename and save?"),
2776                 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2777         switch (ret) {
2778         case 0:
2779                 if (!renameBuffer(b, docstring()))
2780                         return false;
2781                 break;
2782         case 1:
2783                 break;
2784         case 2:
2785                 return false;
2786         }
2787
2788         return saveBuffer(b, fn);
2789 }
2790
2791
2792 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2793 {
2794         return closeWorkArea(wa, false);
2795 }
2796
2797
2798 // We only want to close the buffer if it is not visible in other workareas
2799 // of the same view, nor in other views, and if this is not a child
2800 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2801 {
2802         Buffer & buf = wa->bufferView().buffer();
2803
2804         bool last_wa = d.countWorkAreasOf(buf) == 1
2805                 && !inOtherView(buf) && !buf.parent();
2806
2807         bool close_buffer = last_wa;
2808
2809         if (last_wa) {
2810                 if (lyxrc.close_buffer_with_last_view == "yes")
2811                         ; // Nothing to do
2812                 else if (lyxrc.close_buffer_with_last_view == "no")
2813                         close_buffer = false;
2814                 else {
2815                         docstring file;
2816                         if (buf.isUnnamed())
2817                                 file = from_utf8(buf.fileName().onlyFileName());
2818                         else
2819                                 file = buf.fileName().displayName(30);
2820                         docstring const text = bformat(
2821                                 _("Last view on document %1$s is being closed.\n"
2822                                   "Would you like to close or hide the document?\n"
2823                                   "\n"
2824                                   "Hidden documents can be displayed back through\n"
2825                                   "the menu: View->Hidden->...\n"
2826                                   "\n"
2827                                   "To remove this question, set your preference in:\n"
2828                                   "  Tools->Preferences->Look&Feel->UserInterface\n"
2829                                 ), file);
2830                         int ret = Alert::prompt(_("Close or hide document?"),
2831                                 text, 0, 1, _("&Close"), _("&Hide"));
2832                         close_buffer = (ret == 0);
2833                 }
2834         }
2835
2836         return closeWorkArea(wa, close_buffer);
2837 }
2838
2839
2840 bool GuiView::closeBuffer()
2841 {
2842         GuiWorkArea * wa = currentMainWorkArea();
2843         // coverity complained about this
2844         // it seems unnecessary, but perhaps is worth the check
2845         LASSERT(wa, return false);
2846
2847         setCurrentWorkArea(wa);
2848         Buffer & buf = wa->bufferView().buffer();
2849         return closeWorkArea(wa, !buf.parent());
2850 }
2851
2852
2853 void GuiView::writeSession() const {
2854         GuiWorkArea const * active_wa = currentMainWorkArea();
2855         for (int i = 0; i < d.splitter_->count(); ++i) {
2856                 TabWorkArea * twa = d.tabWorkArea(i);
2857                 for (int j = 0; j < twa->count(); ++j) {
2858                         GuiWorkArea * wa = twa->workArea(j);
2859                         Buffer & buf = wa->bufferView().buffer();
2860                         theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2861                 }
2862         }
2863 }
2864
2865
2866 bool GuiView::closeBufferAll()
2867 {
2868         // Close the workareas in all other views
2869         QList<int> const ids = guiApp->viewIds();
2870         for (int i = 0; i != ids.size(); ++i) {
2871                 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2872                         return false;
2873         }
2874
2875         // Close our own workareas
2876         if (!closeWorkAreaAll())
2877                 return false;
2878
2879         // Now close the hidden buffers. We prevent hidden buffers from being
2880         // dirty, so we can just close them.
2881         theBufferList().closeAll();
2882         return true;
2883 }
2884
2885
2886 bool GuiView::closeWorkAreaAll()
2887 {
2888         setCurrentWorkArea(currentMainWorkArea());
2889
2890         // We might be in a situation that there is still a tabWorkArea, but
2891         // there are no tabs anymore. This can happen when we get here after a
2892         // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
2893         // many TabWorkArea's have no documents anymore.
2894         int empty_twa = 0;
2895
2896         // We have to call count() each time, because it can happen that
2897         // more than one splitter will disappear in one iteration (bug 5998).
2898         while (d.splitter_->count() > empty_twa) {
2899                 TabWorkArea * twa = d.tabWorkArea(empty_twa);
2900
2901                 if (twa->count() == 0)
2902                         ++empty_twa;
2903                 else {
2904                         setCurrentWorkArea(twa->currentWorkArea());
2905                         if (!closeTabWorkArea(twa))
2906                                 return false;
2907                 }
2908         }
2909         return true;
2910 }
2911
2912
2913 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
2914 {
2915         if (!wa)
2916                 return false;
2917
2918         Buffer & buf = wa->bufferView().buffer();
2919
2920         if (GuiViewPrivate::busyBuffers.contains(&buf)) {
2921                 Alert::warning(_("Close document"),
2922                         _("Document could not be closed because it is being processed by LyX."));
2923                 return false;
2924         }
2925
2926         if (close_buffer)
2927                 return closeBuffer(buf);
2928         else {
2929                 if (!inMultiTabs(wa))
2930                         if (!saveBufferIfNeeded(buf, true))
2931                                 return false;
2932                 removeWorkArea(wa);
2933                 return true;
2934         }
2935 }
2936
2937
2938 bool GuiView::closeBuffer(Buffer & buf)
2939 {
2940         // If we are in a close_event all children will be closed in some time,
2941         // so no need to do it here. This will ensure that the children end up
2942         // in the session file in the correct order. If we close the master
2943         // buffer, we can close or release the child buffers here too.
2944         bool success = true;
2945         if (!closing_) {
2946                 ListOfBuffers clist = buf.getChildren();
2947                 ListOfBuffers::const_iterator it = clist.begin();
2948                 ListOfBuffers::const_iterator const bend = clist.end();
2949                 for (; it != bend; ++it) {
2950                         Buffer * child_buf = *it;
2951                         if (theBufferList().isOthersChild(&buf, child_buf)) {
2952                                 child_buf->setParent(0);
2953                                 continue;
2954                         }
2955
2956                         // FIXME: should we look in other tabworkareas?
2957                         // ANSWER: I don't think so. I've tested, and if the child is
2958                         // open in some other window, it closes without a problem.
2959                         GuiWorkArea * child_wa = workArea(*child_buf);
2960                         if (child_wa) {
2961                                 success = closeWorkArea(child_wa, true);
2962                                 if (!success)
2963                                         break;
2964                         } else {
2965                                 // In this case the child buffer is open but hidden.
2966                                 // It therefore should not (MUST NOT) be dirty!
2967                                 LATTEST(child_buf->isClean());
2968                                 theBufferList().release(child_buf);
2969                         }
2970                 }
2971         }
2972         if (success) {
2973                 // goto bookmark to update bookmark pit.
2974                 // FIXME: we should update only the bookmarks related to this buffer!
2975                 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
2976                 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
2977                         guiApp->gotoBookmark(i+1, false, false);
2978
2979                 if (saveBufferIfNeeded(buf, false)) {
2980                         buf.removeAutosaveFile();
2981                         theBufferList().release(&buf);
2982                         return true;
2983                 }
2984         }
2985         // open all children again to avoid a crash because of dangling
2986         // pointers (bug 6603)
2987         buf.updateBuffer();
2988         return false;
2989 }
2990
2991
2992 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
2993 {
2994         while (twa == d.currentTabWorkArea()) {
2995                 twa->setCurrentIndex(twa->count() - 1);
2996
2997                 GuiWorkArea * wa = twa->currentWorkArea();
2998                 Buffer & b = wa->bufferView().buffer();
2999
3000                 // We only want to close the buffer if the same buffer is not visible
3001                 // in another view, and if this is not a child and if we are closing
3002                 // a view (not a tabgroup).
3003                 bool const close_buffer =
3004                         !inOtherView(b) && !b.parent() && closing_;
3005
3006                 if (!closeWorkArea(wa, close_buffer))
3007                         return false;
3008         }
3009         return true;
3010 }
3011
3012
3013 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3014 {
3015         if (buf.isClean() || buf.paragraphs().empty())
3016                 return true;
3017
3018         // Switch to this Buffer.
3019         setBuffer(&buf);
3020
3021         docstring file;
3022         bool exists;
3023         // FIXME: Unicode?
3024         if (buf.isUnnamed()) {
3025                 file = from_utf8(buf.fileName().onlyFileName());
3026                 exists = false;
3027         } else {
3028                 FileName filename = buf.fileName();
3029                 filename.refresh();
3030                 file = filename.displayName(30);
3031                 exists = filename.exists();
3032         }
3033
3034         // Bring this window to top before asking questions.
3035         raise();
3036         activateWindow();
3037
3038         int ret;
3039         if (hiding && buf.isUnnamed()) {
3040                 docstring const text = bformat(_("The document %1$s has not been "
3041                                                  "saved yet.\n\nDo you want to save "
3042                                                  "the document?"), file);
3043                 ret = Alert::prompt(_("Save new document?"),
3044                         text, 0, 1, _("&Save"), _("&Cancel"));
3045                 if (ret == 1)
3046                         ++ret;
3047         } else {
3048                 docstring const text = exists ?
3049                         bformat(_("The document %1$s has unsaved changes."
3050                                   "\n\nDo you want to save the document or "
3051                                   "discard the changes?"), file) :
3052                         bformat(_("The document %1$s has not been saved yet."
3053                                   "\n\nDo you want to save the document or "
3054                                   "discard it entirely?"), file);
3055                 docstring const title = exists ?
3056                         _("Save changed document?") : _("Save document?");
3057                 ret = Alert::prompt(title, text, 0, 2,
3058                                     _("&Save"), _("&Discard"), _("&Cancel"));
3059         }
3060
3061         switch (ret) {
3062         case 0:
3063                 if (!saveBuffer(buf))
3064                         return false;
3065                 break;
3066         case 1:
3067                 // If we crash after this we could have no autosave file
3068                 // but I guess this is really improbable (Jug).
3069                 // Sometimes improbable things happen:
3070                 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3071                 // buf.removeAutosaveFile();
3072                 if (hiding)
3073                         // revert all changes
3074                         reloadBuffer(buf);
3075                 buf.markClean();
3076                 break;
3077         case 2:
3078                 return false;
3079         }
3080         return true;
3081 }
3082
3083
3084 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3085 {
3086         Buffer & buf = wa->bufferView().buffer();
3087
3088         for (int i = 0; i != d.splitter_->count(); ++i) {
3089                 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3090                 if (wa_ && wa_ != wa)
3091                         return true;
3092         }
3093         return inOtherView(buf);
3094 }
3095
3096
3097 bool GuiView::inOtherView(Buffer & buf)
3098 {
3099         QList<int> const ids = guiApp->viewIds();
3100
3101         for (int i = 0; i != ids.size(); ++i) {
3102                 if (id_ == ids[i])
3103                         continue;
3104
3105                 if (guiApp->view(ids[i]).workArea(buf))
3106                         return true;
3107         }
3108         return false;
3109 }
3110
3111
3112 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3113 {
3114         if (!documentBufferView())
3115                 return;
3116
3117         if (TabWorkArea * twa = d.currentTabWorkArea()) {
3118                 Buffer * const curbuf = &documentBufferView()->buffer();
3119                 int nwa = twa->count();
3120                 for (int i = 0; i < nwa; ++i) {
3121                         if (&workArea(i)->bufferView().buffer() == curbuf) {
3122                                 int next_index;
3123                                 if (np == NEXTBUFFER)
3124                                         next_index = (i == nwa - 1 ? 0 : i + 1);
3125                                 else
3126                                         next_index = (i == 0 ? nwa - 1 : i - 1);
3127                                 if (move)
3128                                         twa->moveTab(i, next_index);
3129                                 else
3130                                         setBuffer(&workArea(next_index)->bufferView().buffer());
3131                                 break;
3132                         }
3133                 }
3134         }
3135 }
3136
3137
3138 /// make sure the document is saved
3139 static bool ensureBufferClean(Buffer * buffer)
3140 {
3141         LASSERT(buffer, return false);
3142         if (buffer->isClean() && !buffer->isUnnamed())
3143                 return true;
3144
3145         docstring const file = buffer->fileName().displayName(30);
3146         docstring title;
3147         docstring text;
3148         if (!buffer->isUnnamed()) {
3149                 text = bformat(_("The document %1$s has unsaved "
3150                                                  "changes.\n\nDo you want to save "
3151                                                  "the document?"), file);
3152                 title = _("Save changed document?");
3153
3154         } else {
3155                 text = bformat(_("The document %1$s has not been "
3156                                                  "saved yet.\n\nDo you want to save "
3157                                                  "the document?"), file);
3158                 title = _("Save new document?");
3159         }
3160         int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3161
3162         if (ret == 0)
3163                 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3164
3165         return buffer->isClean() && !buffer->isUnnamed();
3166 }
3167
3168
3169 bool GuiView::reloadBuffer(Buffer & buf)
3170 {
3171         Buffer::ReadStatus status = buf.reload();
3172         return status == Buffer::ReadSuccess;
3173 }
3174
3175
3176 void GuiView::checkExternallyModifiedBuffers()
3177 {
3178         BufferList::iterator bit = theBufferList().begin();
3179         BufferList::iterator const bend = theBufferList().end();
3180         for (; bit != bend; ++bit) {
3181                 Buffer * buf = *bit;
3182                 if (buf->fileName().exists() && buf->isChecksumModified()) {
3183                         docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3184                                         " Reload now? Any local changes will be lost."),
3185                                         from_utf8(buf->absFileName()));
3186                         int const ret = Alert::prompt(_("Reload externally changed document?"),
3187                                                 text, 0, 1, _("&Reload"), _("&Cancel"));
3188                         if (!ret)
3189                                 reloadBuffer(*buf);
3190                 }
3191         }
3192 }
3193
3194
3195 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3196 {
3197         Buffer * buffer = documentBufferView()
3198                 ? &(documentBufferView()->buffer()) : 0;
3199
3200         switch (cmd.action()) {
3201         case LFUN_VC_REGISTER:
3202                 if (!buffer || !ensureBufferClean(buffer))
3203                         break;
3204                 if (!buffer->lyxvc().inUse()) {
3205                         if (buffer->lyxvc().registrer()) {
3206                                 reloadBuffer(*buffer);
3207                                 dr.clearMessageUpdate();
3208                         }
3209                 }
3210                 break;
3211
3212         case LFUN_VC_RENAME:
3213         case LFUN_VC_COPY: {
3214                 if (!buffer || !ensureBufferClean(buffer))
3215                         break;
3216                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3217                         if (buffer->lyxvc().isCheckInWithConfirmation()) {
3218                                 // Some changes are not yet committed.
3219                                 // We test here and not in getStatus(), since
3220                                 // this test is expensive.
3221                                 string log;
3222                                 LyXVC::CommandResult ret =
3223                                         buffer->lyxvc().checkIn(log);
3224                                 dr.setMessage(log);
3225                                 if (ret == LyXVC::ErrorCommand ||
3226                                     ret == LyXVC::VCSuccess)
3227                                         reloadBuffer(*buffer);
3228                                 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3229                                         frontend::Alert::error(
3230                                                 _("Revision control error."),
3231                                                 _("Document could not be checked in."));
3232                                         break;
3233                                 }
3234                         }
3235                         RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3236                                 LV_VC_RENAME : LV_VC_COPY;
3237                         renameBuffer(*buffer, cmd.argument(), kind);
3238                 }
3239                 break;
3240         }
3241
3242         case LFUN_VC_CHECK_IN:
3243                 if (!buffer || !ensureBufferClean(buffer))
3244                         break;
3245                 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3246                         string log;
3247                         LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3248                         dr.setMessage(log);
3249                         // Only skip reloading if the checkin was cancelled or
3250                         // an error occurred before the real checkin VCS command
3251                         // was executed, since the VCS might have changed the
3252                         // file even if it could not checkin successfully.
3253                         if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3254                                 reloadBuffer(*buffer);
3255                 }
3256                 break;
3257
3258         case LFUN_VC_CHECK_OUT:
3259                 if (!buffer || !ensureBufferClean(buffer))
3260                         break;
3261                 if (buffer->lyxvc().inUse()) {
3262                         dr.setMessage(buffer->lyxvc().checkOut());
3263                         reloadBuffer(*buffer);
3264                 }
3265                 break;
3266
3267         case LFUN_VC_LOCKING_TOGGLE:
3268                 LASSERT(buffer, return);
3269                 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3270                         break;
3271                 if (buffer->lyxvc().inUse()) {
3272                         string res = buffer->lyxvc().lockingToggle();
3273                         if (res.empty()) {
3274                                 frontend::Alert::error(_("Revision control error."),
3275                                 _("Error when setting the locking property."));
3276                         } else {
3277                                 dr.setMessage(res);
3278                                 reloadBuffer(*buffer);
3279                         }
3280                 }
3281                 break;
3282
3283         case LFUN_VC_REVERT:
3284                 LASSERT(buffer, return);
3285                 if (buffer->lyxvc().revert()) {
3286                         reloadBuffer(*buffer);
3287                         dr.clearMessageUpdate();
3288                 }
3289                 break;
3290
3291         case LFUN_VC_UNDO_LAST:
3292                 LASSERT(buffer, return);
3293                 buffer->lyxvc().undoLast();
3294                 reloadBuffer(*buffer);
3295                 dr.clearMessageUpdate();
3296                 break;
3297
3298         case LFUN_VC_REPO_UPDATE:
3299                 LASSERT(buffer, return);
3300                 if (ensureBufferClean(buffer)) {
3301                         dr.setMessage(buffer->lyxvc().repoUpdate());
3302                         checkExternallyModifiedBuffers();
3303                 }
3304                 break;
3305
3306         case LFUN_VC_COMMAND: {
3307                 string flag = cmd.getArg(0);
3308                 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3309                         break;
3310                 docstring message;
3311                 if (contains(flag, 'M')) {
3312                         if (!Alert::askForText(message, _("LyX VC: Log Message")))
3313                                 break;
3314                 }
3315                 string path = cmd.getArg(1);
3316                 if (contains(path, "$$p") && buffer)
3317                         path = subst(path, "$$p", buffer->filePath());
3318                 LYXERR(Debug::LYXVC, "Directory: " << path);
3319                 FileName pp(path);
3320                 if (!pp.isReadableDirectory()) {
3321                         lyxerr << _("Directory is not accessible.") << endl;
3322                         break;
3323                 }
3324                 support::PathChanger p(pp);
3325
3326                 string command = cmd.getArg(2);
3327                 if (command.empty())
3328                         break;
3329                 if (buffer) {
3330                         command = subst(command, "$$i", buffer->absFileName());
3331                         command = subst(command, "$$p", buffer->filePath());
3332                 }
3333                 command = subst(command, "$$m", to_utf8(message));
3334                 LYXERR(Debug::LYXVC, "Command: " << command);
3335                 Systemcall one;
3336                 one.startscript(Systemcall::Wait, command);
3337
3338                 if (!buffer)
3339                         break;
3340                 if (contains(flag, 'I'))
3341                         buffer->markDirty();
3342                 if (contains(flag, 'R'))
3343                         reloadBuffer(*buffer);
3344
3345                 break;
3346                 }
3347
3348         case LFUN_VC_COMPARE: {
3349                 if (cmd.argument().empty()) {
3350                         lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3351                         break;
3352                 }
3353
3354                 string rev1 = cmd.getArg(0);
3355                 string f1, f2;
3356                 LATTEST(buffer)
3357
3358                 // f1
3359                 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3360                         break;
3361
3362                 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3363                         f2 = buffer->absFileName();
3364                 } else {
3365                         string rev2 = cmd.getArg(1);
3366                         if (rev2.empty())
3367                                 break;
3368                         // f2
3369                         if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3370                                 break;
3371                 }
3372
3373                 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3374                                         f1 << "\n"  << f2 << "\n" );
3375                 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3376                 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3377                 break;
3378         }
3379
3380         default:
3381                 break;
3382         }
3383 }
3384
3385
3386 void GuiView::openChildDocument(string const & fname)
3387 {
3388         LASSERT(documentBufferView(), return);
3389         Buffer & buffer = documentBufferView()->buffer();
3390         FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3391         documentBufferView()->saveBookmark(false);
3392         Buffer * child = 0;
3393         if (theBufferList().exists(filename)) {
3394                 child = theBufferList().getBuffer(filename);
3395                 setBuffer(child);
3396         } else {
3397                 message(bformat(_("Opening child document %1$s..."),
3398                         makeDisplayPath(filename.absFileName())));
3399                 child = loadDocument(filename, false);
3400         }
3401         // Set the parent name of the child document.
3402         // This makes insertion of citations and references in the child work,
3403         // when the target is in the parent or another child document.
3404         if (child)
3405                 child->setParent(&buffer);
3406 }
3407
3408
3409 bool GuiView::goToFileRow(string const & argument)
3410 {
3411         string file_name;
3412         int row;
3413         size_t i = argument.find_last_of(' ');
3414         if (i != string::npos) {
3415                 file_name = os::internal_path(trim(argument.substr(0, i)));
3416                 istringstream is(argument.substr(i + 1));
3417                 is >> row;
3418                 if (is.fail())
3419                         i = string::npos;
3420         }
3421         if (i == string::npos) {
3422                 LYXERR0("Wrong argument: " << argument);
3423                 return false;
3424         }
3425         Buffer * buf = 0;
3426         string const abstmp = package().temp_dir().absFileName();
3427         string const realtmp = package().temp_dir().realPath();
3428         // We have to use os::path_prefix_is() here, instead of
3429         // simply prefixIs(), because the file name comes from
3430         // an external application and may need case adjustment.
3431         if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3432                 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3433                 // Needed by inverse dvi search. If it is a file
3434                 // in tmpdir, call the apropriated function.
3435                 // If tmpdir is a symlink, we may have the real
3436                 // path passed back, so we correct for that.
3437                 if (!prefixIs(file_name, abstmp))
3438                         file_name = subst(file_name, realtmp, abstmp);
3439                 buf = theBufferList().getBufferFromTmp(file_name);
3440         } else {
3441                 // Must replace extension of the file to be .lyx
3442                 // and get full path
3443                 FileName const s = fileSearch(string(),
3444                                                   support::changeExtension(file_name, ".lyx"), "lyx");
3445                 // Either change buffer or load the file
3446                 if (theBufferList().exists(s))
3447                         buf = theBufferList().getBuffer(s);
3448                 else if (s.exists()) {
3449                         buf = loadDocument(s);
3450                         if (!buf)
3451                                 return false;
3452                 } else {
3453                         message(bformat(
3454                                         _("File does not exist: %1$s"),
3455                                         makeDisplayPath(file_name)));
3456                         return false;
3457                 }
3458         }
3459         if (!buf) {
3460                 message(bformat(
3461                         _("No buffer for file: %1$s."),
3462                         makeDisplayPath(file_name))
3463                 );
3464                 return false;
3465         }
3466         setBuffer(buf);
3467         bool success = documentBufferView()->setCursorFromRow(row);
3468         if (!success) {
3469                 LYXERR(Debug::LATEX,
3470                        "setCursorFromRow: invalid position for row " << row);
3471                 frontend::Alert::error(_("Inverse Search Failed"),
3472                                        _("Invalid position requested by inverse search.\n"
3473                                          "You may need to update the viewed document."));
3474         }
3475         return success;
3476 }
3477
3478
3479 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3480 {
3481         QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3482         menu->exec(QCursor::pos());
3483 }
3484
3485
3486 template<class T>
3487 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format)
3488 {
3489         Buffer::ExportStatus const status = func(format);
3490
3491         // the cloning operation will have produced a clone of the entire set of
3492         // documents, starting from the master. so we must delete those.
3493         Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3494         delete mbuf;
3495         busyBuffers.remove(orig);
3496         return status;
3497 }
3498
3499
3500 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3501 {
3502         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3503         return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3504 }
3505
3506
3507 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3508 {
3509         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport;
3510         return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3511 }
3512
3513
3514 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format)
3515 {
3516         Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview;
3517         return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3518 }
3519
3520
3521 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3522                            string const & argument,
3523                            Buffer const * used_buffer,
3524                            docstring const & msg,
3525                            Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3526                            Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3527                            Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const)
3528 {
3529         if (!used_buffer)
3530                 return false;
3531
3532         string format = argument;
3533         if (format.empty())
3534                 format = used_buffer->params().getDefaultOutputFormat();
3535         processing_format = format;
3536         if (!msg.empty()) {
3537                 progress_->clearMessages();
3538                 gv_->message(msg);
3539         }
3540 #if EXPORT_in_THREAD
3541         GuiViewPrivate::busyBuffers.insert(used_buffer);
3542         Buffer * cloned_buffer = used_buffer->cloneFromMaster();
3543         if (!cloned_buffer) {
3544                 Alert::error(_("Export Error"),
3545                              _("Error cloning the Buffer."));
3546                 return false;
3547         }
3548         QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3549                                 asyncFunc,
3550                                 used_buffer,
3551                                 cloned_buffer,
3552                                 format);
3553         setPreviewFuture(f);
3554         last_export_format = used_buffer->params().bufferFormat();
3555         (void) syncFunc;
3556         (void) previewFunc;
3557         // We are asynchronous, so we don't know here anything about the success
3558         return true;
3559 #else
3560         Buffer::ExportStatus status;
3561         if (syncFunc) {
3562                 status = (used_buffer->*syncFunc)(format, true);
3563         } else if (previewFunc) {
3564                 status = (used_buffer->*previewFunc)(format);
3565         } else
3566                 return false;
3567         handleExportStatus(gv_, status, format);
3568         (void) asyncFunc;
3569         return (status == Buffer::ExportSuccess
3570                         || status == Buffer::PreviewSuccess);
3571 #endif
3572 }
3573
3574 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3575 {
3576         BufferView * bv = currentBufferView();
3577         LASSERT(bv, return);
3578
3579         // Let the current BufferView dispatch its own actions.
3580         bv->dispatch(cmd, dr);
3581         if (dr.dispatched())
3582                 return;
3583
3584         // Try with the document BufferView dispatch if any.
3585         BufferView * doc_bv = documentBufferView();
3586         if (doc_bv && doc_bv != bv) {
3587                 doc_bv->dispatch(cmd, dr);
3588                 if (dr.dispatched())
3589                         return;
3590         }
3591
3592         // Then let the current Cursor dispatch its own actions.
3593         bv->cursor().dispatch(cmd);
3594
3595         // update completion. We do it here and not in
3596         // processKeySym to avoid another redraw just for a
3597         // changed inline completion
3598         if (cmd.origin() == FuncRequest::KEYBOARD) {
3599                 if (cmd.action() == LFUN_SELF_INSERT
3600                         || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3601                         updateCompletion(bv->cursor(), true, true);
3602                 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3603                         updateCompletion(bv->cursor(), false, true);
3604                 else
3605                         updateCompletion(bv->cursor(), false, false);
3606         }
3607
3608         dr = bv->cursor().result();
3609 }
3610
3611
3612 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3613 {
3614         BufferView * bv = currentBufferView();
3615         // By default we won't need any update.
3616         dr.screenUpdate(Update::None);
3617         // assume cmd will be dispatched
3618         dr.dispatched(true);
3619
3620         Buffer * doc_buffer = documentBufferView()
3621                 ? &(documentBufferView()->buffer()) : 0;
3622
3623         if (cmd.origin() == FuncRequest::TOC) {
3624                 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3625                 // FIXME: do we need to pass a DispatchResult object here?
3626                 toc->doDispatch(bv->cursor(), cmd);
3627                 return;
3628         }
3629
3630         string const argument = to_utf8(cmd.argument());
3631
3632         switch(cmd.action()) {
3633                 case LFUN_BUFFER_CHILD_OPEN:
3634                         openChildDocument(to_utf8(cmd.argument()));
3635                         break;
3636
3637                 case LFUN_BUFFER_IMPORT:
3638                         importDocument(to_utf8(cmd.argument()));
3639                         break;
3640
3641                 case LFUN_BUFFER_EXPORT: {
3642                         if (!doc_buffer)
3643                                 break;
3644                         // GCC only sees strfwd.h when building merged
3645                         if (::lyx::operator==(cmd.argument(), "custom")) {
3646                                 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3647                                 break;
3648                         }
3649
3650                         string const dest = cmd.getArg(1);
3651                         FileName target_dir;
3652                         if (!dest.empty() && FileName::isAbsolute(dest))
3653                                 target_dir = FileName(support::onlyPath(dest));
3654                         else
3655                                 target_dir = doc_buffer->fileName().onlyPath();
3656
3657                         string const format = (argument.empty() || argument == "default") ?
3658                                 doc_buffer->params().getDefaultOutputFormat() : argument;
3659
3660                         if ((dest.empty() && doc_buffer->isUnnamed())
3661                             || !target_dir.isDirWritable()) {
3662                                 exportBufferAs(*doc_buffer, from_utf8(format));
3663                                 break;
3664                         }
3665                         /* TODO/Review: Is it a problem to also export the children?
3666                                         See the update_unincluded flag */
3667                         d.asyncBufferProcessing(format,
3668                                                 doc_buffer,
3669                                                 _("Exporting ..."),
3670                                                 &GuiViewPrivate::exportAndDestroy,
3671                                                 &Buffer::doExport,
3672                                                 0);
3673                         // TODO Inform user about success
3674                         break;
3675                 }
3676
3677                 case LFUN_BUFFER_EXPORT_AS: {
3678                         LASSERT(doc_buffer, break);
3679                         docstring f = cmd.argument();
3680                         if (f.empty())
3681                                 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3682                         exportBufferAs(*doc_buffer, f);
3683                         break;
3684                 }
3685
3686                 case LFUN_BUFFER_UPDATE: {
3687                         d.asyncBufferProcessing(argument,
3688                                                 doc_buffer,
3689                                                 _("Exporting ..."),
3690                                                 &GuiViewPrivate::compileAndDestroy,
3691                                                 &Buffer::doExport,
3692                                                 0);
3693                         break;
3694                 }
3695                 case LFUN_BUFFER_VIEW: {
3696                         d.asyncBufferProcessing(argument,
3697                                                 doc_buffer,
3698                                                 _("Previewing ..."),
3699                                                 &GuiViewPrivate::previewAndDestroy,
3700                                                 0,
3701                                                 &Buffer::preview);
3702                         break;
3703                 }
3704                 case LFUN_MASTER_BUFFER_UPDATE: {
3705                         d.asyncBufferProcessing(argument,
3706                                                 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3707                                                 docstring(),
3708                                                 &GuiViewPrivate::compileAndDestroy,
3709                                                 &Buffer::doExport,
3710                                                 0);
3711                         break;
3712                 }
3713                 case LFUN_MASTER_BUFFER_VIEW: {
3714                         d.asyncBufferProcessing(argument,
3715                                                 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3716                                                 docstring(),
3717                                                 &GuiViewPrivate::previewAndDestroy,
3718                                                 0, &Buffer::preview);
3719                         break;
3720                 }
3721                 case LFUN_BUFFER_SWITCH: {
3722                         string const file_name = to_utf8(cmd.argument());
3723                         if (!FileName::isAbsolute(file_name)) {
3724                                 dr.setError(true);
3725                                 dr.setMessage(_("Absolute filename expected."));
3726                                 break;
3727                         }
3728
3729                         Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3730                         if (!buffer) {
3731                                 dr.setError(true);
3732                                 dr.setMessage(_("Document not loaded"));
3733                                 break;
3734                         }
3735
3736                         // Do we open or switch to the buffer in this view ?
3737                         if (workArea(*buffer)
3738                                   || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3739                                 setBuffer(buffer);
3740                                 break;
3741                         }
3742
3743                         // Look for the buffer in other views
3744                         QList<int> const ids = guiApp->viewIds();
3745                         int i = 0;
3746                         for (; i != ids.size(); ++i) {
3747                                 GuiView & gv = guiApp->view(ids[i]);
3748                                 if (gv.workArea(*buffer)) {
3749                                         gv.raise();
3750                                         gv.activateWindow();
3751                                         gv.setFocus();
3752                                         gv.setBuffer(buffer);
3753                                         break;
3754                                 }
3755                         }
3756
3757                         // If necessary, open a new window as a last resort
3758                         if (i == ids.size()) {
3759                                 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3760                                 lyx::dispatch(cmd);
3761                         }
3762                         break;
3763                 }
3764
3765                 case LFUN_BUFFER_NEXT:
3766                         gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3767                         break;
3768
3769                 case LFUN_BUFFER_MOVE_NEXT:
3770                         gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3771                         break;
3772
3773                 case LFUN_BUFFER_PREVIOUS:
3774                         gotoNextOrPreviousBuffer(PREVBUFFER, false);
3775                         break;
3776
3777                 case LFUN_BUFFER_MOVE_PREVIOUS:
3778                         gotoNextOrPreviousBuffer(PREVBUFFER, true);
3779                         break;
3780
3781                 case LFUN_COMMAND_EXECUTE: {
3782                         command_execute_ = true;
3783                         minibuffer_focus_ = true;
3784                         break;
3785                 }
3786                 case LFUN_DROP_LAYOUTS_CHOICE:
3787                         d.layout_->showPopup();
3788                         break;
3789
3790                 case LFUN_MENU_OPEN:
3791                         if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3792                                 menu->exec(QCursor::pos());
3793                         break;
3794
3795                 case LFUN_FILE_INSERT:
3796                         insertLyXFile(cmd.argument());
3797                         break;
3798
3799                 case LFUN_FILE_INSERT_PLAINTEXT:
3800                 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3801                         string const fname = to_utf8(cmd.argument());
3802                         if (!fname.empty() && !FileName::isAbsolute(fname)) {
3803                                 dr.setMessage(_("Absolute filename expected."));
3804                                 break;
3805                         }
3806
3807                         FileName filename(fname);
3808                         if (fname.empty()) {
3809                                 FileDialog dlg(qt_("Select file to insert"));
3810
3811                                 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3812                                         QStringList(qt_("All Files (*)")));
3813
3814                                 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3815                                         dr.setMessage(_("Canceled."));
3816                                         break;
3817                                 }
3818
3819                                 filename.set(fromqstr(result.second));
3820                         }
3821
3822                         if (bv) {
3823                                 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3824                                 bv->dispatch(new_cmd, dr);
3825                         }
3826                         break;
3827                 }
3828
3829                 case LFUN_BUFFER_RELOAD: {
3830                         LASSERT(doc_buffer, break);
3831
3832                         int ret = 0;
3833                         if (!doc_buffer->isClean()) {
3834                                 docstring const file =
3835                                         makeDisplayPath(doc_buffer->absFileName(), 20);
3836                                 if (doc_buffer->notifiesExternalModification()) {
3837                                         docstring text = _("The current version will be lost. "
3838                                             "Are you sure you want to load the version on disk "
3839                                             "of the document %1$s?");
3840                                         ret = Alert::prompt(_("Reload saved document?"),
3841                                                             bformat(text, file), 1, 1,
3842                                                             _("&Reload"), _("&Cancel"));
3843                                 } else {
3844                                         docstring text = _("Any changes will be lost. "
3845                                             "Are you sure you want to revert to the saved version "
3846                                             "of the document %1$s?");
3847                                         ret = Alert::prompt(_("Revert to saved document?"),
3848                                                             bformat(text, file), 1, 1,
3849                                                             _("&Revert"), _("&Cancel"));
3850                                 }
3851                         }
3852
3853                         if (ret == 0) {
3854                                 doc_buffer->markClean();
3855                                 reloadBuffer(*doc_buffer);
3856                                 dr.forceBufferUpdate();
3857                         }
3858                         break;
3859                 }
3860
3861                 case LFUN_BUFFER_WRITE:
3862                         LASSERT(doc_buffer, break);
3863                         saveBuffer(*doc_buffer);
3864                         break;
3865
3866                 case LFUN_BUFFER_WRITE_AS:
3867                         LASSERT(doc_buffer, break);
3868                         renameBuffer(*doc_buffer, cmd.argument());
3869                         break;
3870
3871                 case LFUN_BUFFER_WRITE_ALL: {
3872                         Buffer * first = theBufferList().first();
3873                         if (!first)
3874                                 break;
3875                         message(_("Saving all documents..."));
3876                         // We cannot use a for loop as the buffer list cycles.
3877                         Buffer * b = first;
3878                         do {
3879                                 if (!b->isClean()) {
3880                                         saveBuffer(*b);
3881                                         LYXERR(Debug::ACTION, "Saved " << b->absFileName());
3882                                 }
3883                                 b = theBufferList().next(b);
3884                         } while (b != first);
3885                         dr.setMessage(_("All documents saved."));
3886                         break;
3887                 }
3888
3889                 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
3890                         LASSERT(doc_buffer, break);
3891                         doc_buffer->clearExternalModification();
3892                         break;
3893
3894                 case LFUN_BUFFER_CLOSE:
3895                         closeBuffer();
3896                         break;
3897
3898                 case LFUN_BUFFER_CLOSE_ALL:
3899                         closeBufferAll();
3900                         break;
3901
3902                 case LFUN_DEVEL_MODE_TOGGLE:
3903                         devel_mode_ = !devel_mode_;
3904                         if (devel_mode_)
3905                                 dr.setMessage(_("Developer mode is now enabled."));
3906                         else
3907                                 dr.setMessage(_("Developer mode is now disabled."));
3908                         break;
3909
3910                 case LFUN_TOOLBAR_TOGGLE: {
3911                         string const name = cmd.getArg(0);
3912                         if (GuiToolbar * t = toolbar(name))
3913                                 t->toggle();
3914                         break;
3915                 }
3916
3917                 case LFUN_TOOLBAR_MOVABLE: {
3918                         string const name = cmd.getArg(0);
3919                         if (name == "*") {
3920                                 // toggle (all) toolbars movablility
3921                                 toolbarsMovable_ = !toolbarsMovable_;
3922                                 for (ToolbarInfo const & ti : guiApp->toolbars()) {
3923                                         GuiToolbar * tb = toolbar(ti.name);
3924                                         if (tb && tb->isMovable() != toolbarsMovable_)
3925                                                 // toggle toolbar movablity if it does not fit lock
3926                                                 // (all) toolbars positions state silent = true, since
3927                                                 // status bar notifications are slow
3928                                                 tb->movable(true);
3929                                 }
3930                                 if (toolbarsMovable_)
3931                                         dr.setMessage(_("Toolbars unlocked."));
3932                                 else
3933                                         dr.setMessage(_("Toolbars locked."));
3934                         } else if (GuiToolbar * t = toolbar(name)) {
3935                                 // toggle current toolbar movablity
3936                                 t->movable();
3937                                 // update lock (all) toolbars positions
3938                                 updateLockToolbars();
3939                         }
3940                         break;
3941                 }
3942
3943                 case LFUN_ICON_SIZE: {
3944                         QSize size = d.iconSize(cmd.argument());
3945                         setIconSize(size);
3946                         dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
3947                                                 size.width(), size.height()));
3948                         break;
3949                 }
3950
3951                 case LFUN_DIALOG_UPDATE: {
3952                         string const name = to_utf8(cmd.argument());
3953                         if (name == "prefs" || name == "document")
3954                                 updateDialog(name, string());
3955                         else if (name == "paragraph")
3956                                 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
3957                         else if (currentBufferView()) {
3958                                 Inset * inset = currentBufferView()->editedInset(name);
3959                                 // Can only update a dialog connected to an existing inset
3960                                 if (inset) {
3961                                         // FIXME: get rid of this indirection; GuiView ask the inset
3962                                         // if he is kind enough to update itself...
3963                                         FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
3964                                         //FIXME: pass DispatchResult here?
3965                                         inset->dispatch(currentBufferView()->cursor(), fr);
3966                                 }
3967                         }
3968                         break;
3969                 }
3970
3971                 case LFUN_DIALOG_TOGGLE: {
3972                         FuncCode const func_code = isDialogVisible(cmd.getArg(0))
3973                                 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
3974                         dispatch(FuncRequest(func_code, cmd.argument()), dr);
3975                         break;
3976                 }
3977
3978                 case LFUN_DIALOG_DISCONNECT_INSET:
3979                         disconnectDialog(to_utf8(cmd.argument()));
3980                         break;
3981
3982                 case LFUN_DIALOG_HIDE: {
3983                         guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
3984                         break;
3985                 }
3986
3987                 case LFUN_DIALOG_SHOW: {
3988                         string const name = cmd.getArg(0);
3989                         string data = trim(to_utf8(cmd.argument()).substr(name.size()));
3990
3991                         if (name == "character") {
3992                                 data = freefont2string();
3993                                 if (!data.empty())
3994                                         showDialog("character", data);
3995                         } else if (name == "latexlog") {
3996                                 // getStatus checks that
3997                                 LATTEST(doc_buffer);
3998                                 Buffer::LogType type;
3999                                 string const logfile = doc_buffer->logName(&type);
4000                                 switch (type) {
4001                                 case Buffer::latexlog:
4002                                         data = "latex ";
4003                                         break;
4004                                 case Buffer::buildlog:
4005                                         data = "literate ";
4006                                         break;
4007                                 }
4008                                 data += Lexer::quoteString(logfile);
4009                                 showDialog("log", data);
4010                         } else if (name == "vclog") {
4011                                 // getStatus checks that
4012                                 LATTEST(doc_buffer);
4013                                 string const data = "vc " +
4014                                         Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4015                                 showDialog("log", data);
4016                         } else if (name == "symbols") {
4017                                 data = bv->cursor().getEncoding()->name();
4018                                 if (!data.empty())
4019                                         showDialog("symbols", data);
4020                         // bug 5274
4021                         } else if (name == "prefs" && isFullScreen()) {
4022                                 lfunUiToggle("fullscreen");
4023                                 showDialog("prefs", data);
4024                         } else
4025                                 showDialog(name, data);
4026                         break;
4027                 }
4028
4029                 case LFUN_MESSAGE:
4030                         dr.setMessage(cmd.argument());
4031                         break;
4032
4033                 case LFUN_UI_TOGGLE: {
4034                         string arg = cmd.getArg(0);
4035                         if (!lfunUiToggle(arg)) {
4036                                 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4037                                 dr.setMessage(bformat(msg, from_utf8(arg)));
4038                         }
4039                         // Make sure the keyboard focus stays in the work area.
4040                         setFocus();
4041                         break;
4042                 }
4043
4044                 case LFUN_VIEW_SPLIT: {
4045                         LASSERT(doc_buffer, break);
4046                         string const orientation = cmd.getArg(0);
4047                         d.splitter_->setOrientation(orientation == "vertical"
4048                                 ? Qt::Vertical : Qt::Horizontal);
4049                         TabWorkArea * twa = addTabWorkArea();
4050                         GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4051                         setCurrentWorkArea(wa);
4052                         break;
4053                 }
4054                 case LFUN_TAB_GROUP_CLOSE:
4055                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4056                                 closeTabWorkArea(twa);
4057                                 d.current_work_area_ = 0;
4058                                 twa = d.currentTabWorkArea();
4059                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4060                                 if (twa) {
4061                                         // Make sure the work area is up to date.
4062                                         setCurrentWorkArea(twa->currentWorkArea());
4063                                 } else {
4064                                         setCurrentWorkArea(0);
4065                                 }
4066                         }
4067                         break;
4068
4069                 case LFUN_VIEW_CLOSE:
4070                         if (TabWorkArea * twa = d.currentTabWorkArea()) {
4071                                 closeWorkArea(twa->currentWorkArea());
4072                                 d.current_work_area_ = 0;
4073                                 twa = d.currentTabWorkArea();
4074                                 // Switch to the next GuiWorkArea in the found TabWorkArea.
4075                                 if (twa) {
4076                                         // Make sure the work area is up to date.
4077                                         setCurrentWorkArea(twa->currentWorkArea());
4078                                 } else {
4079                                         setCurrentWorkArea(0);
4080                                 }
4081                         }
4082                         break;
4083
4084                 case LFUN_COMPLETION_INLINE:
4085                         if (d.current_work_area_)
4086                                 d.current_work_area_->completer().showInline();
4087                         break;
4088
4089                 case LFUN_COMPLETION_POPUP:
4090                         if (d.current_work_area_)
4091                                 d.current_work_area_->completer().showPopup();
4092                         break;
4093
4094
4095                 case LFUN_COMPLETE:
4096                         if (d.current_work_area_)
4097                                 d.current_work_area_->completer().tab();
4098                         break;
4099
4100                 case LFUN_COMPLETION_CANCEL:
4101                         if (d.current_work_area_) {
4102                                 if (d.current_work_area_->completer().popupVisible())
4103                                         d.current_work_area_->completer().hidePopup();
4104                                 else
4105                                         d.current_work_area_->completer().hideInline();
4106                         }
4107                         break;
4108
4109                 case LFUN_COMPLETION_ACCEPT:
4110                         if (d.current_work_area_)
4111                                 d.current_work_area_->completer().activate();
4112                         break;
4113
4114                 case LFUN_BUFFER_ZOOM_IN:
4115                 case LFUN_BUFFER_ZOOM_OUT:
4116                 case LFUN_BUFFER_ZOOM: {
4117                         // use a signed temp to avoid overflow
4118                         int zoom = lyxrc.currentZoom;
4119                         if (cmd.argument().empty()) {
4120                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4121                                         zoom = lyxrc.zoom;
4122                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4123                                         zoom += 20;
4124                                 else
4125                                         zoom -= 20;
4126                         } else {
4127                                 if (cmd.action() == LFUN_BUFFER_ZOOM)
4128                                         zoom = convert<int>(cmd.argument());
4129                                 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4130                                         zoom += convert<int>(cmd.argument());
4131                                 else
4132                                         zoom -= convert<int>(cmd.argument());
4133                         }
4134
4135                         if (zoom < static_cast<int>(zoom_min_))
4136                                 zoom = zoom_min_;
4137
4138                         lyxrc.currentZoom = zoom;
4139
4140                         dr.setMessage(bformat(_("Zoom level is now %1$d%"), lyxrc.currentZoom));
4141
4142                         // The global QPixmapCache is used in GuiPainter to cache text
4143                         // painting so we must reset it.
4144                         QPixmapCache::clear();
4145                         guiApp->fontLoader().update();
4146                         lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
4147                         break;
4148                 }
4149
4150                 case LFUN_VC_REGISTER:
4151                 case LFUN_VC_RENAME:
4152                 case LFUN_VC_COPY:
4153                 case LFUN_VC_CHECK_IN:
4154                 case LFUN_VC_CHECK_OUT:
4155                 case LFUN_VC_REPO_UPDATE:
4156                 case LFUN_VC_LOCKING_TOGGLE:
4157                 case LFUN_VC_REVERT:
4158                 case LFUN_VC_UNDO_LAST:
4159                 case LFUN_VC_COMMAND:
4160                 case LFUN_VC_COMPARE:
4161                         dispatchVC(cmd, dr);
4162                         break;
4163
4164                 case LFUN_SERVER_GOTO_FILE_ROW:
4165                         if(goToFileRow(to_utf8(cmd.argument())))
4166                                 dr.screenUpdate(Update::Force | Update::FitCursor);
4167                         break;
4168
4169                 case LFUN_LYX_ACTIVATE:
4170                         activateWindow();
4171                         break;
4172
4173                 case LFUN_FORWARD_SEARCH: {
4174                         // it seems safe to assume we have a document buffer, since
4175                         // getStatus wants one.
4176                         LATTEST(doc_buffer);
4177                         Buffer const * doc_master = doc_buffer->masterBuffer();
4178                         FileName const path(doc_master->temppath());
4179                         string const texname = doc_master->isChild(doc_buffer)
4180                                 ? DocFileName(changeExtension(
4181                                         doc_buffer->absFileName(),
4182                                                 "tex")).mangledFileName()
4183                                 : doc_buffer->latexName();
4184                         string const fulltexname =
4185                                 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4186                         string const mastername =
4187                                 removeExtension(doc_master->latexName());
4188                         FileName const dviname(addName(path.absFileName(),
4189                                         addExtension(mastername, "dvi")));
4190                         FileName const pdfname(addName(path.absFileName(),
4191                                         addExtension(mastername, "pdf")));
4192                         bool const have_dvi = dviname.exists();
4193                         bool const have_pdf = pdfname.exists();
4194                         if (!have_dvi && !have_pdf) {
4195                                 dr.setMessage(_("Please, preview the document first."));
4196                                 break;
4197                         }
4198                         string outname = dviname.onlyFileName();
4199                         string command = lyxrc.forward_search_dvi;
4200                         if (!have_dvi || (have_pdf &&
4201                             pdfname.lastModified() > dviname.lastModified())) {
4202                                 outname = pdfname.onlyFileName();
4203                                 command = lyxrc.forward_search_pdf;
4204                         }
4205
4206                         DocIterator cur = bv->cursor();
4207                         int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4208                         LYXERR(Debug::ACTION, "Forward search: row:" << row
4209                                    << " cur:" << cur);
4210                         if (row == -1 || command.empty()) {
4211                                 dr.setMessage(_("Couldn't proceed."));
4212                                 break;
4213                         }
4214                         string texrow = convert<string>(row);
4215
4216                         command = subst(command, "$$n", texrow);
4217                         command = subst(command, "$$f", fulltexname);
4218                         command = subst(command, "$$t", texname);
4219                         command = subst(command, "$$o", outname);
4220
4221                         PathChanger p(path);
4222                         Systemcall one;
4223                         one.startscript(Systemcall::DontWait, command);
4224                         break;
4225                 }
4226
4227                 case LFUN_SPELLING_CONTINUOUSLY:
4228                         lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4229                         dr.screenUpdate(Update::Force);
4230                         break;
4231
4232                 default:
4233                         // The LFUN must be for one of BufferView, Buffer or Cursor;
4234                         // let's try that:
4235                         dispatchToBufferView(cmd, dr);
4236                         break;
4237         }
4238
4239         // Part of automatic menu appearance feature.
4240         if (isFullScreen()) {
4241                 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4242                         menuBar()->hide();
4243         }
4244
4245         // Need to update bv because many LFUNs here might have destroyed it
4246         bv = currentBufferView();
4247
4248         // Clear non-empty selections
4249         // (e.g. from a "char-forward-select" followed by "char-backward-select")
4250         if (bv) {
4251                 Cursor & cur = bv->cursor();
4252                 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4253                         cur.clearSelection();
4254                 }
4255         }
4256 }
4257
4258
4259 bool GuiView::lfunUiToggle(string const & ui_component)
4260 {
4261         if (ui_component == "scrollbar") {
4262                 // hide() is of no help
4263                 if (d.current_work_area_->verticalScrollBarPolicy() ==
4264                         Qt::ScrollBarAlwaysOff)
4265
4266                         d.current_work_area_->setVerticalScrollBarPolicy(
4267                                 Qt::ScrollBarAsNeeded);
4268                 else
4269                         d.current_work_area_->setVerticalScrollBarPolicy(
4270                                 Qt::ScrollBarAlwaysOff);
4271         } else if (ui_component == "statusbar") {
4272                 statusBar()->setVisible(!statusBar()->isVisible());
4273         } else if (ui_component == "menubar") {
4274                 menuBar()->setVisible(!menuBar()->isVisible());
4275         } else
4276         if (ui_component == "frame") {
4277                 int l, t, r, b;
4278                 getContentsMargins(&l, &t, &r, &b);
4279                 //are the frames in default state?
4280                 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4281                 if (l == 0) {
4282                         setContentsMargins(-2, -2, -2, -2);
4283                 } else {
4284                         setContentsMargins(0, 0, 0, 0);
4285                 }
4286         } else
4287         if (ui_component == "fullscreen") {
4288                 toggleFullScreen();
4289         } else
4290                 return false;
4291         return true;
4292 }
4293
4294
4295 void GuiView::toggleFullScreen()
4296 {
4297         if (isFullScreen()) {
4298                 for (int i = 0; i != d.splitter_->count(); ++i)
4299                         d.tabWorkArea(i)->setFullScreen(false);
4300                 setContentsMargins(0, 0, 0, 0);
4301                 setWindowState(windowState() ^ Qt::WindowFullScreen);
4302                 restoreLayout();
4303                 menuBar()->show();
4304                 statusBar()->show();
4305         } else {
4306                 // bug 5274
4307                 hideDialogs("prefs", 0);
4308                 for (int i = 0; i != d.splitter_->count(); ++i)
4309                         d.tabWorkArea(i)->setFullScreen(true);
4310                 setContentsMargins(-2, -2, -2, -2);
4311                 saveLayout();
4312                 setWindowState(windowState() ^ Qt::WindowFullScreen);
4313                 if (lyxrc.full_screen_statusbar)
4314                         statusBar()->hide();
4315                 if (lyxrc.full_screen_menubar)
4316                         menuBar()->hide();
4317                 if (lyxrc.full_screen_toolbars) {
4318                         ToolbarMap::iterator end = d.toolbars_.end();
4319                         for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4320                                 it->second->hide();
4321                 }
4322         }
4323
4324         // give dialogs like the TOC a chance to adapt
4325         updateDialogs();
4326 }
4327
4328
4329 Buffer const * GuiView::updateInset(Inset const * inset)
4330 {
4331         if (!inset)
4332                 return 0;
4333
4334         Buffer const * inset_buffer = &(inset->buffer());
4335
4336         for (int i = 0; i != d.splitter_->count(); ++i) {
4337                 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4338                 if (!wa)
4339                         continue;
4340                 Buffer const * buffer = &(wa->bufferView().buffer());
4341                 if (inset_buffer == buffer)
4342                         wa->scheduleRedraw();
4343         }
4344         return inset_buffer;
4345 }
4346
4347
4348 void GuiView::restartCursor()
4349 {
4350         /* When we move around, or type, it's nice to be able to see
4351          * the cursor immediately after the keypress.
4352          */
4353         if (d.current_work_area_)
4354                 d.current_work_area_->startBlinkingCursor();
4355
4356         // Take this occasion to update the other GUI elements.
4357         updateDialogs();
4358         updateStatusBar();
4359 }
4360
4361
4362 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4363 {
4364         if (d.current_work_area_)
4365                 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4366 }
4367
4368 namespace {
4369
4370 // This list should be kept in sync with the list of insets in
4371 // src/insets/Inset.cpp.  I.e., if a dialog goes with an inset, the
4372 // dialog should have the same name as the inset.
4373 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4374 // docs in LyXAction.cpp.
4375
4376 char const * const dialognames[] = {
4377
4378 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4379 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4380 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4381 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4382 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4383 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4384 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4385 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4386
4387 char const * const * const end_dialognames =
4388         dialognames + (sizeof(dialognames) / sizeof(char *));
4389
4390 class cmpCStr {
4391 public:
4392         cmpCStr(char const * name) : name_(name) {}
4393         bool operator()(char const * other) {
4394                 return strcmp(other, name_) == 0;
4395         }
4396 private:
4397         char const * name_;
4398 };
4399
4400
4401 bool isValidName(string const & name)
4402 {
4403         return find_if(dialognames, end_dialognames,
4404                                 cmpCStr(name.c_str())) != end_dialognames;
4405 }
4406
4407 } // namespace
4408
4409
4410 void GuiView::resetDialogs()
4411 {
4412         // Make sure that no LFUN uses any GuiView.
4413         guiApp->setCurrentView(0);
4414         saveLayout();
4415         saveUISettings();
4416         menuBar()->clear();
4417         constructToolbars();
4418         guiApp->menus().fillMenuBar(menuBar(), this, false);
4419         d.layout_->updateContents(true);
4420         // Now update controls with current buffer.
4421         guiApp->setCurrentView(this);
4422         restoreLayout();
4423         restartCursor();
4424 }
4425
4426
4427 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4428 {
4429         if (!isValidName(name))
4430                 return 0;
4431
4432         map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4433
4434         if (it != d.dialogs_.end()) {
4435                 if (hide_it)
4436                         it->second->hideView();
4437                 return it->second.get();
4438         }
4439
4440         Dialog * dialog = build(name);
4441         d.dialogs_[name].reset(dialog);
4442         if (lyxrc.allow_geometry_session)
4443                 dialog->restoreSession();
4444         if (hide_it)
4445                 dialog->hideView();
4446         return dialog;
4447 }
4448
4449
4450 void GuiView::showDialog(string const & name, string const & data,
4451         Inset * inset)
4452 {
4453         triggerShowDialog(toqstr(name), toqstr(data), inset);
4454 }
4455
4456
4457 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4458         Inset * inset)
4459 {
4460         if (d.in_show_)
4461                 return;
4462
4463         const string name = fromqstr(qname);
4464         const string data = fromqstr(qdata);
4465
4466         d.in_show_ = true;
4467         try {
4468                 Dialog * dialog = findOrBuild(name, false);
4469                 if (dialog) {
4470                         bool const visible = dialog->isVisibleView();
4471                         dialog->showData(data);
4472                         if (inset && currentBufferView())
4473                                 currentBufferView()->editInset(name, inset);
4474                         // We only set the focus to the new dialog if it was not yet
4475                         // visible in order not to change the existing previous behaviour
4476                         if (visible) {
4477                                 // activateWindow is needed for floating dockviews
4478                                 dialog->asQWidget()->raise();
4479                                 dialog->asQWidget()->activateWindow();
4480                                 dialog->asQWidget()->setFocus();
4481                         }
4482                 }
4483         }
4484         catch (ExceptionMessage const & ex) {
4485                 d.in_show_ = false;
4486                 throw ex;
4487         }
4488         d.in_show_ = false;
4489 }
4490
4491
4492 bool GuiView::isDialogVisible(string const & name) const
4493 {
4494         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4495         if (it == d.dialogs_.end())
4496                 return false;
4497         return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4498 }
4499
4500
4501 void GuiView::hideDialog(string const & name, Inset * inset)
4502 {
4503         map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4504         if (it == d.dialogs_.end())
4505                 return;
4506
4507         if (inset) {
4508                 if (!currentBufferView())
4509                         return;
4510                 if (inset != currentBufferView()->editedInset(name))
4511                         return;
4512         }
4513
4514         Dialog * const dialog = it->second.get();
4515         if (dialog->isVisibleView())
4516                 dialog->hideView();
4517         if (currentBufferView())
4518                 currentBufferView()->editInset(name, 0);
4519 }
4520
4521
4522 void GuiView::disconnectDialog(string const & name)
4523 {
4524         if (!isValidName(name))
4525                 return;
4526         if (currentBufferView())
4527                 currentBufferView()->editInset(name, 0);
4528 }
4529
4530
4531 void GuiView::hideAll() const
4532 {
4533         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
4534         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4535
4536         for(; it != end; ++it)
4537                 it->second->hideView();
4538 }
4539
4540
4541 void GuiView::updateDialogs()
4542 {
4543         map<string, DialogPtr>::const_iterator it  = d.dialogs_.begin();
4544         map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4545
4546         for(; it != end; ++it) {
4547                 Dialog * dialog = it->second.get();
4548                 if (dialog) {
4549                         if (dialog->needBufferOpen() && !documentBufferView())
4550                                 hideDialog(fromqstr(dialog->name()), 0);
4551                         else if (dialog->isVisibleView())
4552                                 dialog->checkStatus();
4553                 }
4554         }
4555         updateToolbars();
4556         updateLayoutList();
4557 }
4558
4559 Dialog * createDialog(GuiView & lv, string const & name);
4560
4561 // will be replaced by a proper factory...
4562 Dialog * createGuiAbout(GuiView & lv);
4563 Dialog * createGuiBibtex(GuiView & lv);
4564 Dialog * createGuiChanges(GuiView & lv);
4565 Dialog * createGuiCharacter(GuiView & lv);
4566 Dialog * createGuiCitation(GuiView & lv);
4567 Dialog * createGuiCompare(GuiView & lv);
4568 Dialog * createGuiCompareHistory(GuiView & lv);
4569 Dialog * createGuiDelimiter(GuiView & lv);
4570 Dialog * createGuiDocument(GuiView & lv);
4571 Dialog * createGuiErrorList(GuiView & lv);
4572 Dialog * createGuiExternal(GuiView & lv);
4573 Dialog * createGuiGraphics(GuiView & lv);
4574 Dialog * createGuiInclude(GuiView & lv);
4575 Dialog * createGuiIndex(GuiView & lv);
4576 Dialog * createGuiListings(GuiView & lv);
4577 Dialog * createGuiLog(GuiView & lv);
4578 Dialog * createGuiMathMatrix(GuiView & lv);
4579 Dialog * createGuiNote(GuiView & lv);
4580 Dialog * createGuiParagraph(GuiView & lv);
4581 Dialog * createGuiPhantom(GuiView & lv);
4582 Dialog * createGuiPreferences(GuiView & lv);
4583 Dialog * createGuiPrint(GuiView & lv);
4584 Dialog * createGuiPrintindex(GuiView & lv);
4585 Dialog * createGuiRef(GuiView & lv);
4586 Dialog * createGuiSearch(GuiView & lv);
4587 Dialog * createGuiSearchAdv(GuiView & lv);
4588 Dialog * createGuiSendTo(GuiView & lv);
4589 Dialog * createGuiShowFile(GuiView & lv);
4590 Dialog * createGuiSpellchecker(GuiView & lv);
4591 Dialog * createGuiSymbols(GuiView & lv);
4592 Dialog * createGuiTabularCreate(GuiView & lv);
4593 Dialog * createGuiTexInfo(GuiView & lv);
4594 Dialog * createGuiToc(GuiView & lv);
4595 Dialog * createGuiThesaurus(GuiView & lv);
4596 Dialog * createGuiViewSource(GuiView & lv);
4597 Dialog * createGuiWrap(GuiView & lv);
4598 Dialog * createGuiProgressView(GuiView & lv);
4599
4600
4601
4602 Dialog * GuiView::build(string const & name)
4603 {
4604         LASSERT(isValidName(name), return 0);
4605
4606         Dialog * dialog = createDialog(*this, name);
4607         if (dialog)
4608                 return dialog;
4609
4610         if (name == "aboutlyx")
4611                 return createGuiAbout(*this);
4612         if (name == "bibtex")
4613                 return createGuiBibtex(*this);
4614         if (name == "changes")
4615                 return createGuiChanges(*this);
4616         if (name == "character")
4617                 return createGuiCharacter(*this);
4618         if (name == "citation")
4619                 return createGuiCitation(*this);
4620         if (name == "compare")
4621                 return createGuiCompare(*this);
4622         if (name == "comparehistory")
4623                 return createGuiCompareHistory(*this);
4624         if (name == "document")
4625                 return createGuiDocument(*this);
4626         if (name == "errorlist")
4627                 return createGuiErrorList(*this);
4628         if (name == "external")
4629                 return createGuiExternal(*this);
4630         if (name == "file")
4631                 return createGuiShowFile(*this);
4632         if (name == "findreplace")
4633                 return createGuiSearch(*this);
4634         if (name == "findreplaceadv")
4635                 return createGuiSearchAdv(*this);
4636         if (name == "graphics")
4637                 return createGuiGraphics(*this);
4638         if (name == "include")
4639                 return createGuiInclude(*this);
4640         if (name == "index")
4641                 return createGuiIndex(*this);
4642         if (name == "index_print")
4643                 return createGuiPrintindex(*this);
4644         if (name == "listings")
4645                 return createGuiListings(*this);
4646         if (name == "log")
4647                 return createGuiLog(*this);
4648         if (name == "mathdelimiter")
4649                 return createGuiDelimiter(*this);
4650         if (name == "mathmatrix")
4651                 return createGuiMathMatrix(*this);
4652         if (name == "note")
4653                 return createGuiNote(*this);
4654         if (name == "paragraph")
4655                 return createGuiParagraph(*this);
4656         if (name == "phantom")
4657                 return createGuiPhantom(*this);
4658         if (name == "prefs")
4659                 return createGuiPreferences(*this);
4660         if (name == "ref")
4661                 return createGuiRef(*this);
4662         if (name == "sendto")
4663                 return createGuiSendTo(*this);
4664         if (name == "spellchecker")
4665                 return createGuiSpellchecker(*this);
4666         if (name == "symbols")
4667                 return createGuiSymbols(*this);
4668         if (name == "tabularcreate")
4669                 return createGuiTabularCreate(*this);
4670         if (name == "texinfo")
4671                 return createGuiTexInfo(*this);
4672         if (name == "thesaurus")
4673                 return createGuiThesaurus(*this);
4674         if (name == "toc")
4675                 return createGuiToc(*this);
4676         if (name == "view-source")
4677                 return createGuiViewSource(*this);
4678         if (name == "wrap")
4679                 return createGuiWrap(*this);
4680         if (name == "progress")
4681                 return createGuiProgressView(*this);
4682
4683         return 0;
4684 }
4685
4686
4687 SEMenu::SEMenu(QWidget * parent)
4688 {
4689         QAction * action = addAction(qt_("Disable Shell Escape"));
4690         connect(action, SIGNAL(triggered()),
4691                 parent, SLOT(disableShellEscape()));
4692 }
4693
4694
4695 } // namespace frontend
4696 } // namespace lyx
4697
4698 #include "moc_GuiView.cpp"