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