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