3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
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"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
56 #include "LyXAction.h"
60 #include "Paragraph.h"
61 #include "SpellChecker.h"
64 #include "TextClass.h"
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"
87 #include <QApplication>
88 #include <QCloseEvent>
90 #include <QDesktopWidget>
91 #include <QDragEnterEvent>
94 #include <QFutureWatcher>
103 #include <QPixmapCache>
105 #include <QPushButton>
106 #include <QScrollBar>
108 #include <QShowEvent>
110 #include <QStackedWidget>
111 #include <QStatusBar>
112 #include <QSvgRenderer>
113 #include <QtConcurrentRun>
121 // sync with GuiAlert.cpp
122 #define EXPORT_in_THREAD 1
125 #include "support/bind.h"
129 #ifdef HAVE_SYS_TIME_H
130 # include <sys/time.h>
138 using namespace lyx::support;
142 using support::addExtension;
143 using support::changeExtension;
144 using support::removeExtension;
150 class BackgroundWidget : public QWidget
153 BackgroundWidget(int width, int height)
154 : width_(width), height_(height)
156 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
157 if (!lyxrc.show_banner)
159 /// The text to be written on top of the pixmap
160 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
161 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
162 /// The text to be written on top of the pixmap
163 QString const text = lyx_version ?
164 qt_("version ") + lyx_version : qt_("unknown version");
165 #if QT_VERSION >= 0x050000
166 QString imagedir = "images/";
167 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
168 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
169 if (svgRenderer.isValid()) {
170 splash_ = QPixmap(splashSize());
171 QPainter painter(&splash_);
172 svgRenderer.render(&painter);
173 splash_.setDevicePixelRatio(pixelRatio());
175 splash_ = getPixmap("images/", "banner", "png");
178 splash_ = getPixmap("images/", "banner", "svgz,png");
181 QPainter pain(&splash_);
182 pain.setPen(QColor(0, 0, 0));
183 qreal const fsize = fontSize();
186 qreal locscale = htextsize.toFloat(&ok);
189 QPointF const position = textPosition(false);
190 QPointF const hposition = textPosition(true);
191 QRectF const hrect(hposition, splashSize());
193 "widget pixel ratio: " << pixelRatio() <<
194 " splash pixel ratio: " << splashPixelRatio() <<
195 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Bold);
200 font.setPointSizeF(fsize);
202 pain.drawText(position, text);
203 // The font used to display the version info
204 font.setStyleHint(QFont::SansSerif);
205 font.setWeight(QFont::Normal);
206 font.setPointSizeF(hfsize);
207 // Check how long the logo gets with the current font
208 // and adapt if the font is running wider than what
210 QFontMetrics fm(font);
211 // Split the title into lines to measure the longest line
212 // in the current l7n.
213 QStringList titlesegs = htext.split('\n');
215 int hline = fm.height();
216 QStringList::const_iterator sit;
217 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
218 if (fm.width(*sit) > wline)
219 wline = fm.width(*sit);
221 // The longest line in the reference font (for English)
222 // is 180. Calculate scale factor from that.
223 double const wscale = wline > 0 ? (180.0 / wline) : 1;
224 // Now do the same for the height (necessary for condensed fonts)
225 double const hscale = (34.0 / hline);
226 // take the lower of the two scale factors.
227 double const scale = min(wscale, hscale);
228 // Now rescale. Also consider l7n's offset factor.
229 font.setPointSizeF(hfsize * scale * locscale);
232 pain.drawText(hrect, Qt::AlignLeft, htext);
233 setFocusPolicy(Qt::StrongFocus);
236 void paintEvent(QPaintEvent *)
238 int const w = width_;
239 int const h = height_;
240 int const x = (width() - w) / 2;
241 int const y = (height() - h) / 2;
243 "widget pixel ratio: " << pixelRatio() <<
244 " splash pixel ratio: " << splashPixelRatio() <<
245 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
247 pain.drawPixmap(x, y, w, h, splash_);
250 void keyPressEvent(QKeyEvent * ev)
253 setKeySymbol(&sym, ev);
255 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
267 /// Current ratio between physical pixels and device-independent pixels
268 double pixelRatio() const {
269 #if QT_VERSION >= 0x050000
270 return qt_scale_factor * devicePixelRatio();
276 qreal fontSize() const {
277 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
280 QPointF textPosition(bool const heading) const {
281 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
282 : QPointF(width_/2 - 18, height_/2 + 45);
285 QSize splashSize() const {
287 static_cast<unsigned int>(width_ * pixelRatio()),
288 static_cast<unsigned int>(height_ * pixelRatio()));
291 /// Ratio between physical pixels and device-independent pixels of splash image
292 double splashPixelRatio() const {
293 #if QT_VERSION >= 0x050000
294 return splash_.devicePixelRatio();
302 /// Toolbar store providing access to individual toolbars by name.
303 typedef map<string, GuiToolbar *> ToolbarMap;
305 typedef shared_ptr<Dialog> DialogPtr;
310 class GuiView::GuiViewPrivate
313 GuiViewPrivate(GuiViewPrivate const &);
314 void operator=(GuiViewPrivate const &);
316 GuiViewPrivate(GuiView * gv)
317 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
318 layout_(0), autosave_timeout_(5000),
321 // hardcode here the platform specific icon size
322 smallIconSize = 16; // scaling problems
323 normalIconSize = 20; // ok, default if iconsize.png is missing
324 bigIconSize = 26; // better for some math icons
325 hugeIconSize = 32; // better for hires displays
328 // if it exists, use width of iconsize.png as normal size
329 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
330 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
332 QImage image(toqstr(fn.absFileName()));
333 if (image.width() < int(smallIconSize))
334 normalIconSize = smallIconSize;
335 else if (image.width() > int(giantIconSize))
336 normalIconSize = giantIconSize;
338 normalIconSize = image.width();
341 splitter_ = new QSplitter;
342 bg_widget_ = new BackgroundWidget(400, 250);
343 stack_widget_ = new QStackedWidget;
344 stack_widget_->addWidget(bg_widget_);
345 stack_widget_->addWidget(splitter_);
348 // TODO cleanup, remove the singleton, handle multiple Windows?
349 progress_ = ProgressInterface::instance();
350 if (!dynamic_cast<GuiProgress*>(progress_)) {
351 progress_ = new GuiProgress; // TODO who deletes it
352 ProgressInterface::setInstance(progress_);
355 dynamic_cast<GuiProgress*>(progress_),
356 SIGNAL(updateStatusBarMessage(QString const&)),
357 gv, SLOT(updateStatusBarMessage(QString const&)));
359 dynamic_cast<GuiProgress*>(progress_),
360 SIGNAL(clearMessageText()),
361 gv, SLOT(clearMessageText()));
368 delete stack_widget_;
373 stack_widget_->setCurrentWidget(bg_widget_);
374 bg_widget_->setUpdatesEnabled(true);
375 bg_widget_->setFocus();
378 int tabWorkAreaCount()
380 return splitter_->count();
383 TabWorkArea * tabWorkArea(int i)
385 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
388 TabWorkArea * currentTabWorkArea()
390 int areas = tabWorkAreaCount();
392 // The first TabWorkArea is always the first one, if any.
393 return tabWorkArea(0);
395 for (int i = 0; i != areas; ++i) {
396 TabWorkArea * twa = tabWorkArea(i);
397 if (current_main_work_area_ == twa->currentWorkArea())
401 // None has the focus so we just take the first one.
402 return tabWorkArea(0);
405 int countWorkAreasOf(Buffer & buf)
407 int areas = tabWorkAreaCount();
409 for (int i = 0; i != areas; ++i) {
410 TabWorkArea * twa = tabWorkArea(i);
411 if (twa->workArea(buf))
417 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
419 if (processing_thread_watcher_.isRunning()) {
420 // we prefer to cancel this preview in order to keep a snappy
424 processing_thread_watcher_.setFuture(f);
427 QSize iconSize(docstring const & icon_size)
430 if (icon_size == "small")
431 size = smallIconSize;
432 else if (icon_size == "normal")
433 size = normalIconSize;
434 else if (icon_size == "big")
436 else if (icon_size == "huge")
438 else if (icon_size == "giant")
439 size = giantIconSize;
441 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
443 if (size < smallIconSize)
444 size = smallIconSize;
446 return QSize(size, size);
449 QSize iconSize(QString const & icon_size)
451 return iconSize(qstring_to_ucs4(icon_size));
454 string & iconSize(QSize const & qsize)
456 LATTEST(qsize.width() == qsize.height());
458 static string icon_size;
460 unsigned int size = qsize.width();
462 if (size < smallIconSize)
463 size = smallIconSize;
465 if (size == smallIconSize)
467 else if (size == normalIconSize)
468 icon_size = "normal";
469 else if (size == bigIconSize)
471 else if (size == hugeIconSize)
473 else if (size == giantIconSize)
476 icon_size = convert<string>(size);
483 GuiWorkArea * current_work_area_;
484 GuiWorkArea * current_main_work_area_;
485 QSplitter * splitter_;
486 QStackedWidget * stack_widget_;
487 BackgroundWidget * bg_widget_;
489 ToolbarMap toolbars_;
490 ProgressInterface* progress_;
491 /// The main layout box.
493 * \warning Don't Delete! The layout box is actually owned by
494 * whichever toolbar contains it. All the GuiView class needs is a
495 * means of accessing it.
497 * FIXME: replace that with a proper model so that we are not limited
498 * to only one dialog.
503 map<string, DialogPtr> dialogs_;
505 unsigned int smallIconSize;
506 unsigned int normalIconSize;
507 unsigned int bigIconSize;
508 unsigned int hugeIconSize;
509 unsigned int giantIconSize;
511 QTimer statusbar_timer_;
512 /// auto-saving of buffers
513 Timeout autosave_timeout_;
514 /// flag against a race condition due to multiclicks, see bug #1119
518 TocModels toc_models_;
521 QFutureWatcher<docstring> autosave_watcher_;
522 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
524 string last_export_format;
525 string processing_format;
527 static QSet<Buffer const *> busyBuffers;
528 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
529 Buffer * buffer, string const & format);
530 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
531 Buffer * buffer, string const & format);
532 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
533 Buffer * buffer, string const & format);
534 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
537 static Buffer::ExportStatus runAndDestroy(const T& func,
538 Buffer const * orig, Buffer * buffer, string const & format);
540 // TODO syncFunc/previewFunc: use bind
541 bool asyncBufferProcessing(string const & argument,
542 Buffer const * used_buffer,
543 docstring const & msg,
544 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
545 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
546 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
549 QVector<GuiWorkArea*> guiWorkAreas();
552 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
555 GuiView::GuiView(int id)
556 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
557 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
560 connect(this, SIGNAL(bufferViewChanged()),
561 this, SLOT(onBufferViewChanged()));
563 // GuiToolbars *must* be initialised before the menu bar.
564 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
567 // set ourself as the current view. This is needed for the menu bar
568 // filling, at least for the static special menu item on Mac. Otherwise
569 // they are greyed out.
570 guiApp->setCurrentView(this);
572 // Fill up the menu bar.
573 guiApp->menus().fillMenuBar(menuBar(), this, true);
575 setCentralWidget(d.stack_widget_);
577 // Start autosave timer
578 if (lyxrc.autosave) {
579 // The connection is closed when this is destroyed.
580 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
581 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
582 d.autosave_timeout_.start();
584 connect(&d.statusbar_timer_, SIGNAL(timeout()),
585 this, SLOT(clearMessage()));
587 // We don't want to keep the window in memory if it is closed.
588 setAttribute(Qt::WA_DeleteOnClose, true);
590 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
591 // QIcon::fromTheme was introduced in Qt 4.6
592 #if (QT_VERSION >= 0x040600)
593 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
594 // since the icon is provided in the application bundle. We use a themed
595 // version when available and use the bundled one as fallback.
596 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
598 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
604 // use tabbed dock area for multiple docks
605 // (such as "source" and "messages")
606 setDockOptions(QMainWindow::ForceTabbedDocks);
609 setAcceptDrops(true);
611 // add busy indicator to statusbar
612 QLabel * busylabel = new QLabel(statusBar());
613 statusBar()->addPermanentWidget(busylabel);
614 search_mode mode = theGuiApp()->imageSearchMode();
615 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
616 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
617 busylabel->setMovie(busyanim);
621 connect(&d.processing_thread_watcher_, SIGNAL(started()),
622 busylabel, SLOT(show()));
623 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
624 busylabel, SLOT(hide()));
626 QFontMetrics const fm(statusBar()->fontMetrics());
627 int const iconheight = max(int(d.normalIconSize), fm.height());
628 QSize const iconsize(iconheight, iconheight);
630 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
631 shell_escape_ = new QLabel(statusBar());
632 shell_escape_->setPixmap(shellescape);
633 shell_escape_->setScaledContents(true);
634 shell_escape_->setAlignment(Qt::AlignCenter);
635 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
636 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
637 "external commands for this document. "
638 "Right click to change."));
639 SEMenu * menu = new SEMenu(this);
640 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
641 menu, SLOT(showMenu(QPoint)));
642 shell_escape_->hide();
643 statusBar()->addPermanentWidget(shell_escape_);
645 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
646 read_only_ = new QLabel(statusBar());
647 read_only_->setPixmap(readonly);
648 read_only_->setScaledContents(true);
649 read_only_->setAlignment(Qt::AlignCenter);
651 statusBar()->addPermanentWidget(read_only_);
653 version_control_ = new QLabel(statusBar());
654 version_control_->setAlignment(Qt::AlignCenter);
655 version_control_->setFrameStyle(QFrame::StyledPanel);
656 version_control_->hide();
657 statusBar()->addPermanentWidget(version_control_);
659 statusBar()->setSizeGripEnabled(true);
662 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
663 SLOT(autoSaveThreadFinished()));
665 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
666 SLOT(processingThreadStarted()));
667 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
668 SLOT(processingThreadFinished()));
670 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
671 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
673 // set custom application bars context menu, e.g. tool bar and menu bar
674 setContextMenuPolicy(Qt::CustomContextMenu);
675 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
676 SLOT(toolBarPopup(const QPoint &)));
678 // Forbid too small unresizable window because it can happen
679 // with some window manager under X11.
680 setMinimumSize(300, 200);
682 if (lyxrc.allow_geometry_session) {
683 // Now take care of session management.
688 // no session handling, default to a sane size.
689 setGeometry(50, 50, 690, 510);
692 // clear session data if any.
694 settings.remove("views");
704 void GuiView::disableShellEscape()
706 BufferView * bv = documentBufferView();
709 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
710 bv->buffer().params().shell_escape = false;
711 bv->processUpdateFlags(Update::Force);
715 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
717 QVector<GuiWorkArea*> areas;
718 for (int i = 0; i < tabWorkAreaCount(); i++) {
719 TabWorkArea* ta = tabWorkArea(i);
720 for (int u = 0; u < ta->count(); u++) {
721 areas << ta->workArea(u);
727 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
728 string const & format)
730 docstring const fmt = theFormats().prettyName(format);
733 case Buffer::ExportSuccess:
734 msg = bformat(_("Successful export to format: %1$s"), fmt);
736 case Buffer::ExportCancel:
737 msg = _("Document export cancelled.");
739 case Buffer::ExportError:
740 case Buffer::ExportNoPathToFormat:
741 case Buffer::ExportTexPathHasSpaces:
742 case Buffer::ExportConverterError:
743 msg = bformat(_("Error while exporting format: %1$s"), fmt);
745 case Buffer::PreviewSuccess:
746 msg = bformat(_("Successful preview of format: %1$s"), fmt);
748 case Buffer::PreviewError:
749 msg = bformat(_("Error while previewing format: %1$s"), fmt);
751 case Buffer::ExportKilled:
752 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
759 void GuiView::processingThreadStarted()
764 void GuiView::processingThreadFinished()
766 QFutureWatcher<Buffer::ExportStatus> const * watcher =
767 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
769 Buffer::ExportStatus const status = watcher->result();
770 handleExportStatus(this, status, d.processing_format);
773 BufferView const * const bv = currentBufferView();
774 if (bv && !bv->buffer().errorList("Export").empty()) {
779 bool const error = (status != Buffer::ExportSuccess &&
780 status != Buffer::PreviewSuccess &&
781 status != Buffer::ExportCancel);
783 ErrorList & el = bv->buffer().errorList(d.last_export_format);
784 // at this point, we do not know if buffer-view or
785 // master-buffer-view was called. If there was an export error,
786 // and the current buffer's error log is empty, we guess that
787 // it must be master-buffer-view that was called so we set
789 errors(d.last_export_format, el.empty());
794 void GuiView::autoSaveThreadFinished()
796 QFutureWatcher<docstring> const * watcher =
797 static_cast<QFutureWatcher<docstring> const *>(sender());
798 message(watcher->result());
803 void GuiView::saveLayout() const
806 settings.setValue("zoom_ratio", zoom_ratio_);
807 settings.setValue("devel_mode", devel_mode_);
808 settings.beginGroup("views");
809 settings.beginGroup(QString::number(id_));
810 #if defined(Q_WS_X11) || defined(QPA_XCB)
811 settings.setValue("pos", pos());
812 settings.setValue("size", size());
814 settings.setValue("geometry", saveGeometry());
816 settings.setValue("layout", saveState(0));
817 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
821 void GuiView::saveUISettings() const
825 // Save the toolbar private states
826 ToolbarMap::iterator end = d.toolbars_.end();
827 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
828 it->second->saveSession(settings);
829 // Now take care of all other dialogs
830 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
831 for (; it!= d.dialogs_.end(); ++it)
832 it->second->saveSession(settings);
836 bool GuiView::restoreLayout()
839 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
840 // Actual zoom value: default zoom + fractional offset
841 int zoom = lyxrc.defaultZoom * zoom_ratio_;
842 if (zoom < static_cast<int>(zoom_min_))
844 lyxrc.currentZoom = zoom;
845 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
846 settings.beginGroup("views");
847 settings.beginGroup(QString::number(id_));
848 QString const icon_key = "icon_size";
849 if (!settings.contains(icon_key))
852 //code below is skipped when when ~/.config/LyX is (re)created
853 setIconSize(d.iconSize(settings.value(icon_key).toString()));
855 #if defined(Q_WS_X11) || defined(QPA_XCB)
856 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
857 QSize size = settings.value("size", QSize(690, 510)).toSize();
861 // Work-around for bug #6034: the window ends up in an undetermined
862 // state when trying to restore a maximized window when it is
863 // already maximized.
864 if (!(windowState() & Qt::WindowMaximized))
865 if (!restoreGeometry(settings.value("geometry").toByteArray()))
866 setGeometry(50, 50, 690, 510);
868 // Make sure layout is correctly oriented.
869 setLayoutDirection(qApp->layoutDirection());
871 // Allow the toc and view-source dock widget to be restored if needed.
873 if ((dialog = findOrBuild("toc", true)))
874 // see bug 5082. At least setup title and enabled state.
875 // Visibility will be adjusted by restoreState below.
876 dialog->prepareView();
877 if ((dialog = findOrBuild("view-source", true)))
878 dialog->prepareView();
879 if ((dialog = findOrBuild("progress", true)))
880 dialog->prepareView();
882 if (!restoreState(settings.value("layout").toByteArray(), 0))
885 // init the toolbars that have not been restored
886 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
887 Toolbars::Infos::iterator end = guiApp->toolbars().end();
888 for (; cit != end; ++cit) {
889 GuiToolbar * tb = toolbar(cit->name);
890 if (tb && !tb->isRestored())
891 initToolbar(cit->name);
894 // update lock (all) toolbars positions
895 updateLockToolbars();
902 GuiToolbar * GuiView::toolbar(string const & name)
904 ToolbarMap::iterator it = d.toolbars_.find(name);
905 if (it != d.toolbars_.end())
908 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
913 void GuiView::updateLockToolbars()
915 toolbarsMovable_ = false;
916 for (ToolbarInfo const & info : guiApp->toolbars()) {
917 GuiToolbar * tb = toolbar(info.name);
918 if (tb && tb->isMovable())
919 toolbarsMovable_ = true;
924 void GuiView::constructToolbars()
926 ToolbarMap::iterator it = d.toolbars_.begin();
927 for (; it != d.toolbars_.end(); ++it)
931 // I don't like doing this here, but the standard toolbar
932 // destroys this object when it's destroyed itself (vfr)
933 d.layout_ = new LayoutBox(*this);
934 d.stack_widget_->addWidget(d.layout_);
935 d.layout_->move(0,0);
937 // extracts the toolbars from the backend
938 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
939 Toolbars::Infos::iterator end = guiApp->toolbars().end();
940 for (; cit != end; ++cit)
941 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
945 void GuiView::initToolbars()
947 // extracts the toolbars from the backend
948 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
949 Toolbars::Infos::iterator end = guiApp->toolbars().end();
950 for (; cit != end; ++cit)
951 initToolbar(cit->name);
955 void GuiView::initToolbar(string const & name)
957 GuiToolbar * tb = toolbar(name);
960 int const visibility = guiApp->toolbars().defaultVisibility(name);
961 bool newline = !(visibility & Toolbars::SAMEROW);
962 tb->setVisible(false);
963 tb->setVisibility(visibility);
965 if (visibility & Toolbars::TOP) {
967 addToolBarBreak(Qt::TopToolBarArea);
968 addToolBar(Qt::TopToolBarArea, tb);
971 if (visibility & Toolbars::BOTTOM) {
973 addToolBarBreak(Qt::BottomToolBarArea);
974 addToolBar(Qt::BottomToolBarArea, tb);
977 if (visibility & Toolbars::LEFT) {
979 addToolBarBreak(Qt::LeftToolBarArea);
980 addToolBar(Qt::LeftToolBarArea, tb);
983 if (visibility & Toolbars::RIGHT) {
985 addToolBarBreak(Qt::RightToolBarArea);
986 addToolBar(Qt::RightToolBarArea, tb);
989 if (visibility & Toolbars::ON)
990 tb->setVisible(true);
992 tb->setMovable(true);
996 TocModels & GuiView::tocModels()
998 return d.toc_models_;
1002 void GuiView::setFocus()
1004 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1005 QMainWindow::setFocus();
1009 bool GuiView::hasFocus() const
1011 if (currentWorkArea())
1012 return currentWorkArea()->hasFocus();
1013 if (currentMainWorkArea())
1014 return currentMainWorkArea()->hasFocus();
1015 return d.bg_widget_->hasFocus();
1019 void GuiView::focusInEvent(QFocusEvent * e)
1021 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1022 QMainWindow::focusInEvent(e);
1023 // Make sure guiApp points to the correct view.
1024 guiApp->setCurrentView(this);
1025 if (currentWorkArea())
1026 currentWorkArea()->setFocus();
1027 else if (currentMainWorkArea())
1028 currentMainWorkArea()->setFocus();
1030 d.bg_widget_->setFocus();
1034 void GuiView::showEvent(QShowEvent * e)
1036 LYXERR(Debug::GUI, "Passed Geometry "
1037 << size().height() << "x" << size().width()
1038 << "+" << pos().x() << "+" << pos().y());
1040 if (d.splitter_->count() == 0)
1041 // No work area, switch to the background widget.
1045 QMainWindow::showEvent(e);
1049 bool GuiView::closeScheduled()
1056 bool GuiView::prepareAllBuffersForLogout()
1058 Buffer * first = theBufferList().first();
1062 // First, iterate over all buffers and ask the users if unsaved
1063 // changes should be saved.
1064 // We cannot use a for loop as the buffer list cycles.
1067 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1069 b = theBufferList().next(b);
1070 } while (b != first);
1072 // Next, save session state
1073 // When a view/window was closed before without quitting LyX, there
1074 // are already entries in the lastOpened list.
1075 theSession().lastOpened().clear();
1082 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1083 ** is responsibility of the container (e.g., dialog)
1085 void GuiView::closeEvent(QCloseEvent * close_event)
1087 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1089 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1090 Alert::warning(_("Exit LyX"),
1091 _("LyX could not be closed because documents are being processed by LyX."));
1092 close_event->setAccepted(false);
1096 // If the user pressed the x (so we didn't call closeView
1097 // programmatically), we want to clear all existing entries.
1099 theSession().lastOpened().clear();
1104 // it can happen that this event arrives without selecting the view,
1105 // e.g. when clicking the close button on a background window.
1107 if (!closeWorkAreaAll()) {
1109 close_event->ignore();
1113 // Make sure that nothing will use this to be closed View.
1114 guiApp->unregisterView(this);
1116 if (isFullScreen()) {
1117 // Switch off fullscreen before closing.
1122 // Make sure the timer time out will not trigger a statusbar update.
1123 d.statusbar_timer_.stop();
1125 // Saving fullscreen requires additional tweaks in the toolbar code.
1126 // It wouldn't also work under linux natively.
1127 if (lyxrc.allow_geometry_session) {
1132 close_event->accept();
1136 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1138 if (event->mimeData()->hasUrls())
1140 /// \todo Ask lyx-devel is this is enough:
1141 /// if (event->mimeData()->hasFormat("text/plain"))
1142 /// event->acceptProposedAction();
1146 void GuiView::dropEvent(QDropEvent * event)
1148 QList<QUrl> files = event->mimeData()->urls();
1149 if (files.isEmpty())
1152 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1153 for (int i = 0; i != files.size(); ++i) {
1154 string const file = os::internal_path(fromqstr(
1155 files.at(i).toLocalFile()));
1159 string const ext = support::getExtension(file);
1160 vector<const Format *> found_formats;
1162 // Find all formats that have the correct extension.
1163 vector<const Format *> const & import_formats
1164 = theConverters().importableFormats();
1165 vector<const Format *>::const_iterator it = import_formats.begin();
1166 for (; it != import_formats.end(); ++it)
1167 if ((*it)->hasExtension(ext))
1168 found_formats.push_back(*it);
1171 if (found_formats.size() >= 1) {
1172 if (found_formats.size() > 1) {
1173 //FIXME: show a dialog to choose the correct importable format
1174 LYXERR(Debug::FILES,
1175 "Multiple importable formats found, selecting first");
1177 string const arg = found_formats[0]->name() + " " + file;
1178 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1181 //FIXME: do we have to explicitly check whether it's a lyx file?
1182 LYXERR(Debug::FILES,
1183 "No formats found, trying to open it as a lyx file");
1184 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1186 // add the functions to the queue
1187 guiApp->addToFuncRequestQueue(cmd);
1190 // now process the collected functions. We perform the events
1191 // asynchronously. This prevents potential problems in case the
1192 // BufferView is closed within an event.
1193 guiApp->processFuncRequestQueueAsync();
1197 void GuiView::message(docstring const & str)
1199 if (ForkedProcess::iAmAChild())
1202 // call is moved to GUI-thread by GuiProgress
1203 d.progress_->appendMessage(toqstr(str));
1207 void GuiView::clearMessageText()
1209 message(docstring());
1213 void GuiView::updateStatusBarMessage(QString const & str)
1215 statusBar()->showMessage(str);
1216 d.statusbar_timer_.stop();
1217 d.statusbar_timer_.start(3000);
1221 void GuiView::clearMessage()
1223 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1224 // the hasFocus function mostly returns false, even if the focus is on
1225 // a workarea in this view.
1229 d.statusbar_timer_.stop();
1233 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1235 if (wa != d.current_work_area_
1236 || wa->bufferView().buffer().isInternal())
1238 Buffer const & buf = wa->bufferView().buffer();
1239 // Set the windows title
1240 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1241 if (buf.notifiesExternalModification()) {
1242 title = bformat(_("%1$s (modified externally)"), title);
1243 // If the external modification status has changed, then maybe the status of
1244 // buffer-save has changed too.
1248 title += from_ascii(" - LyX");
1250 setWindowTitle(toqstr(title));
1251 // Sets the path for the window: this is used by OSX to
1252 // allow a context click on the title bar showing a menu
1253 // with the path up to the file
1254 setWindowFilePath(toqstr(buf.absFileName()));
1255 // Tell Qt whether the current document is changed
1256 setWindowModified(!buf.isClean());
1258 if (buf.params().shell_escape)
1259 shell_escape_->show();
1261 shell_escape_->hide();
1263 if (buf.hasReadonlyFlag())
1268 if (buf.lyxvc().inUse()) {
1269 version_control_->show();
1270 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1272 version_control_->hide();
1276 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1278 if (d.current_work_area_)
1279 // disconnect the current work area from all slots
1280 QObject::disconnect(d.current_work_area_, 0, this, 0);
1282 disconnectBufferView();
1283 connectBufferView(wa->bufferView());
1284 connectBuffer(wa->bufferView().buffer());
1285 d.current_work_area_ = wa;
1286 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1287 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1288 QObject::connect(wa, SIGNAL(busy(bool)),
1289 this, SLOT(setBusy(bool)));
1290 // connection of a signal to a signal
1291 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1292 this, SIGNAL(bufferViewChanged()));
1293 Q_EMIT updateWindowTitle(wa);
1294 Q_EMIT bufferViewChanged();
1298 void GuiView::onBufferViewChanged()
1301 // Buffer-dependent dialogs must be updated. This is done here because
1302 // some dialogs require buffer()->text.
1307 void GuiView::on_lastWorkAreaRemoved()
1310 // We already are in a close event. Nothing more to do.
1313 if (d.splitter_->count() > 1)
1314 // We have a splitter so don't close anything.
1317 // Reset and updates the dialogs.
1318 Q_EMIT bufferViewChanged();
1323 if (lyxrc.open_buffers_in_tabs)
1324 // Nothing more to do, the window should stay open.
1327 if (guiApp->viewIds().size() > 1) {
1333 // On Mac we also close the last window because the application stay
1334 // resident in memory. On other platforms we don't close the last
1335 // window because this would quit the application.
1341 void GuiView::updateStatusBar()
1343 // let the user see the explicit message
1344 if (d.statusbar_timer_.isActive())
1351 void GuiView::showMessage()
1355 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1356 if (msg.isEmpty()) {
1357 BufferView const * bv = currentBufferView();
1359 msg = toqstr(bv->cursor().currentState(devel_mode_));
1361 msg = qt_("Welcome to LyX!");
1363 statusBar()->showMessage(msg);
1367 bool GuiView::event(QEvent * e)
1371 // Useful debug code:
1372 //case QEvent::ActivationChange:
1373 //case QEvent::WindowDeactivate:
1374 //case QEvent::Paint:
1375 //case QEvent::Enter:
1376 //case QEvent::Leave:
1377 //case QEvent::HoverEnter:
1378 //case QEvent::HoverLeave:
1379 //case QEvent::HoverMove:
1380 //case QEvent::StatusTip:
1381 //case QEvent::DragEnter:
1382 //case QEvent::DragLeave:
1383 //case QEvent::Drop:
1386 case QEvent::WindowActivate: {
1387 GuiView * old_view = guiApp->currentView();
1388 if (this == old_view) {
1390 return QMainWindow::event(e);
1392 if (old_view && old_view->currentBufferView()) {
1393 // save current selection to the selection buffer to allow
1394 // middle-button paste in this window.
1395 cap::saveSelection(old_view->currentBufferView()->cursor());
1397 guiApp->setCurrentView(this);
1398 if (d.current_work_area_)
1399 on_currentWorkAreaChanged(d.current_work_area_);
1403 return QMainWindow::event(e);
1406 case QEvent::ShortcutOverride: {
1408 if (isFullScreen() && menuBar()->isHidden()) {
1409 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1410 // FIXME: we should also try to detect special LyX shortcut such as
1411 // Alt-P and Alt-M. Right now there is a hack in
1412 // GuiWorkArea::processKeySym() that hides again the menubar for
1414 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1416 return QMainWindow::event(e);
1419 return QMainWindow::event(e);
1423 return QMainWindow::event(e);
1427 void GuiView::resetWindowTitle()
1429 setWindowTitle(qt_("LyX"));
1432 bool GuiView::focusNextPrevChild(bool /*next*/)
1439 bool GuiView::busy() const
1445 void GuiView::setBusy(bool busy)
1447 bool const busy_before = busy_ > 0;
1448 busy ? ++busy_ : --busy_;
1449 if ((busy_ > 0) == busy_before)
1450 // busy state didn't change
1454 QApplication::setOverrideCursor(Qt::WaitCursor);
1457 QApplication::restoreOverrideCursor();
1462 void GuiView::resetCommandExecute()
1464 command_execute_ = false;
1469 double GuiView::pixelRatio() const
1471 #if QT_VERSION >= 0x050000
1472 return qt_scale_factor * devicePixelRatio();
1479 GuiWorkArea * GuiView::workArea(int index)
1481 if (TabWorkArea * twa = d.currentTabWorkArea())
1482 if (index < twa->count())
1483 return twa->workArea(index);
1488 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1490 if (currentWorkArea()
1491 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1492 return currentWorkArea();
1493 if (TabWorkArea * twa = d.currentTabWorkArea())
1494 return twa->workArea(buffer);
1499 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1501 // Automatically create a TabWorkArea if there are none yet.
1502 TabWorkArea * tab_widget = d.splitter_->count()
1503 ? d.currentTabWorkArea() : addTabWorkArea();
1504 return tab_widget->addWorkArea(buffer, *this);
1508 TabWorkArea * GuiView::addTabWorkArea()
1510 TabWorkArea * twa = new TabWorkArea;
1511 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1512 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1513 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1514 this, SLOT(on_lastWorkAreaRemoved()));
1516 d.splitter_->addWidget(twa);
1517 d.stack_widget_->setCurrentWidget(d.splitter_);
1522 GuiWorkArea const * GuiView::currentWorkArea() const
1524 return d.current_work_area_;
1528 GuiWorkArea * GuiView::currentWorkArea()
1530 return d.current_work_area_;
1534 GuiWorkArea const * GuiView::currentMainWorkArea() const
1536 if (!d.currentTabWorkArea())
1538 return d.currentTabWorkArea()->currentWorkArea();
1542 GuiWorkArea * GuiView::currentMainWorkArea()
1544 if (!d.currentTabWorkArea())
1546 return d.currentTabWorkArea()->currentWorkArea();
1550 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1552 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1554 d.current_work_area_ = 0;
1556 Q_EMIT bufferViewChanged();
1560 // FIXME: I've no clue why this is here and why it accesses
1561 // theGuiApp()->currentView, which might be 0 (bug 6464).
1562 // See also 27525 (vfr).
1563 if (theGuiApp()->currentView() == this
1564 && theGuiApp()->currentView()->currentWorkArea() == wa)
1567 if (currentBufferView())
1568 cap::saveSelection(currentBufferView()->cursor());
1570 theGuiApp()->setCurrentView(this);
1571 d.current_work_area_ = wa;
1573 // We need to reset this now, because it will need to be
1574 // right if the tabWorkArea gets reset in the for loop. We
1575 // will change it back if we aren't in that case.
1576 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1577 d.current_main_work_area_ = wa;
1579 for (int i = 0; i != d.splitter_->count(); ++i) {
1580 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1581 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1582 << ", Current main wa: " << currentMainWorkArea());
1587 d.current_main_work_area_ = old_cmwa;
1589 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1590 on_currentWorkAreaChanged(wa);
1591 BufferView & bv = wa->bufferView();
1592 bv.cursor().fixIfBroken();
1594 wa->setUpdatesEnabled(true);
1595 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1599 void GuiView::removeWorkArea(GuiWorkArea * wa)
1601 LASSERT(wa, return);
1602 if (wa == d.current_work_area_) {
1604 disconnectBufferView();
1605 d.current_work_area_ = 0;
1606 d.current_main_work_area_ = 0;
1609 bool found_twa = false;
1610 for (int i = 0; i != d.splitter_->count(); ++i) {
1611 TabWorkArea * twa = d.tabWorkArea(i);
1612 if (twa->removeWorkArea(wa)) {
1613 // Found in this tab group, and deleted the GuiWorkArea.
1615 if (twa->count() != 0) {
1616 if (d.current_work_area_ == 0)
1617 // This means that we are closing the current GuiWorkArea, so
1618 // switch to the next GuiWorkArea in the found TabWorkArea.
1619 setCurrentWorkArea(twa->currentWorkArea());
1621 // No more WorkAreas in this tab group, so delete it.
1628 // It is not a tabbed work area (i.e., the search work area), so it
1629 // should be deleted by other means.
1630 LASSERT(found_twa, return);
1632 if (d.current_work_area_ == 0) {
1633 if (d.splitter_->count() != 0) {
1634 TabWorkArea * twa = d.currentTabWorkArea();
1635 setCurrentWorkArea(twa->currentWorkArea());
1637 // No more work areas, switch to the background widget.
1638 setCurrentWorkArea(0);
1644 LayoutBox * GuiView::getLayoutDialog() const
1650 void GuiView::updateLayoutList()
1653 d.layout_->updateContents(false);
1657 void GuiView::updateToolbars()
1659 ToolbarMap::iterator end = d.toolbars_.end();
1660 if (d.current_work_area_) {
1662 if (d.current_work_area_->bufferView().cursor().inMathed()
1663 && !d.current_work_area_->bufferView().cursor().inRegexped())
1664 context |= Toolbars::MATH;
1665 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1666 context |= Toolbars::TABLE;
1667 if (currentBufferView()->buffer().areChangesPresent()
1668 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1669 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1670 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1671 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1672 context |= Toolbars::REVIEW;
1673 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1674 context |= Toolbars::MATHMACROTEMPLATE;
1675 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1676 context |= Toolbars::IPA;
1677 if (command_execute_)
1678 context |= Toolbars::MINIBUFFER;
1679 if (minibuffer_focus_) {
1680 context |= Toolbars::MINIBUFFER_FOCUS;
1681 minibuffer_focus_ = false;
1684 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1685 it->second->update(context);
1687 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1688 it->second->update();
1692 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1694 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1695 LASSERT(newBuffer, return);
1697 GuiWorkArea * wa = workArea(*newBuffer);
1700 newBuffer->masterBuffer()->updateBuffer();
1702 wa = addWorkArea(*newBuffer);
1703 // scroll to the position when the BufferView was last closed
1704 if (lyxrc.use_lastfilepos) {
1705 LastFilePosSection::FilePos filepos =
1706 theSession().lastFilePos().load(newBuffer->fileName());
1707 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1710 //Disconnect the old buffer...there's no new one.
1713 connectBuffer(*newBuffer);
1714 connectBufferView(wa->bufferView());
1716 setCurrentWorkArea(wa);
1720 void GuiView::connectBuffer(Buffer & buf)
1722 buf.setGuiDelegate(this);
1726 void GuiView::disconnectBuffer()
1728 if (d.current_work_area_)
1729 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1733 void GuiView::connectBufferView(BufferView & bv)
1735 bv.setGuiDelegate(this);
1739 void GuiView::disconnectBufferView()
1741 if (d.current_work_area_)
1742 d.current_work_area_->bufferView().setGuiDelegate(0);
1746 void GuiView::errors(string const & error_type, bool from_master)
1748 BufferView const * const bv = currentBufferView();
1752 ErrorList const & el = from_master ?
1753 bv->buffer().masterBuffer()->errorList(error_type) :
1754 bv->buffer().errorList(error_type);
1759 string err = error_type;
1761 err = "from_master|" + error_type;
1762 showDialog("errorlist", err);
1766 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1768 d.toc_models_.updateItem(toqstr(type), dit);
1772 void GuiView::structureChanged()
1774 // This is called from the Buffer, which has no way to ensure that cursors
1775 // in BufferView remain valid.
1776 if (documentBufferView())
1777 documentBufferView()->cursor().sanitize();
1778 // FIXME: This is slightly expensive, though less than the tocBackend update
1779 // (#9880). This also resets the view in the Toc Widget (#6675).
1780 d.toc_models_.reset(documentBufferView());
1781 // Navigator needs more than a simple update in this case. It needs to be
1783 updateDialog("toc", "");
1787 void GuiView::updateDialog(string const & name, string const & sdata)
1789 if (!isDialogVisible(name))
1792 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1793 if (it == d.dialogs_.end())
1796 Dialog * const dialog = it->second.get();
1797 if (dialog->isVisibleView())
1798 dialog->initialiseParams(sdata);
1802 BufferView * GuiView::documentBufferView()
1804 return currentMainWorkArea()
1805 ? ¤tMainWorkArea()->bufferView()
1810 BufferView const * GuiView::documentBufferView() const
1812 return currentMainWorkArea()
1813 ? ¤tMainWorkArea()->bufferView()
1818 BufferView * GuiView::currentBufferView()
1820 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1824 BufferView const * GuiView::currentBufferView() const
1826 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1830 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1831 Buffer const * orig, Buffer * clone)
1833 bool const success = clone->autoSave();
1835 busyBuffers.remove(orig);
1837 ? _("Automatic save done.")
1838 : _("Automatic save failed!");
1842 void GuiView::autoSave()
1844 LYXERR(Debug::INFO, "Running autoSave()");
1846 Buffer * buffer = documentBufferView()
1847 ? &documentBufferView()->buffer() : 0;
1849 resetAutosaveTimers();
1853 GuiViewPrivate::busyBuffers.insert(buffer);
1854 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1855 buffer, buffer->cloneBufferOnly());
1856 d.autosave_watcher_.setFuture(f);
1857 resetAutosaveTimers();
1861 void GuiView::resetAutosaveTimers()
1864 d.autosave_timeout_.restart();
1868 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1871 Buffer * buf = currentBufferView()
1872 ? ¤tBufferView()->buffer() : 0;
1873 Buffer * doc_buffer = documentBufferView()
1874 ? &(documentBufferView()->buffer()) : 0;
1877 /* In LyX/Mac, when a dialog is open, the menus of the
1878 application can still be accessed without giving focus to
1879 the main window. In this case, we want to disable the menu
1880 entries that are buffer-related.
1881 This code must not be used on Linux and Windows, since it
1882 would disable buffer-related entries when hovering over the
1883 menu (see bug #9574).
1885 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1891 // Check whether we need a buffer
1892 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1893 // no, exit directly
1894 flag.message(from_utf8(N_("Command not allowed with"
1895 "out any document open")));
1896 flag.setEnabled(false);
1900 if (cmd.origin() == FuncRequest::TOC) {
1901 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1902 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1903 flag.setEnabled(false);
1907 switch(cmd.action()) {
1908 case LFUN_BUFFER_IMPORT:
1911 case LFUN_MASTER_BUFFER_EXPORT:
1913 && (doc_buffer->parent() != 0
1914 || doc_buffer->hasChildren())
1915 && !d.processing_thread_watcher_.isRunning()
1916 // this launches a dialog, which would be in the wrong Buffer
1917 && !(::lyx::operator==(cmd.argument(), "custom"));
1920 case LFUN_MASTER_BUFFER_UPDATE:
1921 case LFUN_MASTER_BUFFER_VIEW:
1923 && (doc_buffer->parent() != 0
1924 || doc_buffer->hasChildren())
1925 && !d.processing_thread_watcher_.isRunning();
1928 case LFUN_BUFFER_UPDATE:
1929 case LFUN_BUFFER_VIEW: {
1930 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1934 string format = to_utf8(cmd.argument());
1935 if (cmd.argument().empty())
1936 format = doc_buffer->params().getDefaultOutputFormat();
1937 enable = doc_buffer->params().isExportable(format, true);
1941 case LFUN_BUFFER_RELOAD:
1942 enable = doc_buffer && !doc_buffer->isUnnamed()
1943 && doc_buffer->fileName().exists()
1944 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1947 case LFUN_BUFFER_CHILD_OPEN:
1948 enable = doc_buffer != 0;
1951 case LFUN_BUFFER_WRITE:
1952 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1955 //FIXME: This LFUN should be moved to GuiApplication.
1956 case LFUN_BUFFER_WRITE_ALL: {
1957 // We enable the command only if there are some modified buffers
1958 Buffer * first = theBufferList().first();
1963 // We cannot use a for loop as the buffer list is a cycle.
1965 if (!b->isClean()) {
1969 b = theBufferList().next(b);
1970 } while (b != first);
1974 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1975 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1978 case LFUN_BUFFER_EXPORT: {
1979 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1983 return doc_buffer->getStatus(cmd, flag);
1987 case LFUN_BUFFER_EXPORT_AS:
1988 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1993 case LFUN_BUFFER_WRITE_AS:
1994 enable = doc_buffer != 0;
1997 case LFUN_EXPORT_CANCEL:
1998 enable = d.processing_thread_watcher_.isRunning();
2001 case LFUN_BUFFER_CLOSE:
2002 case LFUN_VIEW_CLOSE:
2003 enable = doc_buffer != 0;
2006 case LFUN_BUFFER_CLOSE_ALL:
2007 enable = theBufferList().last() != theBufferList().first();
2010 case LFUN_BUFFER_CHKTEX: {
2011 // hide if we have no checktex command
2012 if (lyxrc.chktex_command.empty()) {
2013 flag.setUnknown(true);
2017 if (!doc_buffer || !doc_buffer->params().isLatex()
2018 || d.processing_thread_watcher_.isRunning()) {
2019 // grey out, don't hide
2027 case LFUN_VIEW_SPLIT:
2028 if (cmd.getArg(0) == "vertical")
2029 enable = doc_buffer && (d.splitter_->count() == 1 ||
2030 d.splitter_->orientation() == Qt::Vertical);
2032 enable = doc_buffer && (d.splitter_->count() == 1 ||
2033 d.splitter_->orientation() == Qt::Horizontal);
2036 case LFUN_TAB_GROUP_CLOSE:
2037 enable = d.tabWorkAreaCount() > 1;
2040 case LFUN_DEVEL_MODE_TOGGLE:
2041 flag.setOnOff(devel_mode_);
2044 case LFUN_TOOLBAR_TOGGLE: {
2045 string const name = cmd.getArg(0);
2046 if (GuiToolbar * t = toolbar(name))
2047 flag.setOnOff(t->isVisible());
2050 docstring const msg =
2051 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2057 case LFUN_TOOLBAR_MOVABLE: {
2058 string const name = cmd.getArg(0);
2059 // use negation since locked == !movable
2061 // toolbar name * locks all toolbars
2062 flag.setOnOff(!toolbarsMovable_);
2063 else if (GuiToolbar * t = toolbar(name))
2064 flag.setOnOff(!(t->isMovable()));
2067 docstring const msg =
2068 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2074 case LFUN_ICON_SIZE:
2075 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2078 case LFUN_DROP_LAYOUTS_CHOICE:
2082 case LFUN_UI_TOGGLE:
2083 flag.setOnOff(isFullScreen());
2086 case LFUN_DIALOG_DISCONNECT_INSET:
2089 case LFUN_DIALOG_HIDE:
2090 // FIXME: should we check if the dialog is shown?
2093 case LFUN_DIALOG_TOGGLE:
2094 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2097 case LFUN_DIALOG_SHOW: {
2098 string const name = cmd.getArg(0);
2100 enable = name == "aboutlyx"
2101 || name == "file" //FIXME: should be removed.
2103 || name == "texinfo"
2104 || name == "progress"
2105 || name == "compare";
2106 else if (name == "character" || name == "symbols"
2107 || name == "mathdelimiter" || name == "mathmatrix") {
2108 if (!buf || buf->isReadonly())
2111 Cursor const & cur = currentBufferView()->cursor();
2112 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2115 else if (name == "latexlog")
2116 enable = FileName(doc_buffer->logName()).isReadableFile();
2117 else if (name == "spellchecker")
2118 enable = theSpellChecker()
2119 && !doc_buffer->isReadonly()
2120 && !doc_buffer->text().empty();
2121 else if (name == "vclog")
2122 enable = doc_buffer->lyxvc().inUse();
2126 case LFUN_DIALOG_UPDATE: {
2127 string const name = cmd.getArg(0);
2129 enable = name == "prefs";
2133 case LFUN_COMMAND_EXECUTE:
2135 case LFUN_MENU_OPEN:
2136 // Nothing to check.
2139 case LFUN_COMPLETION_INLINE:
2140 if (!d.current_work_area_
2141 || !d.current_work_area_->completer().inlinePossible(
2142 currentBufferView()->cursor()))
2146 case LFUN_COMPLETION_POPUP:
2147 if (!d.current_work_area_
2148 || !d.current_work_area_->completer().popupPossible(
2149 currentBufferView()->cursor()))
2154 if (!d.current_work_area_
2155 || !d.current_work_area_->completer().inlinePossible(
2156 currentBufferView()->cursor()))
2160 case LFUN_COMPLETION_ACCEPT:
2161 if (!d.current_work_area_
2162 || (!d.current_work_area_->completer().popupVisible()
2163 && !d.current_work_area_->completer().inlineVisible()
2164 && !d.current_work_area_->completer().completionAvailable()))
2168 case LFUN_COMPLETION_CANCEL:
2169 if (!d.current_work_area_
2170 || (!d.current_work_area_->completer().popupVisible()
2171 && !d.current_work_area_->completer().inlineVisible()))
2175 case LFUN_BUFFER_ZOOM_OUT:
2176 case LFUN_BUFFER_ZOOM_IN: {
2177 // only diff between these two is that the default for ZOOM_OUT
2179 bool const neg_zoom =
2180 convert<int>(cmd.argument()) < 0 ||
2181 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2182 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2183 docstring const msg =
2184 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2188 enable = doc_buffer;
2192 case LFUN_BUFFER_ZOOM: {
2193 bool const less_than_min_zoom =
2194 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2195 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2196 docstring const msg =
2197 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2202 enable = doc_buffer;
2206 case LFUN_BUFFER_MOVE_NEXT:
2207 case LFUN_BUFFER_MOVE_PREVIOUS:
2208 // we do not cycle when moving
2209 case LFUN_BUFFER_NEXT:
2210 case LFUN_BUFFER_PREVIOUS:
2211 // because we cycle, it doesn't matter whether on first or last
2212 enable = (d.currentTabWorkArea()->count() > 1);
2214 case LFUN_BUFFER_SWITCH:
2215 // toggle on the current buffer, but do not toggle off
2216 // the other ones (is that a good idea?)
2218 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2219 flag.setOnOff(true);
2222 case LFUN_VC_REGISTER:
2223 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2225 case LFUN_VC_RENAME:
2226 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2229 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2231 case LFUN_VC_CHECK_IN:
2232 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2234 case LFUN_VC_CHECK_OUT:
2235 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2237 case LFUN_VC_LOCKING_TOGGLE:
2238 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2239 && doc_buffer->lyxvc().lockingToggleEnabled();
2240 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2242 case LFUN_VC_REVERT:
2243 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2244 && !doc_buffer->hasReadonlyFlag();
2246 case LFUN_VC_UNDO_LAST:
2247 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2249 case LFUN_VC_REPO_UPDATE:
2250 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2252 case LFUN_VC_COMMAND: {
2253 if (cmd.argument().empty())
2255 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2259 case LFUN_VC_COMPARE:
2260 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2263 case LFUN_SERVER_GOTO_FILE_ROW:
2264 case LFUN_LYX_ACTIVATE:
2266 case LFUN_FORWARD_SEARCH:
2267 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2270 case LFUN_FILE_INSERT_PLAINTEXT:
2271 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2272 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2275 case LFUN_SPELLING_CONTINUOUSLY:
2276 flag.setOnOff(lyxrc.spellcheck_continuously);
2284 flag.setEnabled(false);
2290 static FileName selectTemplateFile()
2292 FileDialog dlg(qt_("Select template file"));
2293 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2294 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2296 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2297 QStringList(qt_("LyX Documents (*.lyx)")));
2299 if (result.first == FileDialog::Later)
2301 if (result.second.isEmpty())
2303 return FileName(fromqstr(result.second));
2307 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2311 Buffer * newBuffer = 0;
2313 newBuffer = checkAndLoadLyXFile(filename);
2314 } catch (ExceptionMessage const & e) {
2321 message(_("Document not loaded."));
2325 setBuffer(newBuffer);
2326 newBuffer->errors("Parse");
2329 theSession().lastFiles().add(filename);
2330 theSession().writeFile();
2337 void GuiView::openDocument(string const & fname)
2339 string initpath = lyxrc.document_path;
2341 if (documentBufferView()) {
2342 string const trypath = documentBufferView()->buffer().filePath();
2343 // If directory is writeable, use this as default.
2344 if (FileName(trypath).isDirWritable())
2350 if (fname.empty()) {
2351 FileDialog dlg(qt_("Select document to open"));
2352 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2353 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2355 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2356 FileDialog::Result result =
2357 dlg.open(toqstr(initpath), filter);
2359 if (result.first == FileDialog::Later)
2362 filename = fromqstr(result.second);
2364 // check selected filename
2365 if (filename.empty()) {
2366 message(_("Canceled."));
2372 // get absolute path of file and add ".lyx" to the filename if
2374 FileName const fullname =
2375 fileSearch(string(), filename, "lyx", support::may_not_exist);
2376 if (!fullname.empty())
2377 filename = fullname.absFileName();
2379 if (!fullname.onlyPath().isDirectory()) {
2380 Alert::warning(_("Invalid filename"),
2381 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2382 from_utf8(fullname.absFileName())));
2386 // if the file doesn't exist and isn't already open (bug 6645),
2387 // let the user create one
2388 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2389 !LyXVC::file_not_found_hook(fullname)) {
2390 // the user specifically chose this name. Believe him.
2391 Buffer * const b = newFile(filename, string(), true);
2397 docstring const disp_fn = makeDisplayPath(filename);
2398 message(bformat(_("Opening document %1$s..."), disp_fn));
2401 Buffer * buf = loadDocument(fullname);
2403 str2 = bformat(_("Document %1$s opened."), disp_fn);
2404 if (buf->lyxvc().inUse())
2405 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2406 " " + _("Version control detected.");
2408 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2413 // FIXME: clean that
2414 static bool import(GuiView * lv, FileName const & filename,
2415 string const & format, ErrorList & errorList)
2417 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2419 string loader_format;
2420 vector<string> loaders = theConverters().loaders();
2421 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2422 vector<string>::const_iterator it = loaders.begin();
2423 vector<string>::const_iterator en = loaders.end();
2424 for (; it != en; ++it) {
2425 if (!theConverters().isReachable(format, *it))
2428 string const tofile =
2429 support::changeExtension(filename.absFileName(),
2430 theFormats().extension(*it));
2431 if (theConverters().convert(0, filename, FileName(tofile),
2432 filename, format, *it, errorList) != Converters::SUCCESS)
2434 loader_format = *it;
2437 if (loader_format.empty()) {
2438 frontend::Alert::error(_("Couldn't import file"),
2439 bformat(_("No information for importing the format %1$s."),
2440 theFormats().prettyName(format)));
2444 loader_format = format;
2446 if (loader_format == "lyx") {
2447 Buffer * buf = lv->loadDocument(lyxfile);
2451 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2455 bool as_paragraphs = loader_format == "textparagraph";
2456 string filename2 = (loader_format == format) ? filename.absFileName()
2457 : support::changeExtension(filename.absFileName(),
2458 theFormats().extension(loader_format));
2459 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2461 guiApp->setCurrentView(lv);
2462 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2469 void GuiView::importDocument(string const & argument)
2472 string filename = split(argument, format, ' ');
2474 LYXERR(Debug::INFO, format << " file: " << filename);
2476 // need user interaction
2477 if (filename.empty()) {
2478 string initpath = lyxrc.document_path;
2479 if (documentBufferView()) {
2480 string const trypath = documentBufferView()->buffer().filePath();
2481 // If directory is writeable, use this as default.
2482 if (FileName(trypath).isDirWritable())
2486 docstring const text = bformat(_("Select %1$s file to import"),
2487 theFormats().prettyName(format));
2489 FileDialog dlg(toqstr(text));
2490 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2491 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2493 docstring filter = theFormats().prettyName(format);
2496 filter += from_utf8(theFormats().extensions(format));
2499 FileDialog::Result result =
2500 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2502 if (result.first == FileDialog::Later)
2505 filename = fromqstr(result.second);
2507 // check selected filename
2508 if (filename.empty())
2509 message(_("Canceled."));
2512 if (filename.empty())
2515 // get absolute path of file
2516 FileName const fullname(support::makeAbsPath(filename));
2518 // Can happen if the user entered a path into the dialog
2520 if (fullname.onlyFileName().empty()) {
2521 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2522 "Aborting import."),
2523 from_utf8(fullname.absFileName()));
2524 frontend::Alert::error(_("File name error"), msg);
2525 message(_("Canceled."));
2530 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2532 // Check if the document already is open
2533 Buffer * buf = theBufferList().getBuffer(lyxfile);
2536 if (!closeBuffer()) {
2537 message(_("Canceled."));
2542 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2544 // if the file exists already, and we didn't do
2545 // -i lyx thefile.lyx, warn
2546 if (lyxfile.exists() && fullname != lyxfile) {
2548 docstring text = bformat(_("The document %1$s already exists.\n\n"
2549 "Do you want to overwrite that document?"), displaypath);
2550 int const ret = Alert::prompt(_("Overwrite document?"),
2551 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2554 message(_("Canceled."));
2559 message(bformat(_("Importing %1$s..."), displaypath));
2560 ErrorList errorList;
2561 if (import(this, fullname, format, errorList))
2562 message(_("imported."));
2564 message(_("file not imported!"));
2566 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2570 void GuiView::newDocument(string const & filename, bool from_template)
2572 FileName initpath(lyxrc.document_path);
2573 if (documentBufferView()) {
2574 FileName const trypath(documentBufferView()->buffer().filePath());
2575 // If directory is writeable, use this as default.
2576 if (trypath.isDirWritable())
2580 string templatefile;
2581 if (from_template) {
2582 templatefile = selectTemplateFile().absFileName();
2583 if (templatefile.empty())
2588 if (filename.empty())
2589 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2591 b = newFile(filename, templatefile, true);
2596 // If no new document could be created, it is unsure
2597 // whether there is a valid BufferView.
2598 if (currentBufferView())
2599 // Ensure the cursor is correctly positioned on screen.
2600 currentBufferView()->showCursor();
2604 void GuiView::insertLyXFile(docstring const & fname)
2606 BufferView * bv = documentBufferView();
2611 FileName filename(to_utf8(fname));
2612 if (filename.empty()) {
2613 // Launch a file browser
2615 string initpath = lyxrc.document_path;
2616 string const trypath = bv->buffer().filePath();
2617 // If directory is writeable, use this as default.
2618 if (FileName(trypath).isDirWritable())
2622 FileDialog dlg(qt_("Select LyX document to insert"));
2623 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2624 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2626 FileDialog::Result result = dlg.open(toqstr(initpath),
2627 QStringList(qt_("LyX Documents (*.lyx)")));
2629 if (result.first == FileDialog::Later)
2633 filename.set(fromqstr(result.second));
2635 // check selected filename
2636 if (filename.empty()) {
2637 // emit message signal.
2638 message(_("Canceled."));
2643 bv->insertLyXFile(filename);
2644 bv->buffer().errors("Parse");
2648 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2650 FileName fname = b.fileName();
2651 FileName const oldname = fname;
2653 if (!newname.empty()) {
2655 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2657 // Switch to this Buffer.
2660 // No argument? Ask user through dialog.
2662 FileDialog dlg(qt_("Choose a filename to save document as"));
2663 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2664 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2666 if (!isLyXFileName(fname.absFileName()))
2667 fname.changeExtension(".lyx");
2669 FileDialog::Result result =
2670 dlg.save(toqstr(fname.onlyPath().absFileName()),
2671 QStringList(qt_("LyX Documents (*.lyx)")),
2672 toqstr(fname.onlyFileName()));
2674 if (result.first == FileDialog::Later)
2677 fname.set(fromqstr(result.second));
2682 if (!isLyXFileName(fname.absFileName()))
2683 fname.changeExtension(".lyx");
2686 // fname is now the new Buffer location.
2688 // if there is already a Buffer open with this name, we do not want
2689 // to have another one. (the second test makes sure we're not just
2690 // trying to overwrite ourselves, which is fine.)
2691 if (theBufferList().exists(fname) && fname != oldname
2692 && theBufferList().getBuffer(fname) != &b) {
2693 docstring const text =
2694 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2695 "Please close it before attempting to overwrite it.\n"
2696 "Do you want to choose a new filename?"),
2697 from_utf8(fname.absFileName()));
2698 int const ret = Alert::prompt(_("Chosen File Already Open"),
2699 text, 0, 1, _("&Rename"), _("&Cancel"));
2701 case 0: return renameBuffer(b, docstring(), kind);
2702 case 1: return false;
2707 bool const existsLocal = fname.exists();
2708 bool const existsInVC = LyXVC::fileInVC(fname);
2709 if (existsLocal || existsInVC) {
2710 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2711 if (kind != LV_WRITE_AS && existsInVC) {
2712 // renaming to a name that is already in VC
2714 docstring text = bformat(_("The document %1$s "
2715 "is already registered.\n\n"
2716 "Do you want to choose a new name?"),
2718 docstring const title = (kind == LV_VC_RENAME) ?
2719 _("Rename document?") : _("Copy document?");
2720 docstring const button = (kind == LV_VC_RENAME) ?
2721 _("&Rename") : _("&Copy");
2722 int const ret = Alert::prompt(title, text, 0, 1,
2723 button, _("&Cancel"));
2725 case 0: return renameBuffer(b, docstring(), kind);
2726 case 1: return false;
2731 docstring text = bformat(_("The document %1$s "
2732 "already exists.\n\n"
2733 "Do you want to overwrite that document?"),
2735 int const ret = Alert::prompt(_("Overwrite document?"),
2736 text, 0, 2, _("&Overwrite"),
2737 _("&Rename"), _("&Cancel"));
2740 case 1: return renameBuffer(b, docstring(), kind);
2741 case 2: return false;
2747 case LV_VC_RENAME: {
2748 string msg = b.lyxvc().rename(fname);
2751 message(from_utf8(msg));
2755 string msg = b.lyxvc().copy(fname);
2758 message(from_utf8(msg));
2764 // LyXVC created the file already in case of LV_VC_RENAME or
2765 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2766 // relative paths of included stuff right if we moved e.g. from
2767 // /a/b.lyx to /a/c/b.lyx.
2769 bool const saved = saveBuffer(b, fname);
2776 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2778 FileName fname = b.fileName();
2780 FileDialog dlg(qt_("Choose a filename to export the document as"));
2781 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2784 QString const anyformat = qt_("Guess from extension (*.*)");
2787 vector<Format const *> export_formats;
2788 for (Format const & f : theFormats())
2789 if (f.documentFormat())
2790 export_formats.push_back(&f);
2791 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2792 map<QString, string> fmap;
2795 for (Format const * f : export_formats) {
2796 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2797 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2799 from_ascii(f->extension())));
2800 types << loc_filter;
2801 fmap[loc_filter] = f->name();
2802 if (from_ascii(f->name()) == iformat) {
2803 filter = loc_filter;
2804 ext = f->extension();
2807 string ofname = fname.onlyFileName();
2809 ofname = support::changeExtension(ofname, ext);
2810 FileDialog::Result result =
2811 dlg.save(toqstr(fname.onlyPath().absFileName()),
2815 if (result.first != FileDialog::Chosen)
2819 fname.set(fromqstr(result.second));
2820 if (filter == anyformat)
2821 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2823 fmt_name = fmap[filter];
2824 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2825 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2827 if (fmt_name.empty() || fname.empty())
2830 // fname is now the new Buffer location.
2831 if (FileName(fname).exists()) {
2832 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2833 docstring text = bformat(_("The document %1$s already "
2834 "exists.\n\nDo you want to "
2835 "overwrite that document?"),
2837 int const ret = Alert::prompt(_("Overwrite document?"),
2838 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2841 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2842 case 2: return false;
2846 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2849 return dr.dispatched();
2853 bool GuiView::saveBuffer(Buffer & b)
2855 return saveBuffer(b, FileName());
2859 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2861 if (workArea(b) && workArea(b)->inDialogMode())
2864 if (fn.empty() && b.isUnnamed())
2865 return renameBuffer(b, docstring());
2867 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2869 theSession().lastFiles().add(b.fileName());
2870 theSession().writeFile();
2874 // Switch to this Buffer.
2877 // FIXME: we don't tell the user *WHY* the save failed !!
2878 docstring const file = makeDisplayPath(b.absFileName(), 30);
2879 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2880 "Do you want to rename the document and "
2881 "try again?"), file);
2882 int const ret = Alert::prompt(_("Rename and save?"),
2883 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2886 if (!renameBuffer(b, docstring()))
2895 return saveBuffer(b, fn);
2899 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2901 return closeWorkArea(wa, false);
2905 // We only want to close the buffer if it is not visible in other workareas
2906 // of the same view, nor in other views, and if this is not a child
2907 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2909 Buffer & buf = wa->bufferView().buffer();
2911 bool last_wa = d.countWorkAreasOf(buf) == 1
2912 && !inOtherView(buf) && !buf.parent();
2914 bool close_buffer = last_wa;
2917 if (lyxrc.close_buffer_with_last_view == "yes")
2919 else if (lyxrc.close_buffer_with_last_view == "no")
2920 close_buffer = false;
2923 if (buf.isUnnamed())
2924 file = from_utf8(buf.fileName().onlyFileName());
2926 file = buf.fileName().displayName(30);
2927 docstring const text = bformat(
2928 _("Last view on document %1$s is being closed.\n"
2929 "Would you like to close or hide the document?\n"
2931 "Hidden documents can be displayed back through\n"
2932 "the menu: View->Hidden->...\n"
2934 "To remove this question, set your preference in:\n"
2935 " Tools->Preferences->Look&Feel->UserInterface\n"
2937 int ret = Alert::prompt(_("Close or hide document?"),
2938 text, 0, 1, _("&Close"), _("&Hide"));
2939 close_buffer = (ret == 0);
2943 return closeWorkArea(wa, close_buffer);
2947 bool GuiView::closeBuffer()
2949 GuiWorkArea * wa = currentMainWorkArea();
2950 // coverity complained about this
2951 // it seems unnecessary, but perhaps is worth the check
2952 LASSERT(wa, return false);
2954 setCurrentWorkArea(wa);
2955 Buffer & buf = wa->bufferView().buffer();
2956 return closeWorkArea(wa, !buf.parent());
2960 void GuiView::writeSession() const {
2961 GuiWorkArea const * active_wa = currentMainWorkArea();
2962 for (int i = 0; i < d.splitter_->count(); ++i) {
2963 TabWorkArea * twa = d.tabWorkArea(i);
2964 for (int j = 0; j < twa->count(); ++j) {
2965 GuiWorkArea * wa = twa->workArea(j);
2966 Buffer & buf = wa->bufferView().buffer();
2967 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2973 bool GuiView::closeBufferAll()
2975 // Close the workareas in all other views
2976 QList<int> const ids = guiApp->viewIds();
2977 for (int i = 0; i != ids.size(); ++i) {
2978 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2982 // Close our own workareas
2983 if (!closeWorkAreaAll())
2986 // Now close the hidden buffers. We prevent hidden buffers from being
2987 // dirty, so we can just close them.
2988 theBufferList().closeAll();
2993 bool GuiView::closeWorkAreaAll()
2995 setCurrentWorkArea(currentMainWorkArea());
2997 // We might be in a situation that there is still a tabWorkArea, but
2998 // there are no tabs anymore. This can happen when we get here after a
2999 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3000 // many TabWorkArea's have no documents anymore.
3003 // We have to call count() each time, because it can happen that
3004 // more than one splitter will disappear in one iteration (bug 5998).
3005 while (d.splitter_->count() > empty_twa) {
3006 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3008 if (twa->count() == 0)
3011 setCurrentWorkArea(twa->currentWorkArea());
3012 if (!closeTabWorkArea(twa))
3020 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3025 Buffer & buf = wa->bufferView().buffer();
3027 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3028 Alert::warning(_("Close document"),
3029 _("Document could not be closed because it is being processed by LyX."));
3034 return closeBuffer(buf);
3036 if (!inMultiTabs(wa))
3037 if (!saveBufferIfNeeded(buf, true))
3045 bool GuiView::closeBuffer(Buffer & buf)
3047 // If we are in a close_event all children will be closed in some time,
3048 // so no need to do it here. This will ensure that the children end up
3049 // in the session file in the correct order. If we close the master
3050 // buffer, we can close or release the child buffers here too.
3051 bool success = true;
3053 ListOfBuffers clist = buf.getChildren();
3054 ListOfBuffers::const_iterator it = clist.begin();
3055 ListOfBuffers::const_iterator const bend = clist.end();
3056 for (; it != bend; ++it) {
3057 Buffer * child_buf = *it;
3058 if (theBufferList().isOthersChild(&buf, child_buf)) {
3059 child_buf->setParent(0);
3063 // FIXME: should we look in other tabworkareas?
3064 // ANSWER: I don't think so. I've tested, and if the child is
3065 // open in some other window, it closes without a problem.
3066 GuiWorkArea * child_wa = workArea(*child_buf);
3068 success = closeWorkArea(child_wa, true);
3072 // In this case the child buffer is open but hidden.
3073 // It therefore should not (MUST NOT) be dirty!
3074 LATTEST(child_buf->isClean());
3075 theBufferList().release(child_buf);
3080 // goto bookmark to update bookmark pit.
3081 // FIXME: we should update only the bookmarks related to this buffer!
3082 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3083 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3084 guiApp->gotoBookmark(i+1, false, false);
3086 if (saveBufferIfNeeded(buf, false)) {
3087 buf.removeAutosaveFile();
3088 theBufferList().release(&buf);
3092 // open all children again to avoid a crash because of dangling
3093 // pointers (bug 6603)
3099 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3101 while (twa == d.currentTabWorkArea()) {
3102 twa->setCurrentIndex(twa->count() - 1);
3104 GuiWorkArea * wa = twa->currentWorkArea();
3105 Buffer & b = wa->bufferView().buffer();
3107 // We only want to close the buffer if the same buffer is not visible
3108 // in another view, and if this is not a child and if we are closing
3109 // a view (not a tabgroup).
3110 bool const close_buffer =
3111 !inOtherView(b) && !b.parent() && closing_;
3113 if (!closeWorkArea(wa, close_buffer))
3120 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3122 if (buf.isClean() || buf.paragraphs().empty())
3125 // Switch to this Buffer.
3131 if (buf.isUnnamed()) {
3132 file = from_utf8(buf.fileName().onlyFileName());
3135 FileName filename = buf.fileName();
3137 file = filename.displayName(30);
3138 exists = filename.exists();
3141 // Bring this window to top before asking questions.
3146 if (hiding && buf.isUnnamed()) {
3147 docstring const text = bformat(_("The document %1$s has not been "
3148 "saved yet.\n\nDo you want to save "
3149 "the document?"), file);
3150 ret = Alert::prompt(_("Save new document?"),
3151 text, 0, 1, _("&Save"), _("&Cancel"));
3155 docstring const text = exists ?
3156 bformat(_("The document %1$s has unsaved changes."
3157 "\n\nDo you want to save the document or "
3158 "discard the changes?"), file) :
3159 bformat(_("The document %1$s has not been saved yet."
3160 "\n\nDo you want to save the document or "
3161 "discard it entirely?"), file);
3162 docstring const title = exists ?
3163 _("Save changed document?") : _("Save document?");
3164 ret = Alert::prompt(title, text, 0, 2,
3165 _("&Save"), _("&Discard"), _("&Cancel"));
3170 if (!saveBuffer(buf))
3174 // If we crash after this we could have no autosave file
3175 // but I guess this is really improbable (Jug).
3176 // Sometimes improbable things happen:
3177 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3178 // buf.removeAutosaveFile();
3180 // revert all changes
3191 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3193 Buffer & buf = wa->bufferView().buffer();
3195 for (int i = 0; i != d.splitter_->count(); ++i) {
3196 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3197 if (wa_ && wa_ != wa)
3200 return inOtherView(buf);
3204 bool GuiView::inOtherView(Buffer & buf)
3206 QList<int> const ids = guiApp->viewIds();
3208 for (int i = 0; i != ids.size(); ++i) {
3212 if (guiApp->view(ids[i]).workArea(buf))
3219 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3221 if (!documentBufferView())
3224 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3225 Buffer * const curbuf = &documentBufferView()->buffer();
3226 int nwa = twa->count();
3227 for (int i = 0; i < nwa; ++i) {
3228 if (&workArea(i)->bufferView().buffer() == curbuf) {
3230 if (np == NEXTBUFFER)
3231 next_index = (i == nwa - 1 ? 0 : i + 1);
3233 next_index = (i == 0 ? nwa - 1 : i - 1);
3235 twa->moveTab(i, next_index);
3237 setBuffer(&workArea(next_index)->bufferView().buffer());
3245 /// make sure the document is saved
3246 static bool ensureBufferClean(Buffer * buffer)
3248 LASSERT(buffer, return false);
3249 if (buffer->isClean() && !buffer->isUnnamed())
3252 docstring const file = buffer->fileName().displayName(30);
3255 if (!buffer->isUnnamed()) {
3256 text = bformat(_("The document %1$s has unsaved "
3257 "changes.\n\nDo you want to save "
3258 "the document?"), file);
3259 title = _("Save changed document?");
3262 text = bformat(_("The document %1$s has not been "
3263 "saved yet.\n\nDo you want to save "
3264 "the document?"), file);
3265 title = _("Save new document?");
3267 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3270 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3272 return buffer->isClean() && !buffer->isUnnamed();
3276 bool GuiView::reloadBuffer(Buffer & buf)
3278 currentBufferView()->cursor().reset();
3279 Buffer::ReadStatus status = buf.reload();
3280 return status == Buffer::ReadSuccess;
3284 void GuiView::checkExternallyModifiedBuffers()
3286 BufferList::iterator bit = theBufferList().begin();
3287 BufferList::iterator const bend = theBufferList().end();
3288 for (; bit != bend; ++bit) {
3289 Buffer * buf = *bit;
3290 if (buf->fileName().exists() && buf->isChecksumModified()) {
3291 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3292 " Reload now? Any local changes will be lost."),
3293 from_utf8(buf->absFileName()));
3294 int const ret = Alert::prompt(_("Reload externally changed document?"),
3295 text, 0, 1, _("&Reload"), _("&Cancel"));
3303 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3305 Buffer * buffer = documentBufferView()
3306 ? &(documentBufferView()->buffer()) : 0;
3308 switch (cmd.action()) {
3309 case LFUN_VC_REGISTER:
3310 if (!buffer || !ensureBufferClean(buffer))
3312 if (!buffer->lyxvc().inUse()) {
3313 if (buffer->lyxvc().registrer()) {
3314 reloadBuffer(*buffer);
3315 dr.clearMessageUpdate();
3320 case LFUN_VC_RENAME:
3321 case LFUN_VC_COPY: {
3322 if (!buffer || !ensureBufferClean(buffer))
3324 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3325 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3326 // Some changes are not yet committed.
3327 // We test here and not in getStatus(), since
3328 // this test is expensive.
3330 LyXVC::CommandResult ret =
3331 buffer->lyxvc().checkIn(log);
3333 if (ret == LyXVC::ErrorCommand ||
3334 ret == LyXVC::VCSuccess)
3335 reloadBuffer(*buffer);
3336 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3337 frontend::Alert::error(
3338 _("Revision control error."),
3339 _("Document could not be checked in."));
3343 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3344 LV_VC_RENAME : LV_VC_COPY;
3345 renameBuffer(*buffer, cmd.argument(), kind);
3350 case LFUN_VC_CHECK_IN:
3351 if (!buffer || !ensureBufferClean(buffer))
3353 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3355 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3357 // Only skip reloading if the checkin was cancelled or
3358 // an error occurred before the real checkin VCS command
3359 // was executed, since the VCS might have changed the
3360 // file even if it could not checkin successfully.
3361 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3362 reloadBuffer(*buffer);
3366 case LFUN_VC_CHECK_OUT:
3367 if (!buffer || !ensureBufferClean(buffer))
3369 if (buffer->lyxvc().inUse()) {
3370 dr.setMessage(buffer->lyxvc().checkOut());
3371 reloadBuffer(*buffer);
3375 case LFUN_VC_LOCKING_TOGGLE:
3376 LASSERT(buffer, return);
3377 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3379 if (buffer->lyxvc().inUse()) {
3380 string res = buffer->lyxvc().lockingToggle();
3382 frontend::Alert::error(_("Revision control error."),
3383 _("Error when setting the locking property."));
3386 reloadBuffer(*buffer);
3391 case LFUN_VC_REVERT:
3392 LASSERT(buffer, return);
3393 if (buffer->lyxvc().revert()) {
3394 reloadBuffer(*buffer);
3395 dr.clearMessageUpdate();
3399 case LFUN_VC_UNDO_LAST:
3400 LASSERT(buffer, return);
3401 buffer->lyxvc().undoLast();
3402 reloadBuffer(*buffer);
3403 dr.clearMessageUpdate();
3406 case LFUN_VC_REPO_UPDATE:
3407 LASSERT(buffer, return);
3408 if (ensureBufferClean(buffer)) {
3409 dr.setMessage(buffer->lyxvc().repoUpdate());
3410 checkExternallyModifiedBuffers();
3414 case LFUN_VC_COMMAND: {
3415 string flag = cmd.getArg(0);
3416 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3419 if (contains(flag, 'M')) {
3420 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3423 string path = cmd.getArg(1);
3424 if (contains(path, "$$p") && buffer)
3425 path = subst(path, "$$p", buffer->filePath());
3426 LYXERR(Debug::LYXVC, "Directory: " << path);
3428 if (!pp.isReadableDirectory()) {
3429 lyxerr << _("Directory is not accessible.") << endl;
3432 support::PathChanger p(pp);
3434 string command = cmd.getArg(2);
3435 if (command.empty())
3438 command = subst(command, "$$i", buffer->absFileName());
3439 command = subst(command, "$$p", buffer->filePath());
3441 command = subst(command, "$$m", to_utf8(message));
3442 LYXERR(Debug::LYXVC, "Command: " << command);
3444 one.startscript(Systemcall::Wait, command);
3448 if (contains(flag, 'I'))
3449 buffer->markDirty();
3450 if (contains(flag, 'R'))
3451 reloadBuffer(*buffer);
3456 case LFUN_VC_COMPARE: {
3457 if (cmd.argument().empty()) {
3458 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3462 string rev1 = cmd.getArg(0);
3467 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3470 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3471 f2 = buffer->absFileName();
3473 string rev2 = cmd.getArg(1);
3477 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3481 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3482 f1 << "\n" << f2 << "\n" );
3483 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3484 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3494 void GuiView::openChildDocument(string const & fname)
3496 LASSERT(documentBufferView(), return);
3497 Buffer & buffer = documentBufferView()->buffer();
3498 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3499 documentBufferView()->saveBookmark(false);
3501 if (theBufferList().exists(filename)) {
3502 child = theBufferList().getBuffer(filename);
3505 message(bformat(_("Opening child document %1$s..."),
3506 makeDisplayPath(filename.absFileName())));
3507 child = loadDocument(filename, false);
3509 // Set the parent name of the child document.
3510 // This makes insertion of citations and references in the child work,
3511 // when the target is in the parent or another child document.
3513 child->setParent(&buffer);
3517 bool GuiView::goToFileRow(string const & argument)
3521 size_t i = argument.find_last_of(' ');
3522 if (i != string::npos) {
3523 file_name = os::internal_path(trim(argument.substr(0, i)));
3524 istringstream is(argument.substr(i + 1));
3529 if (i == string::npos) {
3530 LYXERR0("Wrong argument: " << argument);
3534 string const abstmp = package().temp_dir().absFileName();
3535 string const realtmp = package().temp_dir().realPath();
3536 // We have to use os::path_prefix_is() here, instead of
3537 // simply prefixIs(), because the file name comes from
3538 // an external application and may need case adjustment.
3539 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3540 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3541 // Needed by inverse dvi search. If it is a file
3542 // in tmpdir, call the apropriated function.
3543 // If tmpdir is a symlink, we may have the real
3544 // path passed back, so we correct for that.
3545 if (!prefixIs(file_name, abstmp))
3546 file_name = subst(file_name, realtmp, abstmp);
3547 buf = theBufferList().getBufferFromTmp(file_name);
3549 // Must replace extension of the file to be .lyx
3550 // and get full path
3551 FileName const s = fileSearch(string(),
3552 support::changeExtension(file_name, ".lyx"), "lyx");
3553 // Either change buffer or load the file
3554 if (theBufferList().exists(s))
3555 buf = theBufferList().getBuffer(s);
3556 else if (s.exists()) {
3557 buf = loadDocument(s);
3562 _("File does not exist: %1$s"),
3563 makeDisplayPath(file_name)));
3569 _("No buffer for file: %1$s."),
3570 makeDisplayPath(file_name))
3575 bool success = documentBufferView()->setCursorFromRow(row);
3577 LYXERR(Debug::LATEX,
3578 "setCursorFromRow: invalid position for row " << row);
3579 frontend::Alert::error(_("Inverse Search Failed"),
3580 _("Invalid position requested by inverse search.\n"
3581 "You may need to update the viewed document."));
3587 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3589 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3590 menu->exec(QCursor::pos());
3595 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3596 Buffer const * orig, Buffer * clone, string const & format)
3598 Buffer::ExportStatus const status = func(format);
3600 // the cloning operation will have produced a clone of the entire set of
3601 // documents, starting from the master. so we must delete those.
3602 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3604 busyBuffers.remove(orig);
3609 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3610 Buffer const * orig, Buffer * clone, string const & format)
3612 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3614 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3618 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3619 Buffer const * orig, Buffer * clone, string const & format)
3621 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3623 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3627 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3628 Buffer const * orig, Buffer * clone, string const & format)
3630 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3632 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3636 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3637 string const & argument,
3638 Buffer const * used_buffer,
3639 docstring const & msg,
3640 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3641 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3642 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3648 string format = argument;
3650 format = used_buffer->params().getDefaultOutputFormat();
3651 processing_format = format;
3653 progress_->clearMessages();
3656 #if EXPORT_in_THREAD
3658 GuiViewPrivate::busyBuffers.insert(used_buffer);
3659 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3660 if (!cloned_buffer) {
3661 Alert::error(_("Export Error"),
3662 _("Error cloning the Buffer."));
3665 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3670 setPreviewFuture(f);
3671 last_export_format = used_buffer->params().bufferFormat();
3674 // We are asynchronous, so we don't know here anything about the success
3677 Buffer::ExportStatus status;
3679 status = (used_buffer->*syncFunc)(format, false);
3680 } else if (previewFunc) {
3681 status = (used_buffer->*previewFunc)(format);
3684 handleExportStatus(gv_, status, format);
3686 return (status == Buffer::ExportSuccess
3687 || status == Buffer::PreviewSuccess);
3691 Buffer::ExportStatus status;
3693 status = (used_buffer->*syncFunc)(format, true);
3694 } else if (previewFunc) {
3695 status = (used_buffer->*previewFunc)(format);
3698 handleExportStatus(gv_, status, format);
3700 return (status == Buffer::ExportSuccess
3701 || status == Buffer::PreviewSuccess);
3705 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3707 BufferView * bv = currentBufferView();
3708 LASSERT(bv, return);
3710 // Let the current BufferView dispatch its own actions.
3711 bv->dispatch(cmd, dr);
3712 if (dr.dispatched()) {
3713 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3714 updateDialog("document", "");
3718 // Try with the document BufferView dispatch if any.
3719 BufferView * doc_bv = documentBufferView();
3720 if (doc_bv && doc_bv != bv) {
3721 doc_bv->dispatch(cmd, dr);
3722 if (dr.dispatched()) {
3723 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3724 updateDialog("document", "");
3729 // Then let the current Cursor dispatch its own actions.
3730 bv->cursor().dispatch(cmd);
3732 // update completion. We do it here and not in
3733 // processKeySym to avoid another redraw just for a
3734 // changed inline completion
3735 if (cmd.origin() == FuncRequest::KEYBOARD) {
3736 if (cmd.action() == LFUN_SELF_INSERT
3737 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3738 updateCompletion(bv->cursor(), true, true);
3739 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3740 updateCompletion(bv->cursor(), false, true);
3742 updateCompletion(bv->cursor(), false, false);
3745 dr = bv->cursor().result();
3749 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3751 BufferView * bv = currentBufferView();
3752 // By default we won't need any update.
3753 dr.screenUpdate(Update::None);
3754 // assume cmd will be dispatched
3755 dr.dispatched(true);
3757 Buffer * doc_buffer = documentBufferView()
3758 ? &(documentBufferView()->buffer()) : 0;
3760 if (cmd.origin() == FuncRequest::TOC) {
3761 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3762 // FIXME: do we need to pass a DispatchResult object here?
3763 toc->doDispatch(bv->cursor(), cmd);
3767 string const argument = to_utf8(cmd.argument());
3769 switch(cmd.action()) {
3770 case LFUN_BUFFER_CHILD_OPEN:
3771 openChildDocument(to_utf8(cmd.argument()));
3774 case LFUN_BUFFER_IMPORT:
3775 importDocument(to_utf8(cmd.argument()));
3778 case LFUN_MASTER_BUFFER_EXPORT:
3780 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3782 case LFUN_BUFFER_EXPORT: {
3785 // GCC only sees strfwd.h when building merged
3786 if (::lyx::operator==(cmd.argument(), "custom")) {
3787 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3788 // so the following test should not be needed.
3789 // In principle, we could try to switch to such a view...
3790 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3791 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3795 string const dest = cmd.getArg(1);
3796 FileName target_dir;
3797 if (!dest.empty() && FileName::isAbsolute(dest))
3798 target_dir = FileName(support::onlyPath(dest));
3800 target_dir = doc_buffer->fileName().onlyPath();
3802 string const format = (argument.empty() || argument == "default") ?
3803 doc_buffer->params().getDefaultOutputFormat() : argument;
3805 if ((dest.empty() && doc_buffer->isUnnamed())
3806 || !target_dir.isDirWritable()) {
3807 exportBufferAs(*doc_buffer, from_utf8(format));
3810 /* TODO/Review: Is it a problem to also export the children?
3811 See the update_unincluded flag */
3812 d.asyncBufferProcessing(format,
3815 &GuiViewPrivate::exportAndDestroy,
3817 0, cmd.allowAsync());
3818 // TODO Inform user about success
3822 case LFUN_BUFFER_EXPORT_AS: {
3823 LASSERT(doc_buffer, break);
3824 docstring f = cmd.argument();
3826 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3827 exportBufferAs(*doc_buffer, f);
3831 case LFUN_BUFFER_UPDATE: {
3832 d.asyncBufferProcessing(argument,
3835 &GuiViewPrivate::compileAndDestroy,
3837 0, cmd.allowAsync());
3840 case LFUN_BUFFER_VIEW: {
3841 d.asyncBufferProcessing(argument,
3843 _("Previewing ..."),
3844 &GuiViewPrivate::previewAndDestroy,
3846 &Buffer::preview, cmd.allowAsync());
3849 case LFUN_MASTER_BUFFER_UPDATE: {
3850 d.asyncBufferProcessing(argument,
3851 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3853 &GuiViewPrivate::compileAndDestroy,
3855 0, cmd.allowAsync());
3858 case LFUN_MASTER_BUFFER_VIEW: {
3859 d.asyncBufferProcessing(argument,
3860 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3862 &GuiViewPrivate::previewAndDestroy,
3863 0, &Buffer::preview, cmd.allowAsync());
3866 case LFUN_EXPORT_CANCEL: {
3867 Systemcall::killscript();
3870 case LFUN_BUFFER_SWITCH: {
3871 string const file_name = to_utf8(cmd.argument());
3872 if (!FileName::isAbsolute(file_name)) {
3874 dr.setMessage(_("Absolute filename expected."));
3878 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3881 dr.setMessage(_("Document not loaded"));
3885 // Do we open or switch to the buffer in this view ?
3886 if (workArea(*buffer)
3887 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3892 // Look for the buffer in other views
3893 QList<int> const ids = guiApp->viewIds();
3895 for (; i != ids.size(); ++i) {
3896 GuiView & gv = guiApp->view(ids[i]);
3897 if (gv.workArea(*buffer)) {
3899 gv.activateWindow();
3901 gv.setBuffer(buffer);
3906 // If necessary, open a new window as a last resort
3907 if (i == ids.size()) {
3908 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3914 case LFUN_BUFFER_NEXT:
3915 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3918 case LFUN_BUFFER_MOVE_NEXT:
3919 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3922 case LFUN_BUFFER_PREVIOUS:
3923 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3926 case LFUN_BUFFER_MOVE_PREVIOUS:
3927 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3930 case LFUN_BUFFER_CHKTEX:
3931 LASSERT(doc_buffer, break);
3932 doc_buffer->runChktex();
3935 case LFUN_COMMAND_EXECUTE: {
3936 command_execute_ = true;
3937 minibuffer_focus_ = true;
3940 case LFUN_DROP_LAYOUTS_CHOICE:
3941 d.layout_->showPopup();
3944 case LFUN_MENU_OPEN:
3945 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3946 menu->exec(QCursor::pos());
3949 case LFUN_FILE_INSERT:
3950 insertLyXFile(cmd.argument());
3953 case LFUN_FILE_INSERT_PLAINTEXT:
3954 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3955 string const fname = to_utf8(cmd.argument());
3956 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3957 dr.setMessage(_("Absolute filename expected."));
3961 FileName filename(fname);
3962 if (fname.empty()) {
3963 FileDialog dlg(qt_("Select file to insert"));
3965 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3966 QStringList(qt_("All Files (*)")));
3968 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3969 dr.setMessage(_("Canceled."));
3973 filename.set(fromqstr(result.second));
3977 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3978 bv->dispatch(new_cmd, dr);
3983 case LFUN_BUFFER_RELOAD: {
3984 LASSERT(doc_buffer, break);
3987 bool drop = (cmd.argument() == "dump");
3990 if (!drop && !doc_buffer->isClean()) {
3991 docstring const file =
3992 makeDisplayPath(doc_buffer->absFileName(), 20);
3993 if (doc_buffer->notifiesExternalModification()) {
3994 docstring text = _("The current version will be lost. "
3995 "Are you sure you want to load the version on disk "
3996 "of the document %1$s?");
3997 ret = Alert::prompt(_("Reload saved document?"),
3998 bformat(text, file), 1, 1,
3999 _("&Reload"), _("&Cancel"));
4001 docstring text = _("Any changes will be lost. "
4002 "Are you sure you want to revert to the saved version "
4003 "of the document %1$s?");
4004 ret = Alert::prompt(_("Revert to saved document?"),
4005 bformat(text, file), 1, 1,
4006 _("&Revert"), _("&Cancel"));
4011 doc_buffer->markClean();
4012 reloadBuffer(*doc_buffer);
4013 dr.forceBufferUpdate();
4018 case LFUN_BUFFER_WRITE:
4019 LASSERT(doc_buffer, break);
4020 saveBuffer(*doc_buffer);
4023 case LFUN_BUFFER_WRITE_AS:
4024 LASSERT(doc_buffer, break);
4025 renameBuffer(*doc_buffer, cmd.argument());
4028 case LFUN_BUFFER_WRITE_ALL: {
4029 Buffer * first = theBufferList().first();
4032 message(_("Saving all documents..."));
4033 // We cannot use a for loop as the buffer list cycles.
4036 if (!b->isClean()) {
4038 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4040 b = theBufferList().next(b);
4041 } while (b != first);
4042 dr.setMessage(_("All documents saved."));
4046 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4047 LASSERT(doc_buffer, break);
4048 doc_buffer->clearExternalModification();
4051 case LFUN_BUFFER_CLOSE:
4055 case LFUN_BUFFER_CLOSE_ALL:
4059 case LFUN_DEVEL_MODE_TOGGLE:
4060 devel_mode_ = !devel_mode_;
4062 dr.setMessage(_("Developer mode is now enabled."));
4064 dr.setMessage(_("Developer mode is now disabled."));
4067 case LFUN_TOOLBAR_TOGGLE: {
4068 string const name = cmd.getArg(0);
4069 if (GuiToolbar * t = toolbar(name))
4074 case LFUN_TOOLBAR_MOVABLE: {
4075 string const name = cmd.getArg(0);
4077 // toggle (all) toolbars movablility
4078 toolbarsMovable_ = !toolbarsMovable_;
4079 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4080 GuiToolbar * tb = toolbar(ti.name);
4081 if (tb && tb->isMovable() != toolbarsMovable_)
4082 // toggle toolbar movablity if it does not fit lock
4083 // (all) toolbars positions state silent = true, since
4084 // status bar notifications are slow
4087 if (toolbarsMovable_)
4088 dr.setMessage(_("Toolbars unlocked."));
4090 dr.setMessage(_("Toolbars locked."));
4091 } else if (GuiToolbar * t = toolbar(name)) {
4092 // toggle current toolbar movablity
4094 // update lock (all) toolbars positions
4095 updateLockToolbars();
4100 case LFUN_ICON_SIZE: {
4101 QSize size = d.iconSize(cmd.argument());
4103 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4104 size.width(), size.height()));
4108 case LFUN_DIALOG_UPDATE: {
4109 string const name = to_utf8(cmd.argument());
4110 if (name == "prefs" || name == "document")
4111 updateDialog(name, string());
4112 else if (name == "paragraph")
4113 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4114 else if (currentBufferView()) {
4115 Inset * inset = currentBufferView()->editedInset(name);
4116 // Can only update a dialog connected to an existing inset
4118 // FIXME: get rid of this indirection; GuiView ask the inset
4119 // if he is kind enough to update itself...
4120 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4121 //FIXME: pass DispatchResult here?
4122 inset->dispatch(currentBufferView()->cursor(), fr);
4128 case LFUN_DIALOG_TOGGLE: {
4129 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4130 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4131 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4135 case LFUN_DIALOG_DISCONNECT_INSET:
4136 disconnectDialog(to_utf8(cmd.argument()));
4139 case LFUN_DIALOG_HIDE: {
4140 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4144 case LFUN_DIALOG_SHOW: {
4145 string const name = cmd.getArg(0);
4146 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4148 if (name == "latexlog") {
4149 // gettatus checks that
4150 LATTEST(doc_buffer);
4151 Buffer::LogType type;
4152 string const logfile = doc_buffer->logName(&type);
4154 case Buffer::latexlog:
4157 case Buffer::buildlog:
4158 sdata = "literate ";
4161 sdata += Lexer::quoteString(logfile);
4162 showDialog("log", sdata);
4163 } else if (name == "vclog") {
4164 // getStatus checks that
4165 LATTEST(doc_buffer);
4166 string const sdata2 = "vc " +
4167 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4168 showDialog("log", sdata2);
4169 } else if (name == "symbols") {
4170 sdata = bv->cursor().getEncoding()->name();
4172 showDialog("symbols", sdata);
4174 } else if (name == "prefs" && isFullScreen()) {
4175 lfunUiToggle("fullscreen");
4176 showDialog("prefs", sdata);
4178 showDialog(name, sdata);
4183 dr.setMessage(cmd.argument());
4186 case LFUN_UI_TOGGLE: {
4187 string arg = cmd.getArg(0);
4188 if (!lfunUiToggle(arg)) {
4189 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4190 dr.setMessage(bformat(msg, from_utf8(arg)));
4192 // Make sure the keyboard focus stays in the work area.
4197 case LFUN_VIEW_SPLIT: {
4198 LASSERT(doc_buffer, break);
4199 string const orientation = cmd.getArg(0);
4200 d.splitter_->setOrientation(orientation == "vertical"
4201 ? Qt::Vertical : Qt::Horizontal);
4202 TabWorkArea * twa = addTabWorkArea();
4203 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4204 setCurrentWorkArea(wa);
4207 case LFUN_TAB_GROUP_CLOSE:
4208 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4209 closeTabWorkArea(twa);
4210 d.current_work_area_ = 0;
4211 twa = d.currentTabWorkArea();
4212 // Switch to the next GuiWorkArea in the found TabWorkArea.
4214 // Make sure the work area is up to date.
4215 setCurrentWorkArea(twa->currentWorkArea());
4217 setCurrentWorkArea(0);
4222 case LFUN_VIEW_CLOSE:
4223 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4224 closeWorkArea(twa->currentWorkArea());
4225 d.current_work_area_ = 0;
4226 twa = d.currentTabWorkArea();
4227 // Switch to the next GuiWorkArea in the found TabWorkArea.
4229 // Make sure the work area is up to date.
4230 setCurrentWorkArea(twa->currentWorkArea());
4232 setCurrentWorkArea(0);
4237 case LFUN_COMPLETION_INLINE:
4238 if (d.current_work_area_)
4239 d.current_work_area_->completer().showInline();
4242 case LFUN_COMPLETION_POPUP:
4243 if (d.current_work_area_)
4244 d.current_work_area_->completer().showPopup();
4249 if (d.current_work_area_)
4250 d.current_work_area_->completer().tab();
4253 case LFUN_COMPLETION_CANCEL:
4254 if (d.current_work_area_) {
4255 if (d.current_work_area_->completer().popupVisible())
4256 d.current_work_area_->completer().hidePopup();
4258 d.current_work_area_->completer().hideInline();
4262 case LFUN_COMPLETION_ACCEPT:
4263 if (d.current_work_area_)
4264 d.current_work_area_->completer().activate();
4267 case LFUN_BUFFER_ZOOM_IN:
4268 case LFUN_BUFFER_ZOOM_OUT:
4269 case LFUN_BUFFER_ZOOM: {
4270 if (cmd.argument().empty()) {
4271 if (cmd.action() == LFUN_BUFFER_ZOOM)
4273 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4278 if (cmd.action() == LFUN_BUFFER_ZOOM)
4279 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4280 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4281 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4283 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4286 // Actual zoom value: default zoom + fractional extra value
4287 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4288 if (zoom < static_cast<int>(zoom_min_))
4291 lyxrc.currentZoom = zoom;
4293 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4294 lyxrc.currentZoom, lyxrc.defaultZoom));
4296 // The global QPixmapCache is used in GuiPainter to cache text
4297 // painting so we must reset it.
4298 QPixmapCache::clear();
4299 guiApp->fontLoader().update();
4300 dr.screenUpdate(Update::Force | Update::FitCursor);
4304 case LFUN_VC_REGISTER:
4305 case LFUN_VC_RENAME:
4307 case LFUN_VC_CHECK_IN:
4308 case LFUN_VC_CHECK_OUT:
4309 case LFUN_VC_REPO_UPDATE:
4310 case LFUN_VC_LOCKING_TOGGLE:
4311 case LFUN_VC_REVERT:
4312 case LFUN_VC_UNDO_LAST:
4313 case LFUN_VC_COMMAND:
4314 case LFUN_VC_COMPARE:
4315 dispatchVC(cmd, dr);
4318 case LFUN_SERVER_GOTO_FILE_ROW:
4319 if(goToFileRow(to_utf8(cmd.argument())))
4320 dr.screenUpdate(Update::Force | Update::FitCursor);
4323 case LFUN_LYX_ACTIVATE:
4327 case LFUN_FORWARD_SEARCH: {
4328 // it seems safe to assume we have a document buffer, since
4329 // getStatus wants one.
4330 LATTEST(doc_buffer);
4331 Buffer const * doc_master = doc_buffer->masterBuffer();
4332 FileName const path(doc_master->temppath());
4333 string const texname = doc_master->isChild(doc_buffer)
4334 ? DocFileName(changeExtension(
4335 doc_buffer->absFileName(),
4336 "tex")).mangledFileName()
4337 : doc_buffer->latexName();
4338 string const fulltexname =
4339 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4340 string const mastername =
4341 removeExtension(doc_master->latexName());
4342 FileName const dviname(addName(path.absFileName(),
4343 addExtension(mastername, "dvi")));
4344 FileName const pdfname(addName(path.absFileName(),
4345 addExtension(mastername, "pdf")));
4346 bool const have_dvi = dviname.exists();
4347 bool const have_pdf = pdfname.exists();
4348 if (!have_dvi && !have_pdf) {
4349 dr.setMessage(_("Please, preview the document first."));
4352 string outname = dviname.onlyFileName();
4353 string command = lyxrc.forward_search_dvi;
4354 if (!have_dvi || (have_pdf &&
4355 pdfname.lastModified() > dviname.lastModified())) {
4356 outname = pdfname.onlyFileName();
4357 command = lyxrc.forward_search_pdf;
4360 DocIterator cur = bv->cursor();
4361 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4362 LYXERR(Debug::ACTION, "Forward search: row:" << row
4364 if (row == -1 || command.empty()) {
4365 dr.setMessage(_("Couldn't proceed."));
4368 string texrow = convert<string>(row);
4370 command = subst(command, "$$n", texrow);
4371 command = subst(command, "$$f", fulltexname);
4372 command = subst(command, "$$t", texname);
4373 command = subst(command, "$$o", outname);
4375 PathChanger p(path);
4377 one.startscript(Systemcall::DontWait, command);
4381 case LFUN_SPELLING_CONTINUOUSLY:
4382 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4383 dr.screenUpdate(Update::Force);
4387 // The LFUN must be for one of BufferView, Buffer or Cursor;
4389 dispatchToBufferView(cmd, dr);
4393 // Part of automatic menu appearance feature.
4394 if (isFullScreen()) {
4395 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4399 // Need to update bv because many LFUNs here might have destroyed it
4400 bv = currentBufferView();
4402 // Clear non-empty selections
4403 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4405 Cursor & cur = bv->cursor();
4406 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4407 cur.clearSelection();
4413 bool GuiView::lfunUiToggle(string const & ui_component)
4415 if (ui_component == "scrollbar") {
4416 // hide() is of no help
4417 if (d.current_work_area_->verticalScrollBarPolicy() ==
4418 Qt::ScrollBarAlwaysOff)
4420 d.current_work_area_->setVerticalScrollBarPolicy(
4421 Qt::ScrollBarAsNeeded);
4423 d.current_work_area_->setVerticalScrollBarPolicy(
4424 Qt::ScrollBarAlwaysOff);
4425 } else if (ui_component == "statusbar") {
4426 statusBar()->setVisible(!statusBar()->isVisible());
4427 } else if (ui_component == "menubar") {
4428 menuBar()->setVisible(!menuBar()->isVisible());
4430 if (ui_component == "frame") {
4432 getContentsMargins(&l, &t, &r, &b);
4433 //are the frames in default state?
4434 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4436 setContentsMargins(-2, -2, -2, -2);
4438 setContentsMargins(0, 0, 0, 0);
4441 if (ui_component == "fullscreen") {
4449 void GuiView::toggleFullScreen()
4451 if (isFullScreen()) {
4452 for (int i = 0; i != d.splitter_->count(); ++i)
4453 d.tabWorkArea(i)->setFullScreen(false);
4454 setContentsMargins(0, 0, 0, 0);
4455 setWindowState(windowState() ^ Qt::WindowFullScreen);
4458 statusBar()->show();
4461 hideDialogs("prefs", 0);
4462 for (int i = 0; i != d.splitter_->count(); ++i)
4463 d.tabWorkArea(i)->setFullScreen(true);
4464 setContentsMargins(-2, -2, -2, -2);
4466 setWindowState(windowState() ^ Qt::WindowFullScreen);
4467 if (lyxrc.full_screen_statusbar)
4468 statusBar()->hide();
4469 if (lyxrc.full_screen_menubar)
4471 if (lyxrc.full_screen_toolbars) {
4472 ToolbarMap::iterator end = d.toolbars_.end();
4473 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4478 // give dialogs like the TOC a chance to adapt
4483 Buffer const * GuiView::updateInset(Inset const * inset)
4488 Buffer const * inset_buffer = &(inset->buffer());
4490 for (int i = 0; i != d.splitter_->count(); ++i) {
4491 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4494 Buffer const * buffer = &(wa->bufferView().buffer());
4495 if (inset_buffer == buffer)
4496 wa->scheduleRedraw(true);
4498 return inset_buffer;
4502 void GuiView::restartCaret()
4504 /* When we move around, or type, it's nice to be able to see
4505 * the caret immediately after the keypress.
4507 if (d.current_work_area_)
4508 d.current_work_area_->startBlinkingCaret();
4510 // Take this occasion to update the other GUI elements.
4516 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4518 if (d.current_work_area_)
4519 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4524 // This list should be kept in sync with the list of insets in
4525 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4526 // dialog should have the same name as the inset.
4527 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4528 // docs in LyXAction.cpp.
4530 char const * const dialognames[] = {
4532 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4533 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4534 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4535 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4536 "log", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4537 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4538 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4539 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4541 char const * const * const end_dialognames =
4542 dialognames + (sizeof(dialognames) / sizeof(char *));
4546 cmpCStr(char const * name) : name_(name) {}
4547 bool operator()(char const * other) {
4548 return strcmp(other, name_) == 0;
4555 bool isValidName(string const & name)
4557 return find_if(dialognames, end_dialognames,
4558 cmpCStr(name.c_str())) != end_dialognames;
4564 void GuiView::resetDialogs()
4566 // Make sure that no LFUN uses any GuiView.
4567 guiApp->setCurrentView(0);
4571 constructToolbars();
4572 guiApp->menus().fillMenuBar(menuBar(), this, false);
4573 d.layout_->updateContents(true);
4574 // Now update controls with current buffer.
4575 guiApp->setCurrentView(this);
4581 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4583 if (!isValidName(name))
4586 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4588 if (it != d.dialogs_.end()) {
4590 it->second->hideView();
4591 return it->second.get();
4594 Dialog * dialog = build(name);
4595 d.dialogs_[name].reset(dialog);
4596 if (lyxrc.allow_geometry_session)
4597 dialog->restoreSession();
4604 void GuiView::showDialog(string const & name, string const & sdata,
4607 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4611 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4617 const string name = fromqstr(qname);
4618 const string sdata = fromqstr(qdata);
4622 Dialog * dialog = findOrBuild(name, false);
4624 bool const visible = dialog->isVisibleView();
4625 dialog->showData(sdata);
4626 if (inset && currentBufferView())
4627 currentBufferView()->editInset(name, inset);
4628 // We only set the focus to the new dialog if it was not yet
4629 // visible in order not to change the existing previous behaviour
4631 // activateWindow is needed for floating dockviews
4632 dialog->asQWidget()->raise();
4633 dialog->asQWidget()->activateWindow();
4634 dialog->asQWidget()->setFocus();
4638 catch (ExceptionMessage const & ex) {
4646 bool GuiView::isDialogVisible(string const & name) const
4648 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4649 if (it == d.dialogs_.end())
4651 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4655 void GuiView::hideDialog(string const & name, Inset * inset)
4657 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4658 if (it == d.dialogs_.end())
4662 if (!currentBufferView())
4664 if (inset != currentBufferView()->editedInset(name))
4668 Dialog * const dialog = it->second.get();
4669 if (dialog->isVisibleView())
4671 if (currentBufferView())
4672 currentBufferView()->editInset(name, 0);
4676 void GuiView::disconnectDialog(string const & name)
4678 if (!isValidName(name))
4680 if (currentBufferView())
4681 currentBufferView()->editInset(name, 0);
4685 void GuiView::hideAll() const
4687 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4688 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4690 for(; it != end; ++it)
4691 it->second->hideView();
4695 void GuiView::updateDialogs()
4697 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4698 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4700 for(; it != end; ++it) {
4701 Dialog * dialog = it->second.get();
4703 if (dialog->needBufferOpen() && !documentBufferView())
4704 hideDialog(fromqstr(dialog->name()), 0);
4705 else if (dialog->isVisibleView())
4706 dialog->checkStatus();
4713 Dialog * createDialog(GuiView & lv, string const & name);
4715 // will be replaced by a proper factory...
4716 Dialog * createGuiAbout(GuiView & lv);
4717 Dialog * createGuiBibtex(GuiView & lv);
4718 Dialog * createGuiChanges(GuiView & lv);
4719 Dialog * createGuiCharacter(GuiView & lv);
4720 Dialog * createGuiCitation(GuiView & lv);
4721 Dialog * createGuiCompare(GuiView & lv);
4722 Dialog * createGuiCompareHistory(GuiView & lv);
4723 Dialog * createGuiDelimiter(GuiView & lv);
4724 Dialog * createGuiDocument(GuiView & lv);
4725 Dialog * createGuiErrorList(GuiView & lv);
4726 Dialog * createGuiExternal(GuiView & lv);
4727 Dialog * createGuiGraphics(GuiView & lv);
4728 Dialog * createGuiInclude(GuiView & lv);
4729 Dialog * createGuiIndex(GuiView & lv);
4730 Dialog * createGuiListings(GuiView & lv);
4731 Dialog * createGuiLog(GuiView & lv);
4732 Dialog * createGuiMathMatrix(GuiView & lv);
4733 Dialog * createGuiNote(GuiView & lv);
4734 Dialog * createGuiParagraph(GuiView & lv);
4735 Dialog * createGuiPhantom(GuiView & lv);
4736 Dialog * createGuiPreferences(GuiView & lv);
4737 Dialog * createGuiPrint(GuiView & lv);
4738 Dialog * createGuiPrintindex(GuiView & lv);
4739 Dialog * createGuiRef(GuiView & lv);
4740 Dialog * createGuiSearch(GuiView & lv);
4741 Dialog * createGuiSearchAdv(GuiView & lv);
4742 Dialog * createGuiSendTo(GuiView & lv);
4743 Dialog * createGuiShowFile(GuiView & lv);
4744 Dialog * createGuiSpellchecker(GuiView & lv);
4745 Dialog * createGuiSymbols(GuiView & lv);
4746 Dialog * createGuiTabularCreate(GuiView & lv);
4747 Dialog * createGuiTexInfo(GuiView & lv);
4748 Dialog * createGuiToc(GuiView & lv);
4749 Dialog * createGuiThesaurus(GuiView & lv);
4750 Dialog * createGuiViewSource(GuiView & lv);
4751 Dialog * createGuiWrap(GuiView & lv);
4752 Dialog * createGuiProgressView(GuiView & lv);
4756 Dialog * GuiView::build(string const & name)
4758 LASSERT(isValidName(name), return 0);
4760 Dialog * dialog = createDialog(*this, name);
4764 if (name == "aboutlyx")
4765 return createGuiAbout(*this);
4766 if (name == "bibtex")
4767 return createGuiBibtex(*this);
4768 if (name == "changes")
4769 return createGuiChanges(*this);
4770 if (name == "character")
4771 return createGuiCharacter(*this);
4772 if (name == "citation")
4773 return createGuiCitation(*this);
4774 if (name == "compare")
4775 return createGuiCompare(*this);
4776 if (name == "comparehistory")
4777 return createGuiCompareHistory(*this);
4778 if (name == "document")
4779 return createGuiDocument(*this);
4780 if (name == "errorlist")
4781 return createGuiErrorList(*this);
4782 if (name == "external")
4783 return createGuiExternal(*this);
4785 return createGuiShowFile(*this);
4786 if (name == "findreplace")
4787 return createGuiSearch(*this);
4788 if (name == "findreplaceadv")
4789 return createGuiSearchAdv(*this);
4790 if (name == "graphics")
4791 return createGuiGraphics(*this);
4792 if (name == "include")
4793 return createGuiInclude(*this);
4794 if (name == "index")
4795 return createGuiIndex(*this);
4796 if (name == "index_print")
4797 return createGuiPrintindex(*this);
4798 if (name == "listings")
4799 return createGuiListings(*this);
4801 return createGuiLog(*this);
4802 if (name == "mathdelimiter")
4803 return createGuiDelimiter(*this);
4804 if (name == "mathmatrix")
4805 return createGuiMathMatrix(*this);
4807 return createGuiNote(*this);
4808 if (name == "paragraph")
4809 return createGuiParagraph(*this);
4810 if (name == "phantom")
4811 return createGuiPhantom(*this);
4812 if (name == "prefs")
4813 return createGuiPreferences(*this);
4815 return createGuiRef(*this);
4816 if (name == "sendto")
4817 return createGuiSendTo(*this);
4818 if (name == "spellchecker")
4819 return createGuiSpellchecker(*this);
4820 if (name == "symbols")
4821 return createGuiSymbols(*this);
4822 if (name == "tabularcreate")
4823 return createGuiTabularCreate(*this);
4824 if (name == "texinfo")
4825 return createGuiTexInfo(*this);
4826 if (name == "thesaurus")
4827 return createGuiThesaurus(*this);
4829 return createGuiToc(*this);
4830 if (name == "view-source")
4831 return createGuiViewSource(*this);
4833 return createGuiWrap(*this);
4834 if (name == "progress")
4835 return createGuiProgressView(*this);
4841 SEMenu::SEMenu(QWidget * parent)
4843 QAction * action = addAction(qt_("Disable Shell Escape"));
4844 connect(action, SIGNAL(triggered()),
4845 parent, SLOT(disableShellEscape()));
4848 } // namespace frontend
4851 #include "moc_GuiView.cpp"