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