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