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