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 "GuiClickableLabel.h"
23 #include "GuiCommandBuffer.h"
24 #include "GuiCompleter.h"
25 #include "GuiKeySymbol.h"
27 #include "GuiToolbar.h"
28 #include "GuiWorkArea.h"
29 #include "GuiProgress.h"
30 #include "LayoutBox.h"
34 #include "qt_helpers.h"
35 #include "support/filetools.h"
37 #include "frontends/alert.h"
38 #include "frontends/KeySymbol.h"
40 #include "buffer_funcs.h"
42 #include "BufferList.h"
43 #include "BufferParams.h"
44 #include "BufferView.h"
46 #include "Converter.h"
48 #include "CutAndPaste.h"
50 #include "ErrorList.h"
52 #include "FuncStatus.h"
53 #include "FuncRequest.h"
57 #include "LayoutFile.h"
59 #include "LyXAction.h"
63 #include "Paragraph.h"
64 #include "SpellChecker.h"
67 #include "TextClass.h"
72 #include "support/convert.h"
73 #include "support/debug.h"
74 #include "support/ExceptionMessage.h"
75 #include "support/FileName.h"
76 #include "support/filetools.h"
77 #include "support/gettext.h"
78 #include "support/filetools.h"
79 #include "support/ForkedCalls.h"
80 #include "support/lassert.h"
81 #include "support/lstrings.h"
82 #include "support/os.h"
83 #include "support/Package.h"
84 #include "support/PathChanger.h"
85 #include "support/Systemcall.h"
86 #include "support/Timeout.h"
87 #include "support/ProgressInterface.h"
90 #include <QApplication>
91 #include <QCloseEvent>
93 #include <QDesktopWidget>
94 #include <QDragEnterEvent>
97 #include <QFutureWatcher>
107 #include <QPushButton>
108 #include <QScrollBar>
110 #include <QShowEvent>
112 #include <QStackedWidget>
113 #include <QStatusBar>
114 #include <QSvgRenderer>
115 #include <QtConcurrentRun>
123 // sync with GuiAlert.cpp
124 #define EXPORT_in_THREAD 1
127 #include "support/bind.h"
131 #ifdef HAVE_SYS_TIME_H
132 # include <sys/time.h>
140 using namespace lyx::support;
144 using support::addExtension;
145 using support::changeExtension;
146 using support::removeExtension;
152 class BackgroundWidget : public QWidget
155 BackgroundWidget(int width, int height)
156 : width_(width), height_(height)
158 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
159 if (!lyxrc.show_banner)
161 /// The text to be written on top of the pixmap
162 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
163 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
164 /// The text to be written on top of the pixmap
165 QString const text = lyx_version ?
166 qt_("version ") + lyx_version : qt_("unknown version");
167 #if QT_VERSION >= 0x050000
168 QString imagedir = "images/";
169 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
170 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
171 if (svgRenderer.isValid()) {
172 splash_ = QPixmap(splashSize());
173 QPainter painter(&splash_);
174 svgRenderer.render(&painter);
175 splash_.setDevicePixelRatio(pixelRatio());
177 splash_ = getPixmap("images/", "banner", "png");
180 splash_ = getPixmap("images/", "banner", "svgz,png");
183 QPainter pain(&splash_);
184 pain.setPen(QColor(0, 0, 0));
185 qreal const fsize = fontSize();
188 qreal locscale = htextsize.toFloat(&ok);
191 QPointF const position = textPosition(false);
192 QPointF const hposition = textPosition(true);
193 QRectF const hrect(hposition, splashSize());
195 "widget pixel ratio: " << pixelRatio() <<
196 " splash pixel ratio: " << splashPixelRatio() <<
197 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
199 // The font used to display the version info
200 font.setStyleHint(QFont::SansSerif);
201 font.setWeight(QFont::Bold);
202 font.setPointSizeF(fsize);
204 pain.drawText(position, text);
205 // The font used to display the version info
206 font.setStyleHint(QFont::SansSerif);
207 font.setWeight(QFont::Normal);
208 font.setPointSizeF(hfsize);
209 // Check how long the logo gets with the current font
210 // and adapt if the font is running wider than what
212 QFontMetrics fm(font);
213 // Split the title into lines to measure the longest line
214 // in the current l7n.
215 QStringList titlesegs = htext.split('\n');
217 int hline = fm.height();
218 QStringList::const_iterator sit;
219 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
220 if (fm.width(*sit) > wline)
221 wline = fm.width(*sit);
223 // The longest line in the reference font (for English)
224 // is 180. Calculate scale factor from that.
225 double const wscale = wline > 0 ? (180.0 / wline) : 1;
226 // Now do the same for the height (necessary for condensed fonts)
227 double const hscale = (34.0 / hline);
228 // take the lower of the two scale factors.
229 double const scale = min(wscale, hscale);
230 // Now rescale. Also consider l7n's offset factor.
231 font.setPointSizeF(hfsize * scale * locscale);
234 pain.drawText(hrect, Qt::AlignLeft, htext);
235 setFocusPolicy(Qt::StrongFocus);
238 void paintEvent(QPaintEvent *)
240 int const w = width_;
241 int const h = height_;
242 int const x = (width() - w) / 2;
243 int const y = (height() - h) / 2;
245 "widget pixel ratio: " << pixelRatio() <<
246 " splash pixel ratio: " << splashPixelRatio() <<
247 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
249 pain.drawPixmap(x, y, w, h, splash_);
252 void keyPressEvent(QKeyEvent * ev)
255 setKeySymbol(&sym, ev);
257 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
269 /// Current ratio between physical pixels and device-independent pixels
270 double pixelRatio() const {
271 #if QT_VERSION >= 0x050000
272 return qt_scale_factor * devicePixelRatio();
278 qreal fontSize() const {
279 return toqstr(lyxrc.font_sizes[NORMAL_SIZE]).toDouble();
282 QPointF textPosition(bool const heading) const {
283 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
284 : QPointF(width_/2 - 18, height_/2 + 45);
287 QSize splashSize() const {
289 static_cast<unsigned int>(width_ * pixelRatio()),
290 static_cast<unsigned int>(height_ * pixelRatio()));
293 /// Ratio between physical pixels and device-independent pixels of splash image
294 double splashPixelRatio() const {
295 #if QT_VERSION >= 0x050000
296 return splash_.devicePixelRatio();
304 /// Toolbar store providing access to individual toolbars by name.
305 typedef map<string, GuiToolbar *> ToolbarMap;
307 typedef shared_ptr<Dialog> DialogPtr;
312 class GuiView::GuiViewPrivate
315 GuiViewPrivate(GuiViewPrivate const &);
316 void operator=(GuiViewPrivate const &);
318 GuiViewPrivate(GuiView * gv)
319 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
320 layout_(0), autosave_timeout_(5000),
323 // hardcode here the platform specific icon size
324 smallIconSize = 16; // scaling problems
325 normalIconSize = 20; // ok, default if iconsize.png is missing
326 bigIconSize = 26; // better for some math icons
327 hugeIconSize = 32; // better for hires displays
330 // if it exists, use width of iconsize.png as normal size
331 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
332 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
334 QImage image(toqstr(fn.absFileName()));
335 if (image.width() < int(smallIconSize))
336 normalIconSize = smallIconSize;
337 else if (image.width() > int(giantIconSize))
338 normalIconSize = giantIconSize;
340 normalIconSize = image.width();
343 splitter_ = new QSplitter;
344 bg_widget_ = new BackgroundWidget(400, 250);
345 stack_widget_ = new QStackedWidget;
346 stack_widget_->addWidget(bg_widget_);
347 stack_widget_->addWidget(splitter_);
350 // TODO cleanup, remove the singleton, handle multiple Windows?
351 progress_ = ProgressInterface::instance();
352 if (!dynamic_cast<GuiProgress*>(progress_)) {
353 progress_ = new GuiProgress; // TODO who deletes it
354 ProgressInterface::setInstance(progress_);
357 dynamic_cast<GuiProgress*>(progress_),
358 SIGNAL(updateStatusBarMessage(QString const&)),
359 gv, SLOT(updateStatusBarMessage(QString const&)));
361 dynamic_cast<GuiProgress*>(progress_),
362 SIGNAL(clearMessageText()),
363 gv, SLOT(clearMessageText()));
370 delete stack_widget_;
375 stack_widget_->setCurrentWidget(bg_widget_);
376 bg_widget_->setUpdatesEnabled(true);
377 bg_widget_->setFocus();
380 int tabWorkAreaCount()
382 return splitter_->count();
385 TabWorkArea * tabWorkArea(int i)
387 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
390 TabWorkArea * currentTabWorkArea()
392 int areas = tabWorkAreaCount();
394 // The first TabWorkArea is always the first one, if any.
395 return tabWorkArea(0);
397 for (int i = 0; i != areas; ++i) {
398 TabWorkArea * twa = tabWorkArea(i);
399 if (current_main_work_area_ == twa->currentWorkArea())
403 // None has the focus so we just take the first one.
404 return tabWorkArea(0);
407 int countWorkAreasOf(Buffer & buf)
409 int areas = tabWorkAreaCount();
411 for (int i = 0; i != areas; ++i) {
412 TabWorkArea * twa = tabWorkArea(i);
413 if (twa->workArea(buf))
419 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
421 if (processing_thread_watcher_.isRunning()) {
422 // we prefer to cancel this preview in order to keep a snappy
426 processing_thread_watcher_.setFuture(f);
429 QSize iconSize(docstring const & icon_size)
432 if (icon_size == "small")
433 size = smallIconSize;
434 else if (icon_size == "normal")
435 size = normalIconSize;
436 else if (icon_size == "big")
438 else if (icon_size == "huge")
440 else if (icon_size == "giant")
441 size = giantIconSize;
443 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
445 if (size < smallIconSize)
446 size = smallIconSize;
448 return QSize(size, size);
451 QSize iconSize(QString const & icon_size)
453 return iconSize(qstring_to_ucs4(icon_size));
456 string & iconSize(QSize const & qsize)
458 LATTEST(qsize.width() == qsize.height());
460 static string icon_size;
462 unsigned int size = qsize.width();
464 if (size < smallIconSize)
465 size = smallIconSize;
467 if (size == smallIconSize)
469 else if (size == normalIconSize)
470 icon_size = "normal";
471 else if (size == bigIconSize)
473 else if (size == hugeIconSize)
475 else if (size == giantIconSize)
478 icon_size = convert<string>(size);
485 GuiWorkArea * current_work_area_;
486 GuiWorkArea * current_main_work_area_;
487 QSplitter * splitter_;
488 QStackedWidget * stack_widget_;
489 BackgroundWidget * bg_widget_;
491 ToolbarMap toolbars_;
492 ProgressInterface* progress_;
493 /// The main layout box.
495 * \warning Don't Delete! The layout box is actually owned by
496 * whichever toolbar contains it. All the GuiView class needs is a
497 * means of accessing it.
499 * FIXME: replace that with a proper model so that we are not limited
500 * to only one dialog.
505 map<string, DialogPtr> dialogs_;
507 unsigned int smallIconSize;
508 unsigned int normalIconSize;
509 unsigned int bigIconSize;
510 unsigned int hugeIconSize;
511 unsigned int giantIconSize;
513 QTimer statusbar_timer_;
514 /// auto-saving of buffers
515 Timeout autosave_timeout_;
516 /// flag against a race condition due to multiclicks, see bug #1119
520 TocModels toc_models_;
523 QFutureWatcher<docstring> autosave_watcher_;
524 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
526 string last_export_format;
527 string processing_format;
529 static QSet<Buffer const *> busyBuffers;
530 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
531 Buffer * buffer, string const & format);
532 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
533 Buffer * buffer, string const & format);
534 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
535 Buffer * buffer, string const & format);
536 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
539 static Buffer::ExportStatus runAndDestroy(const T& func,
540 Buffer const * orig, Buffer * buffer, string const & format);
542 // TODO syncFunc/previewFunc: use bind
543 bool asyncBufferProcessing(string const & argument,
544 Buffer const * used_buffer,
545 docstring const & msg,
546 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
547 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
548 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
551 QVector<GuiWorkArea*> guiWorkAreas();
554 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
557 GuiView::GuiView(int id)
558 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
559 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
562 connect(this, SIGNAL(bufferViewChanged()),
563 this, SLOT(onBufferViewChanged()));
565 // GuiToolbars *must* be initialised before the menu bar.
566 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
569 // set ourself as the current view. This is needed for the menu bar
570 // filling, at least for the static special menu item on Mac. Otherwise
571 // they are greyed out.
572 guiApp->setCurrentView(this);
574 // Fill up the menu bar.
575 guiApp->menus().fillMenuBar(menuBar(), this, true);
577 setCentralWidget(d.stack_widget_);
579 // Start autosave timer
580 if (lyxrc.autosave) {
581 // The connection is closed when this is destroyed.
582 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
583 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
584 d.autosave_timeout_.start();
586 connect(&d.statusbar_timer_, SIGNAL(timeout()),
587 this, SLOT(clearMessage()));
589 // We don't want to keep the window in memory if it is closed.
590 setAttribute(Qt::WA_DeleteOnClose, true);
592 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
593 // QIcon::fromTheme was introduced in Qt 4.6
594 #if (QT_VERSION >= 0x040600)
595 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
596 // since the icon is provided in the application bundle. We use a themed
597 // version when available and use the bundled one as fallback.
598 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
600 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
606 // use tabbed dock area for multiple docks
607 // (such as "source" and "messages")
608 setDockOptions(QMainWindow::ForceTabbedDocks);
611 setAcceptDrops(true);
613 // add busy indicator to statusbar
614 GuiClickableLabel * busylabel = new GuiClickableLabel(statusBar());
615 statusBar()->addPermanentWidget(busylabel);
616 search_mode mode = theGuiApp()->imageSearchMode();
617 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
618 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
619 busylabel->setMovie(busyanim);
623 connect(&d.processing_thread_watcher_, SIGNAL(started()),
624 busylabel, SLOT(show()));
625 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
626 busylabel, SLOT(hide()));
627 connect(busylabel, SIGNAL(clicked()), this, SLOT(checkCancelBackground()));
629 QFontMetrics const fm(statusBar()->fontMetrics());
630 int const iconheight = max(int(d.normalIconSize), fm.height());
631 QSize const iconsize(iconheight, iconheight);
633 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
634 shell_escape_ = new QLabel(statusBar());
635 shell_escape_->setPixmap(shellescape);
636 shell_escape_->setScaledContents(true);
637 shell_escape_->setAlignment(Qt::AlignCenter);
638 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
639 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
640 "external commands for this document. "
641 "Right click to change."));
642 SEMenu * menu = new SEMenu(this);
643 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
644 menu, SLOT(showMenu(QPoint)));
645 shell_escape_->hide();
646 statusBar()->addPermanentWidget(shell_escape_);
648 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
649 read_only_ = new QLabel(statusBar());
650 read_only_->setPixmap(readonly);
651 read_only_->setScaledContents(true);
652 read_only_->setAlignment(Qt::AlignCenter);
654 statusBar()->addPermanentWidget(read_only_);
656 version_control_ = new QLabel(statusBar());
657 version_control_->setAlignment(Qt::AlignCenter);
658 version_control_->setFrameStyle(QFrame::StyledPanel);
659 version_control_->hide();
660 statusBar()->addPermanentWidget(version_control_);
662 statusBar()->setSizeGripEnabled(true);
665 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
666 SLOT(autoSaveThreadFinished()));
668 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
669 SLOT(processingThreadStarted()));
670 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
671 SLOT(processingThreadFinished()));
673 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
674 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
676 // set custom application bars context menu, e.g. tool bar and menu bar
677 setContextMenuPolicy(Qt::CustomContextMenu);
678 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
679 SLOT(toolBarPopup(const QPoint &)));
681 // Forbid too small unresizable window because it can happen
682 // with some window manager under X11.
683 setMinimumSize(300, 200);
685 if (lyxrc.allow_geometry_session) {
686 // Now take care of session management.
691 // no session handling, default to a sane size.
692 setGeometry(50, 50, 690, 510);
695 // clear session data if any.
697 settings.remove("views");
707 void GuiView::disableShellEscape()
709 BufferView * bv = documentBufferView();
712 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
713 bv->buffer().params().shell_escape = false;
714 bv->processUpdateFlags(Update::Force);
718 void GuiView::checkCancelBackground()
720 docstring const ttl = _("Cancel Export?");
721 docstring const msg = _("Do you want to cancel the background export process?");
723 Alert::prompt(ttl, msg, 1, 1,
724 _("&Cancel export"), _("Co&ntinue"));
726 Systemcall::killscript();
730 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
732 QVector<GuiWorkArea*> areas;
733 for (int i = 0; i < tabWorkAreaCount(); i++) {
734 TabWorkArea* ta = tabWorkArea(i);
735 for (int u = 0; u < ta->count(); u++) {
736 areas << ta->workArea(u);
742 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
743 string const & format)
745 docstring const fmt = theFormats().prettyName(format);
748 case Buffer::ExportSuccess:
749 msg = bformat(_("Successful export to format: %1$s"), fmt);
751 case Buffer::ExportCancel:
752 msg = _("Document export cancelled.");
754 case Buffer::ExportError:
755 case Buffer::ExportNoPathToFormat:
756 case Buffer::ExportTexPathHasSpaces:
757 case Buffer::ExportConverterError:
758 msg = bformat(_("Error while exporting format: %1$s"), fmt);
760 case Buffer::PreviewSuccess:
761 msg = bformat(_("Successful preview of format: %1$s"), fmt);
763 case Buffer::PreviewError:
764 msg = bformat(_("Error while previewing format: %1$s"), fmt);
766 case Buffer::ExportKilled:
767 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
774 void GuiView::processingThreadStarted()
779 void GuiView::processingThreadFinished()
781 QFutureWatcher<Buffer::ExportStatus> const * watcher =
782 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
784 Buffer::ExportStatus const status = watcher->result();
785 handleExportStatus(this, status, d.processing_format);
788 BufferView const * const bv = currentBufferView();
789 if (bv && !bv->buffer().errorList("Export").empty()) {
794 bool const error = (status != Buffer::ExportSuccess &&
795 status != Buffer::PreviewSuccess &&
796 status != Buffer::ExportCancel);
798 ErrorList & el = bv->buffer().errorList(d.last_export_format);
799 // at this point, we do not know if buffer-view or
800 // master-buffer-view was called. If there was an export error,
801 // and the current buffer's error log is empty, we guess that
802 // it must be master-buffer-view that was called so we set
804 errors(d.last_export_format, el.empty());
809 void GuiView::autoSaveThreadFinished()
811 QFutureWatcher<docstring> const * watcher =
812 static_cast<QFutureWatcher<docstring> const *>(sender());
813 message(watcher->result());
818 void GuiView::saveLayout() const
821 settings.setValue("zoom_ratio", zoom_ratio_);
822 settings.setValue("devel_mode", devel_mode_);
823 settings.beginGroup("views");
824 settings.beginGroup(QString::number(id_));
825 #if defined(Q_WS_X11) || defined(QPA_XCB)
826 settings.setValue("pos", pos());
827 settings.setValue("size", size());
829 settings.setValue("geometry", saveGeometry());
831 settings.setValue("layout", saveState(0));
832 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
836 void GuiView::saveUISettings() const
840 // Save the toolbar private states
841 ToolbarMap::iterator end = d.toolbars_.end();
842 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
843 it->second->saveSession(settings);
844 // Now take care of all other dialogs
845 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
846 for (; it!= d.dialogs_.end(); ++it)
847 it->second->saveSession(settings);
851 bool GuiView::restoreLayout()
854 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
855 // Actual zoom value: default zoom + fractional offset
856 int zoom = lyxrc.defaultZoom * zoom_ratio_;
857 if (zoom < static_cast<int>(zoom_min_))
859 lyxrc.currentZoom = zoom;
860 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
861 settings.beginGroup("views");
862 settings.beginGroup(QString::number(id_));
863 QString const icon_key = "icon_size";
864 if (!settings.contains(icon_key))
867 //code below is skipped when when ~/.config/LyX is (re)created
868 setIconSize(d.iconSize(settings.value(icon_key).toString()));
870 #if defined(Q_WS_X11) || defined(QPA_XCB)
871 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
872 QSize size = settings.value("size", QSize(690, 510)).toSize();
876 // Work-around for bug #6034: the window ends up in an undetermined
877 // state when trying to restore a maximized window when it is
878 // already maximized.
879 if (!(windowState() & Qt::WindowMaximized))
880 if (!restoreGeometry(settings.value("geometry").toByteArray()))
881 setGeometry(50, 50, 690, 510);
883 // Make sure layout is correctly oriented.
884 setLayoutDirection(qApp->layoutDirection());
886 // Allow the toc and view-source dock widget to be restored if needed.
888 if ((dialog = findOrBuild("toc", true)))
889 // see bug 5082. At least setup title and enabled state.
890 // Visibility will be adjusted by restoreState below.
891 dialog->prepareView();
892 if ((dialog = findOrBuild("view-source", true)))
893 dialog->prepareView();
894 if ((dialog = findOrBuild("progress", true)))
895 dialog->prepareView();
897 if (!restoreState(settings.value("layout").toByteArray(), 0))
900 // init the toolbars that have not been restored
901 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
902 Toolbars::Infos::iterator end = guiApp->toolbars().end();
903 for (; cit != end; ++cit) {
904 GuiToolbar * tb = toolbar(cit->name);
905 if (tb && !tb->isRestored())
906 initToolbar(cit->name);
909 // update lock (all) toolbars positions
910 updateLockToolbars();
917 GuiToolbar * GuiView::toolbar(string const & name)
919 ToolbarMap::iterator it = d.toolbars_.find(name);
920 if (it != d.toolbars_.end())
923 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
928 void GuiView::updateLockToolbars()
930 toolbarsMovable_ = false;
931 for (ToolbarInfo const & info : guiApp->toolbars()) {
932 GuiToolbar * tb = toolbar(info.name);
933 if (tb && tb->isMovable())
934 toolbarsMovable_ = true;
939 void GuiView::constructToolbars()
941 ToolbarMap::iterator it = d.toolbars_.begin();
942 for (; it != d.toolbars_.end(); ++it)
946 // I don't like doing this here, but the standard toolbar
947 // destroys this object when it's destroyed itself (vfr)
948 d.layout_ = new LayoutBox(*this);
949 d.stack_widget_->addWidget(d.layout_);
950 d.layout_->move(0,0);
952 // extracts the toolbars from the backend
953 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
954 Toolbars::Infos::iterator end = guiApp->toolbars().end();
955 for (; cit != end; ++cit)
956 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
960 void GuiView::initToolbars()
962 // extracts the toolbars from the backend
963 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
964 Toolbars::Infos::iterator end = guiApp->toolbars().end();
965 for (; cit != end; ++cit)
966 initToolbar(cit->name);
970 void GuiView::initToolbar(string const & name)
972 GuiToolbar * tb = toolbar(name);
975 int const visibility = guiApp->toolbars().defaultVisibility(name);
976 bool newline = !(visibility & Toolbars::SAMEROW);
977 tb->setVisible(false);
978 tb->setVisibility(visibility);
980 if (visibility & Toolbars::TOP) {
982 addToolBarBreak(Qt::TopToolBarArea);
983 addToolBar(Qt::TopToolBarArea, tb);
986 if (visibility & Toolbars::BOTTOM) {
988 addToolBarBreak(Qt::BottomToolBarArea);
989 addToolBar(Qt::BottomToolBarArea, tb);
992 if (visibility & Toolbars::LEFT) {
994 addToolBarBreak(Qt::LeftToolBarArea);
995 addToolBar(Qt::LeftToolBarArea, tb);
998 if (visibility & Toolbars::RIGHT) {
1000 addToolBarBreak(Qt::RightToolBarArea);
1001 addToolBar(Qt::RightToolBarArea, tb);
1004 if (visibility & Toolbars::ON)
1005 tb->setVisible(true);
1007 tb->setMovable(true);
1011 TocModels & GuiView::tocModels()
1013 return d.toc_models_;
1017 void GuiView::setFocus()
1019 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1020 QMainWindow::setFocus();
1024 bool GuiView::hasFocus() const
1026 if (currentWorkArea())
1027 return currentWorkArea()->hasFocus();
1028 if (currentMainWorkArea())
1029 return currentMainWorkArea()->hasFocus();
1030 return d.bg_widget_->hasFocus();
1034 void GuiView::focusInEvent(QFocusEvent * e)
1036 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1037 QMainWindow::focusInEvent(e);
1038 // Make sure guiApp points to the correct view.
1039 guiApp->setCurrentView(this);
1040 if (currentWorkArea())
1041 currentWorkArea()->setFocus();
1042 else if (currentMainWorkArea())
1043 currentMainWorkArea()->setFocus();
1045 d.bg_widget_->setFocus();
1049 void GuiView::showEvent(QShowEvent * e)
1051 LYXERR(Debug::GUI, "Passed Geometry "
1052 << size().height() << "x" << size().width()
1053 << "+" << pos().x() << "+" << pos().y());
1055 if (d.splitter_->count() == 0)
1056 // No work area, switch to the background widget.
1060 QMainWindow::showEvent(e);
1064 bool GuiView::closeScheduled()
1071 bool GuiView::prepareAllBuffersForLogout()
1073 Buffer * first = theBufferList().first();
1077 // First, iterate over all buffers and ask the users if unsaved
1078 // changes should be saved.
1079 // We cannot use a for loop as the buffer list cycles.
1082 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1084 b = theBufferList().next(b);
1085 } while (b != first);
1087 // Next, save session state
1088 // When a view/window was closed before without quitting LyX, there
1089 // are already entries in the lastOpened list.
1090 theSession().lastOpened().clear();
1097 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1098 ** is responsibility of the container (e.g., dialog)
1100 void GuiView::closeEvent(QCloseEvent * close_event)
1102 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1104 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1105 Alert::warning(_("Exit LyX"),
1106 _("LyX could not be closed because documents are being processed by LyX."));
1107 close_event->setAccepted(false);
1111 // If the user pressed the x (so we didn't call closeView
1112 // programmatically), we want to clear all existing entries.
1114 theSession().lastOpened().clear();
1119 // it can happen that this event arrives without selecting the view,
1120 // e.g. when clicking the close button on a background window.
1122 if (!closeWorkAreaAll()) {
1124 close_event->ignore();
1128 // Make sure that nothing will use this to be closed View.
1129 guiApp->unregisterView(this);
1131 if (isFullScreen()) {
1132 // Switch off fullscreen before closing.
1137 // Make sure the timer time out will not trigger a statusbar update.
1138 d.statusbar_timer_.stop();
1140 // Saving fullscreen requires additional tweaks in the toolbar code.
1141 // It wouldn't also work under linux natively.
1142 if (lyxrc.allow_geometry_session) {
1147 close_event->accept();
1151 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1153 if (event->mimeData()->hasUrls())
1155 /// \todo Ask lyx-devel is this is enough:
1156 /// if (event->mimeData()->hasFormat("text/plain"))
1157 /// event->acceptProposedAction();
1161 void GuiView::dropEvent(QDropEvent * event)
1163 QList<QUrl> files = event->mimeData()->urls();
1164 if (files.isEmpty())
1167 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1168 for (int i = 0; i != files.size(); ++i) {
1169 string const file = os::internal_path(fromqstr(
1170 files.at(i).toLocalFile()));
1174 string const ext = support::getExtension(file);
1175 vector<const Format *> found_formats;
1177 // Find all formats that have the correct extension.
1178 vector<const Format *> const & import_formats
1179 = theConverters().importableFormats();
1180 vector<const Format *>::const_iterator it = import_formats.begin();
1181 for (; it != import_formats.end(); ++it)
1182 if ((*it)->hasExtension(ext))
1183 found_formats.push_back(*it);
1186 if (found_formats.size() >= 1) {
1187 if (found_formats.size() > 1) {
1188 //FIXME: show a dialog to choose the correct importable format
1189 LYXERR(Debug::FILES,
1190 "Multiple importable formats found, selecting first");
1192 string const arg = found_formats[0]->name() + " " + file;
1193 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1196 //FIXME: do we have to explicitly check whether it's a lyx file?
1197 LYXERR(Debug::FILES,
1198 "No formats found, trying to open it as a lyx file");
1199 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1201 // add the functions to the queue
1202 guiApp->addToFuncRequestQueue(cmd);
1205 // now process the collected functions. We perform the events
1206 // asynchronously. This prevents potential problems in case the
1207 // BufferView is closed within an event.
1208 guiApp->processFuncRequestQueueAsync();
1212 void GuiView::message(docstring const & str)
1214 if (ForkedProcess::iAmAChild())
1217 // call is moved to GUI-thread by GuiProgress
1218 d.progress_->appendMessage(toqstr(str));
1222 void GuiView::clearMessageText()
1224 message(docstring());
1228 void GuiView::updateStatusBarMessage(QString const & str)
1230 statusBar()->showMessage(str);
1231 d.statusbar_timer_.stop();
1232 d.statusbar_timer_.start(3000);
1236 void GuiView::clearMessage()
1238 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1239 // the hasFocus function mostly returns false, even if the focus is on
1240 // a workarea in this view.
1244 d.statusbar_timer_.stop();
1248 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1250 if (wa != d.current_work_area_
1251 || wa->bufferView().buffer().isInternal())
1253 Buffer const & buf = wa->bufferView().buffer();
1254 // Set the windows title
1255 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1256 if (buf.notifiesExternalModification()) {
1257 title = bformat(_("%1$s (modified externally)"), title);
1258 // If the external modification status has changed, then maybe the status of
1259 // buffer-save has changed too.
1263 title += from_ascii(" - LyX");
1265 setWindowTitle(toqstr(title));
1266 // Sets the path for the window: this is used by OSX to
1267 // allow a context click on the title bar showing a menu
1268 // with the path up to the file
1269 setWindowFilePath(toqstr(buf.absFileName()));
1270 // Tell Qt whether the current document is changed
1271 setWindowModified(!buf.isClean());
1273 if (buf.params().shell_escape)
1274 shell_escape_->show();
1276 shell_escape_->hide();
1278 if (buf.hasReadonlyFlag())
1283 if (buf.lyxvc().inUse()) {
1284 version_control_->show();
1285 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1287 version_control_->hide();
1291 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1293 if (d.current_work_area_)
1294 // disconnect the current work area from all slots
1295 QObject::disconnect(d.current_work_area_, 0, this, 0);
1297 disconnectBufferView();
1298 connectBufferView(wa->bufferView());
1299 connectBuffer(wa->bufferView().buffer());
1300 d.current_work_area_ = wa;
1301 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1302 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1303 QObject::connect(wa, SIGNAL(busy(bool)),
1304 this, SLOT(setBusy(bool)));
1305 // connection of a signal to a signal
1306 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1307 this, SIGNAL(bufferViewChanged()));
1308 Q_EMIT updateWindowTitle(wa);
1309 Q_EMIT bufferViewChanged();
1313 void GuiView::onBufferViewChanged()
1316 // Buffer-dependent dialogs must be updated. This is done here because
1317 // some dialogs require buffer()->text.
1322 void GuiView::on_lastWorkAreaRemoved()
1325 // We already are in a close event. Nothing more to do.
1328 if (d.splitter_->count() > 1)
1329 // We have a splitter so don't close anything.
1332 // Reset and updates the dialogs.
1333 Q_EMIT bufferViewChanged();
1338 if (lyxrc.open_buffers_in_tabs)
1339 // Nothing more to do, the window should stay open.
1342 if (guiApp->viewIds().size() > 1) {
1348 // On Mac we also close the last window because the application stay
1349 // resident in memory. On other platforms we don't close the last
1350 // window because this would quit the application.
1356 void GuiView::updateStatusBar()
1358 // let the user see the explicit message
1359 if (d.statusbar_timer_.isActive())
1366 void GuiView::showMessage()
1370 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1371 if (msg.isEmpty()) {
1372 BufferView const * bv = currentBufferView();
1374 msg = toqstr(bv->cursor().currentState(devel_mode_));
1376 msg = qt_("Welcome to LyX!");
1378 statusBar()->showMessage(msg);
1382 bool GuiView::event(QEvent * e)
1386 // Useful debug code:
1387 //case QEvent::ActivationChange:
1388 //case QEvent::WindowDeactivate:
1389 //case QEvent::Paint:
1390 //case QEvent::Enter:
1391 //case QEvent::Leave:
1392 //case QEvent::HoverEnter:
1393 //case QEvent::HoverLeave:
1394 //case QEvent::HoverMove:
1395 //case QEvent::StatusTip:
1396 //case QEvent::DragEnter:
1397 //case QEvent::DragLeave:
1398 //case QEvent::Drop:
1401 case QEvent::WindowActivate: {
1402 GuiView * old_view = guiApp->currentView();
1403 if (this == old_view) {
1405 return QMainWindow::event(e);
1407 if (old_view && old_view->currentBufferView()) {
1408 // save current selection to the selection buffer to allow
1409 // middle-button paste in this window.
1410 cap::saveSelection(old_view->currentBufferView()->cursor());
1412 guiApp->setCurrentView(this);
1413 if (d.current_work_area_)
1414 on_currentWorkAreaChanged(d.current_work_area_);
1418 return QMainWindow::event(e);
1421 case QEvent::ShortcutOverride: {
1423 if (isFullScreen() && menuBar()->isHidden()) {
1424 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1425 // FIXME: we should also try to detect special LyX shortcut such as
1426 // Alt-P and Alt-M. Right now there is a hack in
1427 // GuiWorkArea::processKeySym() that hides again the menubar for
1429 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1431 return QMainWindow::event(e);
1434 return QMainWindow::event(e);
1438 return QMainWindow::event(e);
1442 void GuiView::resetWindowTitle()
1444 setWindowTitle(qt_("LyX"));
1447 bool GuiView::focusNextPrevChild(bool /*next*/)
1454 bool GuiView::busy() const
1460 void GuiView::setBusy(bool busy)
1462 bool const busy_before = busy_ > 0;
1463 busy ? ++busy_ : --busy_;
1464 if ((busy_ > 0) == busy_before)
1465 // busy state didn't change
1469 QApplication::setOverrideCursor(Qt::WaitCursor);
1472 QApplication::restoreOverrideCursor();
1477 void GuiView::resetCommandExecute()
1479 command_execute_ = false;
1484 double GuiView::pixelRatio() const
1486 #if QT_VERSION >= 0x050000
1487 return qt_scale_factor * devicePixelRatio();
1494 GuiWorkArea * GuiView::workArea(int index)
1496 if (TabWorkArea * twa = d.currentTabWorkArea())
1497 if (index < twa->count())
1498 return twa->workArea(index);
1503 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1505 if (currentWorkArea()
1506 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1507 return currentWorkArea();
1508 if (TabWorkArea * twa = d.currentTabWorkArea())
1509 return twa->workArea(buffer);
1514 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1516 // Automatically create a TabWorkArea if there are none yet.
1517 TabWorkArea * tab_widget = d.splitter_->count()
1518 ? d.currentTabWorkArea() : addTabWorkArea();
1519 return tab_widget->addWorkArea(buffer, *this);
1523 TabWorkArea * GuiView::addTabWorkArea()
1525 TabWorkArea * twa = new TabWorkArea;
1526 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1527 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1528 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1529 this, SLOT(on_lastWorkAreaRemoved()));
1531 d.splitter_->addWidget(twa);
1532 d.stack_widget_->setCurrentWidget(d.splitter_);
1537 GuiWorkArea const * GuiView::currentWorkArea() const
1539 return d.current_work_area_;
1543 GuiWorkArea * GuiView::currentWorkArea()
1545 return d.current_work_area_;
1549 GuiWorkArea const * GuiView::currentMainWorkArea() const
1551 if (!d.currentTabWorkArea())
1553 return d.currentTabWorkArea()->currentWorkArea();
1557 GuiWorkArea * GuiView::currentMainWorkArea()
1559 if (!d.currentTabWorkArea())
1561 return d.currentTabWorkArea()->currentWorkArea();
1565 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1567 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1569 d.current_work_area_ = 0;
1571 Q_EMIT bufferViewChanged();
1575 // FIXME: I've no clue why this is here and why it accesses
1576 // theGuiApp()->currentView, which might be 0 (bug 6464).
1577 // See also 27525 (vfr).
1578 if (theGuiApp()->currentView() == this
1579 && theGuiApp()->currentView()->currentWorkArea() == wa)
1582 if (currentBufferView())
1583 cap::saveSelection(currentBufferView()->cursor());
1585 theGuiApp()->setCurrentView(this);
1586 d.current_work_area_ = wa;
1588 // We need to reset this now, because it will need to be
1589 // right if the tabWorkArea gets reset in the for loop. We
1590 // will change it back if we aren't in that case.
1591 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1592 d.current_main_work_area_ = wa;
1594 for (int i = 0; i != d.splitter_->count(); ++i) {
1595 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1596 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1597 << ", Current main wa: " << currentMainWorkArea());
1602 d.current_main_work_area_ = old_cmwa;
1604 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1605 on_currentWorkAreaChanged(wa);
1606 BufferView & bv = wa->bufferView();
1607 bv.cursor().fixIfBroken();
1609 wa->setUpdatesEnabled(true);
1610 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1614 void GuiView::removeWorkArea(GuiWorkArea * wa)
1616 LASSERT(wa, return);
1617 if (wa == d.current_work_area_) {
1619 disconnectBufferView();
1620 d.current_work_area_ = 0;
1621 d.current_main_work_area_ = 0;
1624 bool found_twa = false;
1625 for (int i = 0; i != d.splitter_->count(); ++i) {
1626 TabWorkArea * twa = d.tabWorkArea(i);
1627 if (twa->removeWorkArea(wa)) {
1628 // Found in this tab group, and deleted the GuiWorkArea.
1630 if (twa->count() != 0) {
1631 if (d.current_work_area_ == 0)
1632 // This means that we are closing the current GuiWorkArea, so
1633 // switch to the next GuiWorkArea in the found TabWorkArea.
1634 setCurrentWorkArea(twa->currentWorkArea());
1636 // No more WorkAreas in this tab group, so delete it.
1643 // It is not a tabbed work area (i.e., the search work area), so it
1644 // should be deleted by other means.
1645 LASSERT(found_twa, return);
1647 if (d.current_work_area_ == 0) {
1648 if (d.splitter_->count() != 0) {
1649 TabWorkArea * twa = d.currentTabWorkArea();
1650 setCurrentWorkArea(twa->currentWorkArea());
1652 // No more work areas, switch to the background widget.
1653 setCurrentWorkArea(0);
1659 LayoutBox * GuiView::getLayoutDialog() const
1665 void GuiView::updateLayoutList()
1668 d.layout_->updateContents(false);
1672 void GuiView::updateToolbars()
1674 ToolbarMap::iterator end = d.toolbars_.end();
1675 if (d.current_work_area_) {
1677 if (d.current_work_area_->bufferView().cursor().inMathed()
1678 && !d.current_work_area_->bufferView().cursor().inRegexped())
1679 context |= Toolbars::MATH;
1680 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1681 context |= Toolbars::TABLE;
1682 if (currentBufferView()->buffer().areChangesPresent()
1683 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1684 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1685 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1686 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1687 context |= Toolbars::REVIEW;
1688 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1689 context |= Toolbars::MATHMACROTEMPLATE;
1690 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1691 context |= Toolbars::IPA;
1692 if (command_execute_)
1693 context |= Toolbars::MINIBUFFER;
1694 if (minibuffer_focus_) {
1695 context |= Toolbars::MINIBUFFER_FOCUS;
1696 minibuffer_focus_ = false;
1699 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1700 it->second->update(context);
1702 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1703 it->second->update();
1707 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1709 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1710 LASSERT(newBuffer, return);
1712 GuiWorkArea * wa = workArea(*newBuffer);
1715 newBuffer->masterBuffer()->updateBuffer();
1717 wa = addWorkArea(*newBuffer);
1718 // scroll to the position when the BufferView was last closed
1719 if (lyxrc.use_lastfilepos) {
1720 LastFilePosSection::FilePos filepos =
1721 theSession().lastFilePos().load(newBuffer->fileName());
1722 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1725 //Disconnect the old buffer...there's no new one.
1728 connectBuffer(*newBuffer);
1729 connectBufferView(wa->bufferView());
1731 setCurrentWorkArea(wa);
1735 void GuiView::connectBuffer(Buffer & buf)
1737 buf.setGuiDelegate(this);
1741 void GuiView::disconnectBuffer()
1743 if (d.current_work_area_)
1744 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1748 void GuiView::connectBufferView(BufferView & bv)
1750 bv.setGuiDelegate(this);
1754 void GuiView::disconnectBufferView()
1756 if (d.current_work_area_)
1757 d.current_work_area_->bufferView().setGuiDelegate(0);
1761 void GuiView::errors(string const & error_type, bool from_master)
1763 BufferView const * const bv = currentBufferView();
1767 ErrorList const & el = from_master ?
1768 bv->buffer().masterBuffer()->errorList(error_type) :
1769 bv->buffer().errorList(error_type);
1774 string err = error_type;
1776 err = "from_master|" + error_type;
1777 showDialog("errorlist", err);
1781 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1783 d.toc_models_.updateItem(toqstr(type), dit);
1787 void GuiView::structureChanged()
1789 // This is called from the Buffer, which has no way to ensure that cursors
1790 // in BufferView remain valid.
1791 if (documentBufferView())
1792 documentBufferView()->cursor().sanitize();
1793 // FIXME: This is slightly expensive, though less than the tocBackend update
1794 // (#9880). This also resets the view in the Toc Widget (#6675).
1795 d.toc_models_.reset(documentBufferView());
1796 // Navigator needs more than a simple update in this case. It needs to be
1798 updateDialog("toc", "");
1802 void GuiView::updateDialog(string const & name, string const & sdata)
1804 if (!isDialogVisible(name))
1807 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1808 if (it == d.dialogs_.end())
1811 Dialog * const dialog = it->second.get();
1812 if (dialog->isVisibleView())
1813 dialog->initialiseParams(sdata);
1817 BufferView * GuiView::documentBufferView()
1819 return currentMainWorkArea()
1820 ? ¤tMainWorkArea()->bufferView()
1825 BufferView const * GuiView::documentBufferView() const
1827 return currentMainWorkArea()
1828 ? ¤tMainWorkArea()->bufferView()
1833 BufferView * GuiView::currentBufferView()
1835 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1839 BufferView const * GuiView::currentBufferView() const
1841 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1845 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1846 Buffer const * orig, Buffer * clone)
1848 bool const success = clone->autoSave();
1850 busyBuffers.remove(orig);
1852 ? _("Automatic save done.")
1853 : _("Automatic save failed!");
1857 void GuiView::autoSave()
1859 LYXERR(Debug::INFO, "Running autoSave()");
1861 Buffer * buffer = documentBufferView()
1862 ? &documentBufferView()->buffer() : 0;
1864 resetAutosaveTimers();
1868 GuiViewPrivate::busyBuffers.insert(buffer);
1869 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1870 buffer, buffer->cloneBufferOnly());
1871 d.autosave_watcher_.setFuture(f);
1872 resetAutosaveTimers();
1876 void GuiView::resetAutosaveTimers()
1879 d.autosave_timeout_.restart();
1883 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1886 Buffer * buf = currentBufferView()
1887 ? ¤tBufferView()->buffer() : 0;
1888 Buffer * doc_buffer = documentBufferView()
1889 ? &(documentBufferView()->buffer()) : 0;
1892 /* In LyX/Mac, when a dialog is open, the menus of the
1893 application can still be accessed without giving focus to
1894 the main window. In this case, we want to disable the menu
1895 entries that are buffer-related.
1896 This code must not be used on Linux and Windows, since it
1897 would disable buffer-related entries when hovering over the
1898 menu (see bug #9574).
1900 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1906 // Check whether we need a buffer
1907 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1908 // no, exit directly
1909 flag.message(from_utf8(N_("Command not allowed with"
1910 "out any document open")));
1911 flag.setEnabled(false);
1915 if (cmd.origin() == FuncRequest::TOC) {
1916 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1917 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1918 flag.setEnabled(false);
1922 switch(cmd.action()) {
1923 case LFUN_BUFFER_IMPORT:
1926 case LFUN_MASTER_BUFFER_EXPORT:
1928 && (doc_buffer->parent() != 0
1929 || doc_buffer->hasChildren())
1930 && !d.processing_thread_watcher_.isRunning()
1931 // this launches a dialog, which would be in the wrong Buffer
1932 && !(::lyx::operator==(cmd.argument(), "custom"));
1935 case LFUN_MASTER_BUFFER_UPDATE:
1936 case LFUN_MASTER_BUFFER_VIEW:
1938 && (doc_buffer->parent() != 0
1939 || doc_buffer->hasChildren())
1940 && !d.processing_thread_watcher_.isRunning();
1943 case LFUN_BUFFER_UPDATE:
1944 case LFUN_BUFFER_VIEW: {
1945 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1949 string format = to_utf8(cmd.argument());
1950 if (cmd.argument().empty())
1951 format = doc_buffer->params().getDefaultOutputFormat();
1952 enable = doc_buffer->params().isExportable(format, true);
1956 case LFUN_BUFFER_RELOAD:
1957 enable = doc_buffer && !doc_buffer->isUnnamed()
1958 && doc_buffer->fileName().exists()
1959 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1962 case LFUN_BUFFER_RESET_EXPORT:
1963 enable = doc_buffer != 0;
1966 case LFUN_BUFFER_CHILD_OPEN:
1967 enable = doc_buffer != 0;
1970 case LFUN_MASTER_BUFFER_FORALL: {
1971 if (doc_buffer == 0) {
1972 flag.message(from_utf8(N_("Command not allowed without a buffer open")));
1976 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
1977 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
1978 flag.message(from_utf8(N_("Invalid argument of master-buffer-forall")));
1983 for (Buffer * buf : doc_buffer->allRelatives()) {
1984 GuiWorkArea * wa = workArea(*buf);
1987 if (wa->bufferView().getStatus(cmdToPass, flag)) {
1988 enable = flag.enabled();
1995 case LFUN_BUFFER_WRITE:
1996 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1999 //FIXME: This LFUN should be moved to GuiApplication.
2000 case LFUN_BUFFER_WRITE_ALL: {
2001 // We enable the command only if there are some modified buffers
2002 Buffer * first = theBufferList().first();
2007 // We cannot use a for loop as the buffer list is a cycle.
2009 if (!b->isClean()) {
2013 b = theBufferList().next(b);
2014 } while (b != first);
2018 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
2019 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2022 case LFUN_BUFFER_EXPORT: {
2023 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2027 return doc_buffer->getStatus(cmd, flag);
2030 case LFUN_BUFFER_EXPORT_AS:
2031 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2036 case LFUN_BUFFER_WRITE_AS:
2037 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2038 enable = doc_buffer != 0;
2041 case LFUN_EXPORT_CANCEL:
2042 enable = d.processing_thread_watcher_.isRunning();
2045 case LFUN_BUFFER_CLOSE:
2046 case LFUN_VIEW_CLOSE:
2047 enable = doc_buffer != 0;
2050 case LFUN_BUFFER_CLOSE_ALL:
2051 enable = theBufferList().last() != theBufferList().first();
2054 case LFUN_BUFFER_CHKTEX: {
2055 // hide if we have no checktex command
2056 if (lyxrc.chktex_command.empty()) {
2057 flag.setUnknown(true);
2061 if (!doc_buffer || !doc_buffer->params().isLatex()
2062 || d.processing_thread_watcher_.isRunning()) {
2063 // grey out, don't hide
2071 case LFUN_VIEW_SPLIT:
2072 if (cmd.getArg(0) == "vertical")
2073 enable = doc_buffer && (d.splitter_->count() == 1 ||
2074 d.splitter_->orientation() == Qt::Vertical);
2076 enable = doc_buffer && (d.splitter_->count() == 1 ||
2077 d.splitter_->orientation() == Qt::Horizontal);
2080 case LFUN_TAB_GROUP_CLOSE:
2081 enable = d.tabWorkAreaCount() > 1;
2084 case LFUN_DEVEL_MODE_TOGGLE:
2085 flag.setOnOff(devel_mode_);
2088 case LFUN_TOOLBAR_TOGGLE: {
2089 string const name = cmd.getArg(0);
2090 if (GuiToolbar * t = toolbar(name))
2091 flag.setOnOff(t->isVisible());
2094 docstring const msg =
2095 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2101 case LFUN_TOOLBAR_MOVABLE: {
2102 string const name = cmd.getArg(0);
2103 // use negation since locked == !movable
2105 // toolbar name * locks all toolbars
2106 flag.setOnOff(!toolbarsMovable_);
2107 else if (GuiToolbar * t = toolbar(name))
2108 flag.setOnOff(!(t->isMovable()));
2111 docstring const msg =
2112 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2118 case LFUN_ICON_SIZE:
2119 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2122 case LFUN_DROP_LAYOUTS_CHOICE:
2126 case LFUN_UI_TOGGLE:
2127 flag.setOnOff(isFullScreen());
2130 case LFUN_DIALOG_DISCONNECT_INSET:
2133 case LFUN_DIALOG_HIDE:
2134 // FIXME: should we check if the dialog is shown?
2137 case LFUN_DIALOG_TOGGLE:
2138 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2141 case LFUN_DIALOG_SHOW: {
2142 string const name = cmd.getArg(0);
2144 enable = name == "aboutlyx"
2145 || name == "file" //FIXME: should be removed.
2146 || name == "lyxfiles"
2148 || name == "texinfo"
2149 || name == "progress"
2150 || name == "compare";
2151 else if (name == "character" || name == "symbols"
2152 || name == "mathdelimiter" || name == "mathmatrix") {
2153 if (!buf || buf->isReadonly())
2156 Cursor const & cur = currentBufferView()->cursor();
2157 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2160 else if (name == "latexlog")
2161 enable = FileName(doc_buffer->logName()).isReadableFile();
2162 else if (name == "spellchecker")
2163 enable = theSpellChecker()
2164 && !doc_buffer->isReadonly()
2165 && !doc_buffer->text().empty();
2166 else if (name == "vclog")
2167 enable = doc_buffer->lyxvc().inUse();
2171 case LFUN_DIALOG_UPDATE: {
2172 string const name = cmd.getArg(0);
2174 enable = name == "prefs";
2178 case LFUN_COMMAND_EXECUTE:
2180 case LFUN_MENU_OPEN:
2181 // Nothing to check.
2184 case LFUN_COMPLETION_INLINE:
2185 if (!d.current_work_area_
2186 || !d.current_work_area_->completer().inlinePossible(
2187 currentBufferView()->cursor()))
2191 case LFUN_COMPLETION_POPUP:
2192 if (!d.current_work_area_
2193 || !d.current_work_area_->completer().popupPossible(
2194 currentBufferView()->cursor()))
2199 if (!d.current_work_area_
2200 || !d.current_work_area_->completer().inlinePossible(
2201 currentBufferView()->cursor()))
2205 case LFUN_COMPLETION_ACCEPT:
2206 if (!d.current_work_area_
2207 || (!d.current_work_area_->completer().popupVisible()
2208 && !d.current_work_area_->completer().inlineVisible()
2209 && !d.current_work_area_->completer().completionAvailable()))
2213 case LFUN_COMPLETION_CANCEL:
2214 if (!d.current_work_area_
2215 || (!d.current_work_area_->completer().popupVisible()
2216 && !d.current_work_area_->completer().inlineVisible()))
2220 case LFUN_BUFFER_ZOOM_OUT:
2221 case LFUN_BUFFER_ZOOM_IN: {
2222 // only diff between these two is that the default for ZOOM_OUT
2224 bool const neg_zoom =
2225 convert<int>(cmd.argument()) < 0 ||
2226 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2227 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2228 docstring const msg =
2229 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2233 enable = doc_buffer;
2237 case LFUN_BUFFER_ZOOM: {
2238 bool const less_than_min_zoom =
2239 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2240 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2241 docstring const msg =
2242 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2247 enable = doc_buffer;
2251 case LFUN_BUFFER_MOVE_NEXT:
2252 case LFUN_BUFFER_MOVE_PREVIOUS:
2253 // we do not cycle when moving
2254 case LFUN_BUFFER_NEXT:
2255 case LFUN_BUFFER_PREVIOUS:
2256 // because we cycle, it doesn't matter whether on first or last
2257 enable = (d.currentTabWorkArea()->count() > 1);
2259 case LFUN_BUFFER_SWITCH:
2260 // toggle on the current buffer, but do not toggle off
2261 // the other ones (is that a good idea?)
2263 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2264 flag.setOnOff(true);
2267 case LFUN_VC_REGISTER:
2268 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2270 case LFUN_VC_RENAME:
2271 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2274 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2276 case LFUN_VC_CHECK_IN:
2277 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2279 case LFUN_VC_CHECK_OUT:
2280 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2282 case LFUN_VC_LOCKING_TOGGLE:
2283 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2284 && doc_buffer->lyxvc().lockingToggleEnabled();
2285 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2287 case LFUN_VC_REVERT:
2288 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2289 && !doc_buffer->hasReadonlyFlag();
2291 case LFUN_VC_UNDO_LAST:
2292 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2294 case LFUN_VC_REPO_UPDATE:
2295 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2297 case LFUN_VC_COMMAND: {
2298 if (cmd.argument().empty())
2300 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2304 case LFUN_VC_COMPARE:
2305 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2308 case LFUN_SERVER_GOTO_FILE_ROW:
2309 case LFUN_LYX_ACTIVATE:
2311 case LFUN_FORWARD_SEARCH:
2312 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2315 case LFUN_FILE_INSERT_PLAINTEXT:
2316 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2317 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2320 case LFUN_SPELLING_CONTINUOUSLY:
2321 flag.setOnOff(lyxrc.spellcheck_continuously);
2329 flag.setEnabled(false);
2335 static FileName selectTemplateFile()
2337 FileDialog dlg(qt_("Select template file"));
2338 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2339 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2341 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2342 QStringList(qt_("LyX Documents (*.lyx)")));
2344 if (result.first == FileDialog::Later)
2346 if (result.second.isEmpty())
2348 return FileName(fromqstr(result.second));
2352 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2356 Buffer * newBuffer = 0;
2358 newBuffer = checkAndLoadLyXFile(filename);
2359 } catch (ExceptionMessage const & e) {
2366 message(_("Document not loaded."));
2370 setBuffer(newBuffer);
2371 newBuffer->errors("Parse");
2374 theSession().lastFiles().add(filename);
2375 theSession().writeFile();
2382 void GuiView::openDocument(string const & fname)
2384 string initpath = lyxrc.document_path;
2386 if (documentBufferView()) {
2387 string const trypath = documentBufferView()->buffer().filePath();
2388 // If directory is writeable, use this as default.
2389 if (FileName(trypath).isDirWritable())
2395 if (fname.empty()) {
2396 FileDialog dlg(qt_("Select document to open"));
2397 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2398 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2400 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2401 FileDialog::Result result =
2402 dlg.open(toqstr(initpath), filter);
2404 if (result.first == FileDialog::Later)
2407 filename = fromqstr(result.second);
2409 // check selected filename
2410 if (filename.empty()) {
2411 message(_("Canceled."));
2417 // get absolute path of file and add ".lyx" to the filename if
2419 FileName const fullname =
2420 fileSearch(string(), filename, "lyx", support::may_not_exist);
2421 if (!fullname.empty())
2422 filename = fullname.absFileName();
2424 if (!fullname.onlyPath().isDirectory()) {
2425 Alert::warning(_("Invalid filename"),
2426 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2427 from_utf8(fullname.absFileName())));
2431 // if the file doesn't exist and isn't already open (bug 6645),
2432 // let the user create one
2433 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2434 !LyXVC::file_not_found_hook(fullname)) {
2435 // the user specifically chose this name. Believe him.
2436 Buffer * const b = newFile(filename, string(), true);
2442 docstring const disp_fn = makeDisplayPath(filename);
2443 message(bformat(_("Opening document %1$s..."), disp_fn));
2446 Buffer * buf = loadDocument(fullname);
2448 str2 = bformat(_("Document %1$s opened."), disp_fn);
2449 if (buf->lyxvc().inUse())
2450 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2451 " " + _("Version control detected.");
2453 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2458 // FIXME: clean that
2459 static bool import(GuiView * lv, FileName const & filename,
2460 string const & format, ErrorList & errorList)
2462 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2464 string loader_format;
2465 vector<string> loaders = theConverters().loaders();
2466 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2467 vector<string>::const_iterator it = loaders.begin();
2468 vector<string>::const_iterator en = loaders.end();
2469 for (; it != en; ++it) {
2470 if (!theConverters().isReachable(format, *it))
2473 string const tofile =
2474 support::changeExtension(filename.absFileName(),
2475 theFormats().extension(*it));
2476 if (theConverters().convert(0, filename, FileName(tofile),
2477 filename, format, *it, errorList) != Converters::SUCCESS)
2479 loader_format = *it;
2482 if (loader_format.empty()) {
2483 frontend::Alert::error(_("Couldn't import file"),
2484 bformat(_("No information for importing the format %1$s."),
2485 theFormats().prettyName(format)));
2489 loader_format = format;
2491 if (loader_format == "lyx") {
2492 Buffer * buf = lv->loadDocument(lyxfile);
2496 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2500 bool as_paragraphs = loader_format == "textparagraph";
2501 string filename2 = (loader_format == format) ? filename.absFileName()
2502 : support::changeExtension(filename.absFileName(),
2503 theFormats().extension(loader_format));
2504 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2506 guiApp->setCurrentView(lv);
2507 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2514 void GuiView::importDocument(string const & argument)
2517 string filename = split(argument, format, ' ');
2519 LYXERR(Debug::INFO, format << " file: " << filename);
2521 // need user interaction
2522 if (filename.empty()) {
2523 string initpath = lyxrc.document_path;
2524 if (documentBufferView()) {
2525 string const trypath = documentBufferView()->buffer().filePath();
2526 // If directory is writeable, use this as default.
2527 if (FileName(trypath).isDirWritable())
2531 docstring const text = bformat(_("Select %1$s file to import"),
2532 theFormats().prettyName(format));
2534 FileDialog dlg(toqstr(text));
2535 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2536 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2538 docstring filter = theFormats().prettyName(format);
2541 filter += from_utf8(theFormats().extensions(format));
2544 FileDialog::Result result =
2545 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2547 if (result.first == FileDialog::Later)
2550 filename = fromqstr(result.second);
2552 // check selected filename
2553 if (filename.empty())
2554 message(_("Canceled."));
2557 if (filename.empty())
2560 // get absolute path of file
2561 FileName const fullname(support::makeAbsPath(filename));
2563 // Can happen if the user entered a path into the dialog
2565 if (fullname.onlyFileName().empty()) {
2566 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2567 "Aborting import."),
2568 from_utf8(fullname.absFileName()));
2569 frontend::Alert::error(_("File name error"), msg);
2570 message(_("Canceled."));
2575 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2577 // Check if the document already is open
2578 Buffer * buf = theBufferList().getBuffer(lyxfile);
2581 if (!closeBuffer()) {
2582 message(_("Canceled."));
2587 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2589 // if the file exists already, and we didn't do
2590 // -i lyx thefile.lyx, warn
2591 if (lyxfile.exists() && fullname != lyxfile) {
2593 docstring text = bformat(_("The document %1$s already exists.\n\n"
2594 "Do you want to overwrite that document?"), displaypath);
2595 int const ret = Alert::prompt(_("Overwrite document?"),
2596 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2599 message(_("Canceled."));
2604 message(bformat(_("Importing %1$s..."), displaypath));
2605 ErrorList errorList;
2606 if (import(this, fullname, format, errorList))
2607 message(_("imported."));
2609 message(_("file not imported!"));
2611 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2615 void GuiView::newDocument(string const & filename, string templatefile,
2618 FileName initpath(lyxrc.document_path);
2619 if (documentBufferView()) {
2620 FileName const trypath(documentBufferView()->buffer().filePath());
2621 // If directory is writeable, use this as default.
2622 if (trypath.isDirWritable())
2626 if (from_template) {
2627 if (templatefile.empty())
2628 templatefile = selectTemplateFile().absFileName();
2629 if (templatefile.empty())
2634 if (filename.empty())
2635 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2637 b = newFile(filename, templatefile, true);
2642 // If no new document could be created, it is unsure
2643 // whether there is a valid BufferView.
2644 if (currentBufferView())
2645 // Ensure the cursor is correctly positioned on screen.
2646 currentBufferView()->showCursor();
2650 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2652 BufferView * bv = documentBufferView();
2657 FileName filename(to_utf8(fname));
2658 if (filename.empty()) {
2659 // Launch a file browser
2661 string initpath = lyxrc.document_path;
2662 string const trypath = bv->buffer().filePath();
2663 // If directory is writeable, use this as default.
2664 if (FileName(trypath).isDirWritable())
2668 FileDialog dlg(qt_("Select LyX document to insert"));
2669 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2670 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2672 FileDialog::Result result = dlg.open(toqstr(initpath),
2673 QStringList(qt_("LyX Documents (*.lyx)")));
2675 if (result.first == FileDialog::Later)
2679 filename.set(fromqstr(result.second));
2681 // check selected filename
2682 if (filename.empty()) {
2683 // emit message signal.
2684 message(_("Canceled."));
2689 bv->insertLyXFile(filename, ignorelang);
2690 bv->buffer().errors("Parse");
2694 string const GuiView::getTemplatesPath(Buffer & b)
2696 // We start off with the user's templates path
2697 string result = addPath(package().user_support().absFileName(), "templates");
2698 // Check for the document language
2699 string const langcode = b.params().language->code();
2700 string const shortcode = langcode.substr(0, 2);
2701 if (!langcode.empty() && shortcode != "en") {
2702 string subpath = addPath(result, shortcode);
2703 string subpath_long = addPath(result, langcode);
2704 // If we have a subdirectory for the language already,
2706 FileName sp = FileName(subpath);
2707 if (sp.isDirectory())
2709 else if (FileName(subpath_long).isDirectory())
2710 result = subpath_long;
2712 // Ask whether we should create such a subdirectory
2713 docstring const text =
2714 bformat(_("It is suggested to save the template in a subdirectory\n"
2715 "appropriate to the document language (%1$s).\n"
2716 "This subdirectory does not exists yet.\n"
2717 "Do you want to create it?"),
2718 _(b.params().language->display()));
2719 if (Alert::prompt(_("Create Language Directory?"),
2720 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2721 // If the user agreed, we try to create it and report if this failed.
2722 if (!sp.createDirectory(0777))
2723 Alert::error(_("Subdirectory creation failed!"),
2724 _("Could not create subdirectory.\n"
2725 "The template will be saved in the parent directory."));
2731 // Do we have a layout category?
2732 string const cat = b.params().baseClass() ?
2733 b.params().baseClass()->category()
2736 string subpath = addPath(result, cat);
2737 // If we have a subdirectory for the category already,
2739 FileName sp = FileName(subpath);
2740 if (sp.isDirectory())
2743 // Ask whether we should create such a subdirectory
2744 docstring const text =
2745 bformat(_("It is suggested to save the template in a subdirectory\n"
2746 "appropriate to the layout category (%1$s).\n"
2747 "This subdirectory does not exists yet.\n"
2748 "Do you want to create it?"),
2750 if (Alert::prompt(_("Create Category Directory?"),
2751 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2752 // If the user agreed, we try to create it and report if this failed.
2753 if (!sp.createDirectory(0777))
2754 Alert::error(_("Subdirectory creation failed!"),
2755 _("Could not create subdirectory.\n"
2756 "The template will be saved in the parent directory."));
2766 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2768 FileName fname = b.fileName();
2769 FileName const oldname = fname;
2770 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2772 if (!newname.empty()) {
2775 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2777 fname = support::makeAbsPath(to_utf8(newname),
2778 oldname.onlyPath().absFileName());
2780 // Switch to this Buffer.
2783 // No argument? Ask user through dialog.
2785 QString const title = as_template ? qt_("Choose a filename to save template as")
2786 : qt_("Choose a filename to save document as");
2787 FileDialog dlg(title);
2788 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2789 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2791 if (!isLyXFileName(fname.absFileName()))
2792 fname.changeExtension(".lyx");
2794 string const path = as_template ?
2796 : fname.onlyPath().absFileName();
2797 FileDialog::Result result =
2798 dlg.save(toqstr(path),
2799 QStringList(qt_("LyX Documents (*.lyx)")),
2800 toqstr(fname.onlyFileName()));
2802 if (result.first == FileDialog::Later)
2805 fname.set(fromqstr(result.second));
2810 if (!isLyXFileName(fname.absFileName()))
2811 fname.changeExtension(".lyx");
2814 // fname is now the new Buffer location.
2816 // if there is already a Buffer open with this name, we do not want
2817 // to have another one. (the second test makes sure we're not just
2818 // trying to overwrite ourselves, which is fine.)
2819 if (theBufferList().exists(fname) && fname != oldname
2820 && theBufferList().getBuffer(fname) != &b) {
2821 docstring const text =
2822 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2823 "Please close it before attempting to overwrite it.\n"
2824 "Do you want to choose a new filename?"),
2825 from_utf8(fname.absFileName()));
2826 int const ret = Alert::prompt(_("Chosen File Already Open"),
2827 text, 0, 1, _("&Rename"), _("&Cancel"));
2829 case 0: return renameBuffer(b, docstring(), kind);
2830 case 1: return false;
2835 bool const existsLocal = fname.exists();
2836 bool const existsInVC = LyXVC::fileInVC(fname);
2837 if (existsLocal || existsInVC) {
2838 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2839 if (kind != LV_WRITE_AS && existsInVC) {
2840 // renaming to a name that is already in VC
2842 docstring text = bformat(_("The document %1$s "
2843 "is already registered.\n\n"
2844 "Do you want to choose a new name?"),
2846 docstring const title = (kind == LV_VC_RENAME) ?
2847 _("Rename document?") : _("Copy document?");
2848 docstring const button = (kind == LV_VC_RENAME) ?
2849 _("&Rename") : _("&Copy");
2850 int const ret = Alert::prompt(title, text, 0, 1,
2851 button, _("&Cancel"));
2853 case 0: return renameBuffer(b, docstring(), kind);
2854 case 1: return false;
2859 docstring text = bformat(_("The document %1$s "
2860 "already exists.\n\n"
2861 "Do you want to overwrite that document?"),
2863 int const ret = Alert::prompt(_("Overwrite document?"),
2864 text, 0, 2, _("&Overwrite"),
2865 _("&Rename"), _("&Cancel"));
2868 case 1: return renameBuffer(b, docstring(), kind);
2869 case 2: return false;
2875 case LV_VC_RENAME: {
2876 string msg = b.lyxvc().rename(fname);
2879 message(from_utf8(msg));
2883 string msg = b.lyxvc().copy(fname);
2886 message(from_utf8(msg));
2890 case LV_WRITE_AS_TEMPLATE:
2893 // LyXVC created the file already in case of LV_VC_RENAME or
2894 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2895 // relative paths of included stuff right if we moved e.g. from
2896 // /a/b.lyx to /a/c/b.lyx.
2898 bool const saved = saveBuffer(b, fname);
2905 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2907 FileName fname = b.fileName();
2909 FileDialog dlg(qt_("Choose a filename to export the document as"));
2910 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2913 QString const anyformat = qt_("Guess from extension (*.*)");
2916 vector<Format const *> export_formats;
2917 for (Format const & f : theFormats())
2918 if (f.documentFormat())
2919 export_formats.push_back(&f);
2920 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2921 map<QString, string> fmap;
2924 for (Format const * f : export_formats) {
2925 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2926 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2928 from_ascii(f->extension())));
2929 types << loc_filter;
2930 fmap[loc_filter] = f->name();
2931 if (from_ascii(f->name()) == iformat) {
2932 filter = loc_filter;
2933 ext = f->extension();
2936 string ofname = fname.onlyFileName();
2938 ofname = support::changeExtension(ofname, ext);
2939 FileDialog::Result result =
2940 dlg.save(toqstr(fname.onlyPath().absFileName()),
2944 if (result.first != FileDialog::Chosen)
2948 fname.set(fromqstr(result.second));
2949 if (filter == anyformat)
2950 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2952 fmt_name = fmap[filter];
2953 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2954 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2956 if (fmt_name.empty() || fname.empty())
2959 // fname is now the new Buffer location.
2960 if (FileName(fname).exists()) {
2961 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2962 docstring text = bformat(_("The document %1$s already "
2963 "exists.\n\nDo you want to "
2964 "overwrite that document?"),
2966 int const ret = Alert::prompt(_("Overwrite document?"),
2967 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2970 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2971 case 2: return false;
2975 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2978 return dr.dispatched();
2982 bool GuiView::saveBuffer(Buffer & b)
2984 return saveBuffer(b, FileName());
2988 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2990 if (workArea(b) && workArea(b)->inDialogMode())
2993 if (fn.empty() && b.isUnnamed())
2994 return renameBuffer(b, docstring());
2996 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2998 theSession().lastFiles().add(b.fileName());
2999 theSession().writeFile();
3003 // Switch to this Buffer.
3006 // FIXME: we don't tell the user *WHY* the save failed !!
3007 docstring const file = makeDisplayPath(b.absFileName(), 30);
3008 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
3009 "Do you want to rename the document and "
3010 "try again?"), file);
3011 int const ret = Alert::prompt(_("Rename and save?"),
3012 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
3015 if (!renameBuffer(b, docstring()))
3024 return saveBuffer(b, fn);
3028 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3030 return closeWorkArea(wa, false);
3034 // We only want to close the buffer if it is not visible in other workareas
3035 // of the same view, nor in other views, and if this is not a child
3036 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3038 Buffer & buf = wa->bufferView().buffer();
3040 bool last_wa = d.countWorkAreasOf(buf) == 1
3041 && !inOtherView(buf) && !buf.parent();
3043 bool close_buffer = last_wa;
3046 if (lyxrc.close_buffer_with_last_view == "yes")
3048 else if (lyxrc.close_buffer_with_last_view == "no")
3049 close_buffer = false;
3052 if (buf.isUnnamed())
3053 file = from_utf8(buf.fileName().onlyFileName());
3055 file = buf.fileName().displayName(30);
3056 docstring const text = bformat(
3057 _("Last view on document %1$s is being closed.\n"
3058 "Would you like to close or hide the document?\n"
3060 "Hidden documents can be displayed back through\n"
3061 "the menu: View->Hidden->...\n"
3063 "To remove this question, set your preference in:\n"
3064 " Tools->Preferences->Look&Feel->UserInterface\n"
3066 int ret = Alert::prompt(_("Close or hide document?"),
3067 text, 0, 1, _("&Close"), _("&Hide"));
3068 close_buffer = (ret == 0);
3072 return closeWorkArea(wa, close_buffer);
3076 bool GuiView::closeBuffer()
3078 GuiWorkArea * wa = currentMainWorkArea();
3079 // coverity complained about this
3080 // it seems unnecessary, but perhaps is worth the check
3081 LASSERT(wa, return false);
3083 setCurrentWorkArea(wa);
3084 Buffer & buf = wa->bufferView().buffer();
3085 return closeWorkArea(wa, !buf.parent());
3089 void GuiView::writeSession() const {
3090 GuiWorkArea const * active_wa = currentMainWorkArea();
3091 for (int i = 0; i < d.splitter_->count(); ++i) {
3092 TabWorkArea * twa = d.tabWorkArea(i);
3093 for (int j = 0; j < twa->count(); ++j) {
3094 GuiWorkArea * wa = twa->workArea(j);
3095 Buffer & buf = wa->bufferView().buffer();
3096 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3102 bool GuiView::closeBufferAll()
3104 // Close the workareas in all other views
3105 QList<int> const ids = guiApp->viewIds();
3106 for (int i = 0; i != ids.size(); ++i) {
3107 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3111 // Close our own workareas
3112 if (!closeWorkAreaAll())
3115 // Now close the hidden buffers. We prevent hidden buffers from being
3116 // dirty, so we can just close them.
3117 theBufferList().closeAll();
3122 bool GuiView::closeWorkAreaAll()
3124 setCurrentWorkArea(currentMainWorkArea());
3126 // We might be in a situation that there is still a tabWorkArea, but
3127 // there are no tabs anymore. This can happen when we get here after a
3128 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3129 // many TabWorkArea's have no documents anymore.
3132 // We have to call count() each time, because it can happen that
3133 // more than one splitter will disappear in one iteration (bug 5998).
3134 while (d.splitter_->count() > empty_twa) {
3135 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3137 if (twa->count() == 0)
3140 setCurrentWorkArea(twa->currentWorkArea());
3141 if (!closeTabWorkArea(twa))
3149 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3154 Buffer & buf = wa->bufferView().buffer();
3156 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3157 Alert::warning(_("Close document"),
3158 _("Document could not be closed because it is being processed by LyX."));
3163 return closeBuffer(buf);
3165 if (!inMultiTabs(wa))
3166 if (!saveBufferIfNeeded(buf, true))
3174 bool GuiView::closeBuffer(Buffer & buf)
3176 bool success = true;
3177 ListOfBuffers clist = buf.getChildren();
3178 ListOfBuffers::const_iterator it = clist.begin();
3179 ListOfBuffers::const_iterator const bend = clist.end();
3180 for (; it != bend; ++it) {
3181 Buffer * child_buf = *it;
3182 if (theBufferList().isOthersChild(&buf, child_buf)) {
3183 child_buf->setParent(0);
3187 // FIXME: should we look in other tabworkareas?
3188 // ANSWER: I don't think so. I've tested, and if the child is
3189 // open in some other window, it closes without a problem.
3190 GuiWorkArea * child_wa = workArea(*child_buf);
3193 // If we are in a close_event all children will be closed in some time,
3194 // so no need to do it here. This will ensure that the children end up
3195 // in the session file in the correct order. If we close the master
3196 // buffer, we can close or release the child buffers here too.
3198 success = closeWorkArea(child_wa, true);
3202 // In this case the child buffer is open but hidden.
3203 // Even in this case, children can be dirty (e.g.,
3204 // after a label change in the master, see #11405).
3205 // Therefore, check this
3206 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3207 // If we are in a close_event all children will be closed in some time,
3208 // so no need to do it here. This will ensure that the children end up
3209 // in the session file in the correct order. If we close the master
3210 // buffer, we can close or release the child buffers here too.
3212 // Save dirty buffers also if closing_!
3213 if (saveBufferIfNeeded(*child_buf, false)) {
3214 child_buf->removeAutosaveFile();
3215 theBufferList().release(child_buf);
3217 // Saving of dirty children has been cancelled.
3218 // Cancel the whole process.
3225 // goto bookmark to update bookmark pit.
3226 // FIXME: we should update only the bookmarks related to this buffer!
3227 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3228 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3229 guiApp->gotoBookmark(i+1, false, false);
3231 if (saveBufferIfNeeded(buf, false)) {
3232 buf.removeAutosaveFile();
3233 theBufferList().release(&buf);
3237 // open all children again to avoid a crash because of dangling
3238 // pointers (bug 6603)
3244 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3246 while (twa == d.currentTabWorkArea()) {
3247 twa->setCurrentIndex(twa->count() - 1);
3249 GuiWorkArea * wa = twa->currentWorkArea();
3250 Buffer & b = wa->bufferView().buffer();
3252 // We only want to close the buffer if the same buffer is not visible
3253 // in another view, and if this is not a child and if we are closing
3254 // a view (not a tabgroup).
3255 bool const close_buffer =
3256 !inOtherView(b) && !b.parent() && closing_;
3258 if (!closeWorkArea(wa, close_buffer))
3265 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3267 if (buf.isClean() || buf.paragraphs().empty())
3270 // Switch to this Buffer.
3276 if (buf.isUnnamed()) {
3277 file = from_utf8(buf.fileName().onlyFileName());
3280 FileName filename = buf.fileName();
3282 file = filename.displayName(30);
3283 exists = filename.exists();
3286 // Bring this window to top before asking questions.
3291 if (hiding && buf.isUnnamed()) {
3292 docstring const text = bformat(_("The document %1$s has not been "
3293 "saved yet.\n\nDo you want to save "
3294 "the document?"), file);
3295 ret = Alert::prompt(_("Save new document?"),
3296 text, 0, 1, _("&Save"), _("&Cancel"));
3300 docstring const text = exists ?
3301 bformat(_("The document %1$s has unsaved changes."
3302 "\n\nDo you want to save the document or "
3303 "discard the changes?"), file) :
3304 bformat(_("The document %1$s has not been saved yet."
3305 "\n\nDo you want to save the document or "
3306 "discard it entirely?"), file);
3307 docstring const title = exists ?
3308 _("Save changed document?") : _("Save document?");
3309 ret = Alert::prompt(title, text, 0, 2,
3310 _("&Save"), _("&Discard"), _("&Cancel"));
3315 if (!saveBuffer(buf))
3319 // If we crash after this we could have no autosave file
3320 // but I guess this is really improbable (Jug).
3321 // Sometimes improbable things happen:
3322 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3323 // buf.removeAutosaveFile();
3325 // revert all changes
3336 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3338 Buffer & buf = wa->bufferView().buffer();
3340 for (int i = 0; i != d.splitter_->count(); ++i) {
3341 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3342 if (wa_ && wa_ != wa)
3345 return inOtherView(buf);
3349 bool GuiView::inOtherView(Buffer & buf)
3351 QList<int> const ids = guiApp->viewIds();
3353 for (int i = 0; i != ids.size(); ++i) {
3357 if (guiApp->view(ids[i]).workArea(buf))
3364 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3366 if (!documentBufferView())
3369 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3370 Buffer * const curbuf = &documentBufferView()->buffer();
3371 int nwa = twa->count();
3372 for (int i = 0; i < nwa; ++i) {
3373 if (&workArea(i)->bufferView().buffer() == curbuf) {
3375 if (np == NEXTBUFFER)
3376 next_index = (i == nwa - 1 ? 0 : i + 1);
3378 next_index = (i == 0 ? nwa - 1 : i - 1);
3380 twa->moveTab(i, next_index);
3382 setBuffer(&workArea(next_index)->bufferView().buffer());
3390 /// make sure the document is saved
3391 static bool ensureBufferClean(Buffer * buffer)
3393 LASSERT(buffer, return false);
3394 if (buffer->isClean() && !buffer->isUnnamed())
3397 docstring const file = buffer->fileName().displayName(30);
3400 if (!buffer->isUnnamed()) {
3401 text = bformat(_("The document %1$s has unsaved "
3402 "changes.\n\nDo you want to save "
3403 "the document?"), file);
3404 title = _("Save changed document?");
3407 text = bformat(_("The document %1$s has not been "
3408 "saved yet.\n\nDo you want to save "
3409 "the document?"), file);
3410 title = _("Save new document?");
3412 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3415 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3417 return buffer->isClean() && !buffer->isUnnamed();
3421 bool GuiView::reloadBuffer(Buffer & buf)
3423 currentBufferView()->cursor().reset();
3424 Buffer::ReadStatus status = buf.reload();
3425 return status == Buffer::ReadSuccess;
3429 void GuiView::checkExternallyModifiedBuffers()
3431 BufferList::iterator bit = theBufferList().begin();
3432 BufferList::iterator const bend = theBufferList().end();
3433 for (; bit != bend; ++bit) {
3434 Buffer * buf = *bit;
3435 if (buf->fileName().exists() && buf->isChecksumModified()) {
3436 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3437 " Reload now? Any local changes will be lost."),
3438 from_utf8(buf->absFileName()));
3439 int const ret = Alert::prompt(_("Reload externally changed document?"),
3440 text, 0, 1, _("&Reload"), _("&Cancel"));
3448 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3450 Buffer * buffer = documentBufferView()
3451 ? &(documentBufferView()->buffer()) : 0;
3453 switch (cmd.action()) {
3454 case LFUN_VC_REGISTER:
3455 if (!buffer || !ensureBufferClean(buffer))
3457 if (!buffer->lyxvc().inUse()) {
3458 if (buffer->lyxvc().registrer()) {
3459 reloadBuffer(*buffer);
3460 dr.clearMessageUpdate();
3465 case LFUN_VC_RENAME:
3466 case LFUN_VC_COPY: {
3467 if (!buffer || !ensureBufferClean(buffer))
3469 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3470 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3471 // Some changes are not yet committed.
3472 // We test here and not in getStatus(), since
3473 // this test is expensive.
3475 LyXVC::CommandResult ret =
3476 buffer->lyxvc().checkIn(log);
3478 if (ret == LyXVC::ErrorCommand ||
3479 ret == LyXVC::VCSuccess)
3480 reloadBuffer(*buffer);
3481 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3482 frontend::Alert::error(
3483 _("Revision control error."),
3484 _("Document could not be checked in."));
3488 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3489 LV_VC_RENAME : LV_VC_COPY;
3490 renameBuffer(*buffer, cmd.argument(), kind);
3495 case LFUN_VC_CHECK_IN:
3496 if (!buffer || !ensureBufferClean(buffer))
3498 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3500 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3502 // Only skip reloading if the checkin was cancelled or
3503 // an error occurred before the real checkin VCS command
3504 // was executed, since the VCS might have changed the
3505 // file even if it could not checkin successfully.
3506 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3507 reloadBuffer(*buffer);
3511 case LFUN_VC_CHECK_OUT:
3512 if (!buffer || !ensureBufferClean(buffer))
3514 if (buffer->lyxvc().inUse()) {
3515 dr.setMessage(buffer->lyxvc().checkOut());
3516 reloadBuffer(*buffer);
3520 case LFUN_VC_LOCKING_TOGGLE:
3521 LASSERT(buffer, return);
3522 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3524 if (buffer->lyxvc().inUse()) {
3525 string res = buffer->lyxvc().lockingToggle();
3527 frontend::Alert::error(_("Revision control error."),
3528 _("Error when setting the locking property."));
3531 reloadBuffer(*buffer);
3536 case LFUN_VC_REVERT:
3537 LASSERT(buffer, return);
3538 if (buffer->lyxvc().revert()) {
3539 reloadBuffer(*buffer);
3540 dr.clearMessageUpdate();
3544 case LFUN_VC_UNDO_LAST:
3545 LASSERT(buffer, return);
3546 buffer->lyxvc().undoLast();
3547 reloadBuffer(*buffer);
3548 dr.clearMessageUpdate();
3551 case LFUN_VC_REPO_UPDATE:
3552 LASSERT(buffer, return);
3553 if (ensureBufferClean(buffer)) {
3554 dr.setMessage(buffer->lyxvc().repoUpdate());
3555 checkExternallyModifiedBuffers();
3559 case LFUN_VC_COMMAND: {
3560 string flag = cmd.getArg(0);
3561 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3564 if (contains(flag, 'M')) {
3565 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3568 string path = cmd.getArg(1);
3569 if (contains(path, "$$p") && buffer)
3570 path = subst(path, "$$p", buffer->filePath());
3571 LYXERR(Debug::LYXVC, "Directory: " << path);
3573 if (!pp.isReadableDirectory()) {
3574 lyxerr << _("Directory is not accessible.") << endl;
3577 support::PathChanger p(pp);
3579 string command = cmd.getArg(2);
3580 if (command.empty())
3583 command = subst(command, "$$i", buffer->absFileName());
3584 command = subst(command, "$$p", buffer->filePath());
3586 command = subst(command, "$$m", to_utf8(message));
3587 LYXERR(Debug::LYXVC, "Command: " << command);
3589 one.startscript(Systemcall::Wait, command);
3593 if (contains(flag, 'I'))
3594 buffer->markDirty();
3595 if (contains(flag, 'R'))
3596 reloadBuffer(*buffer);
3601 case LFUN_VC_COMPARE: {
3602 if (cmd.argument().empty()) {
3603 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3607 string rev1 = cmd.getArg(0);
3612 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3615 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3616 f2 = buffer->absFileName();
3618 string rev2 = cmd.getArg(1);
3622 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3626 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3627 f1 << "\n" << f2 << "\n" );
3628 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3629 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3639 void GuiView::openChildDocument(string const & fname)
3641 LASSERT(documentBufferView(), return);
3642 Buffer & buffer = documentBufferView()->buffer();
3643 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3644 documentBufferView()->saveBookmark(false);
3646 if (theBufferList().exists(filename)) {
3647 child = theBufferList().getBuffer(filename);
3650 message(bformat(_("Opening child document %1$s..."),
3651 makeDisplayPath(filename.absFileName())));
3652 child = loadDocument(filename, false);
3654 // Set the parent name of the child document.
3655 // This makes insertion of citations and references in the child work,
3656 // when the target is in the parent or another child document.
3658 child->setParent(&buffer);
3662 bool GuiView::goToFileRow(string const & argument)
3666 size_t i = argument.find_last_of(' ');
3667 if (i != string::npos) {
3668 file_name = os::internal_path(trim(argument.substr(0, i)));
3669 istringstream is(argument.substr(i + 1));
3674 if (i == string::npos) {
3675 LYXERR0("Wrong argument: " << argument);
3679 string const abstmp = package().temp_dir().absFileName();
3680 string const realtmp = package().temp_dir().realPath();
3681 // We have to use os::path_prefix_is() here, instead of
3682 // simply prefixIs(), because the file name comes from
3683 // an external application and may need case adjustment.
3684 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3685 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3686 // Needed by inverse dvi search. If it is a file
3687 // in tmpdir, call the apropriated function.
3688 // If tmpdir is a symlink, we may have the real
3689 // path passed back, so we correct for that.
3690 if (!prefixIs(file_name, abstmp))
3691 file_name = subst(file_name, realtmp, abstmp);
3692 buf = theBufferList().getBufferFromTmp(file_name);
3694 // Must replace extension of the file to be .lyx
3695 // and get full path
3696 FileName const s = fileSearch(string(),
3697 support::changeExtension(file_name, ".lyx"), "lyx");
3698 // Either change buffer or load the file
3699 if (theBufferList().exists(s))
3700 buf = theBufferList().getBuffer(s);
3701 else if (s.exists()) {
3702 buf = loadDocument(s);
3707 _("File does not exist: %1$s"),
3708 makeDisplayPath(file_name)));
3714 _("No buffer for file: %1$s."),
3715 makeDisplayPath(file_name))
3720 bool success = documentBufferView()->setCursorFromRow(row);
3722 LYXERR(Debug::LATEX,
3723 "setCursorFromRow: invalid position for row " << row);
3724 frontend::Alert::error(_("Inverse Search Failed"),
3725 _("Invalid position requested by inverse search.\n"
3726 "You may need to update the viewed document."));
3732 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3734 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3735 menu->exec(QCursor::pos());
3740 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3741 Buffer const * orig, Buffer * clone, string const & format)
3743 Buffer::ExportStatus const status = func(format);
3745 // the cloning operation will have produced a clone of the entire set of
3746 // documents, starting from the master. so we must delete those.
3747 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3749 busyBuffers.remove(orig);
3754 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3755 Buffer const * orig, Buffer * clone, string const & format)
3757 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3759 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3763 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3764 Buffer const * orig, Buffer * clone, string const & format)
3766 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3768 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3772 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3773 Buffer const * orig, Buffer * clone, string const & format)
3775 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3777 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3781 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3782 string const & argument,
3783 Buffer const * used_buffer,
3784 docstring const & msg,
3785 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3786 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3787 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3793 string format = argument;
3795 format = used_buffer->params().getDefaultOutputFormat();
3796 processing_format = format;
3798 progress_->clearMessages();
3801 #if EXPORT_in_THREAD
3803 GuiViewPrivate::busyBuffers.insert(used_buffer);
3804 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3805 if (!cloned_buffer) {
3806 Alert::error(_("Export Error"),
3807 _("Error cloning the Buffer."));
3810 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3815 setPreviewFuture(f);
3816 last_export_format = used_buffer->params().bufferFormat();
3819 // We are asynchronous, so we don't know here anything about the success
3822 Buffer::ExportStatus status;
3824 status = (used_buffer->*syncFunc)(format, false);
3825 } else if (previewFunc) {
3826 status = (used_buffer->*previewFunc)(format);
3829 handleExportStatus(gv_, status, format);
3831 return (status == Buffer::ExportSuccess
3832 || status == Buffer::PreviewSuccess);
3836 Buffer::ExportStatus status;
3838 status = (used_buffer->*syncFunc)(format, true);
3839 } else if (previewFunc) {
3840 status = (used_buffer->*previewFunc)(format);
3843 handleExportStatus(gv_, status, format);
3845 return (status == Buffer::ExportSuccess
3846 || status == Buffer::PreviewSuccess);
3850 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3852 BufferView * bv = currentBufferView();
3853 LASSERT(bv, return);
3855 // Let the current BufferView dispatch its own actions.
3856 bv->dispatch(cmd, dr);
3857 if (dr.dispatched()) {
3858 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3859 updateDialog("document", "");
3863 // Try with the document BufferView dispatch if any.
3864 BufferView * doc_bv = documentBufferView();
3865 if (doc_bv && doc_bv != bv) {
3866 doc_bv->dispatch(cmd, dr);
3867 if (dr.dispatched()) {
3868 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3869 updateDialog("document", "");
3874 // Then let the current Cursor dispatch its own actions.
3875 bv->cursor().dispatch(cmd);
3877 // update completion. We do it here and not in
3878 // processKeySym to avoid another redraw just for a
3879 // changed inline completion
3880 if (cmd.origin() == FuncRequest::KEYBOARD) {
3881 if (cmd.action() == LFUN_SELF_INSERT
3882 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3883 updateCompletion(bv->cursor(), true, true);
3884 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3885 updateCompletion(bv->cursor(), false, true);
3887 updateCompletion(bv->cursor(), false, false);
3890 dr = bv->cursor().result();
3894 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3896 BufferView * bv = currentBufferView();
3897 // By default we won't need any update.
3898 dr.screenUpdate(Update::None);
3899 // assume cmd will be dispatched
3900 dr.dispatched(true);
3902 Buffer * doc_buffer = documentBufferView()
3903 ? &(documentBufferView()->buffer()) : 0;
3905 if (cmd.origin() == FuncRequest::TOC) {
3906 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3907 // FIXME: do we need to pass a DispatchResult object here?
3908 toc->doDispatch(bv->cursor(), cmd);
3912 string const argument = to_utf8(cmd.argument());
3914 switch(cmd.action()) {
3915 case LFUN_BUFFER_CHILD_OPEN:
3916 openChildDocument(to_utf8(cmd.argument()));
3919 case LFUN_BUFFER_IMPORT:
3920 importDocument(to_utf8(cmd.argument()));
3923 case LFUN_MASTER_BUFFER_EXPORT:
3925 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3927 case LFUN_BUFFER_EXPORT: {
3930 // GCC only sees strfwd.h when building merged
3931 if (::lyx::operator==(cmd.argument(), "custom")) {
3932 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3933 // so the following test should not be needed.
3934 // In principle, we could try to switch to such a view...
3935 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3936 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3940 string const dest = cmd.getArg(1);
3941 FileName target_dir;
3942 if (!dest.empty() && FileName::isAbsolute(dest))
3943 target_dir = FileName(support::onlyPath(dest));
3945 target_dir = doc_buffer->fileName().onlyPath();
3947 string const format = (argument.empty() || argument == "default") ?
3948 doc_buffer->params().getDefaultOutputFormat() : argument;
3950 if ((dest.empty() && doc_buffer->isUnnamed())
3951 || !target_dir.isDirWritable()) {
3952 exportBufferAs(*doc_buffer, from_utf8(format));
3955 /* TODO/Review: Is it a problem to also export the children?
3956 See the update_unincluded flag */
3957 d.asyncBufferProcessing(format,
3960 &GuiViewPrivate::exportAndDestroy,
3962 0, cmd.allowAsync());
3963 // TODO Inform user about success
3967 case LFUN_BUFFER_EXPORT_AS: {
3968 LASSERT(doc_buffer, break);
3969 docstring f = cmd.argument();
3971 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3972 exportBufferAs(*doc_buffer, f);
3976 case LFUN_BUFFER_UPDATE: {
3977 d.asyncBufferProcessing(argument,
3980 &GuiViewPrivate::compileAndDestroy,
3982 0, cmd.allowAsync());
3985 case LFUN_BUFFER_VIEW: {
3986 d.asyncBufferProcessing(argument,
3988 _("Previewing ..."),
3989 &GuiViewPrivate::previewAndDestroy,
3991 &Buffer::preview, cmd.allowAsync());
3994 case LFUN_MASTER_BUFFER_UPDATE: {
3995 d.asyncBufferProcessing(argument,
3996 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3998 &GuiViewPrivate::compileAndDestroy,
4000 0, cmd.allowAsync());
4003 case LFUN_MASTER_BUFFER_VIEW: {
4004 d.asyncBufferProcessing(argument,
4005 (doc_buffer ? doc_buffer->masterBuffer() : 0),
4007 &GuiViewPrivate::previewAndDestroy,
4008 0, &Buffer::preview, cmd.allowAsync());
4011 case LFUN_EXPORT_CANCEL: {
4012 Systemcall::killscript();
4015 case LFUN_BUFFER_SWITCH: {
4016 string const file_name = to_utf8(cmd.argument());
4017 if (!FileName::isAbsolute(file_name)) {
4019 dr.setMessage(_("Absolute filename expected."));
4023 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4026 dr.setMessage(_("Document not loaded"));
4030 // Do we open or switch to the buffer in this view ?
4031 if (workArea(*buffer)
4032 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4037 // Look for the buffer in other views
4038 QList<int> const ids = guiApp->viewIds();
4040 for (; i != ids.size(); ++i) {
4041 GuiView & gv = guiApp->view(ids[i]);
4042 if (gv.workArea(*buffer)) {
4044 gv.activateWindow();
4046 gv.setBuffer(buffer);
4051 // If necessary, open a new window as a last resort
4052 if (i == ids.size()) {
4053 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4059 case LFUN_BUFFER_NEXT:
4060 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4063 case LFUN_BUFFER_MOVE_NEXT:
4064 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4067 case LFUN_BUFFER_PREVIOUS:
4068 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4071 case LFUN_BUFFER_MOVE_PREVIOUS:
4072 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4075 case LFUN_BUFFER_CHKTEX:
4076 LASSERT(doc_buffer, break);
4077 doc_buffer->runChktex();
4080 case LFUN_COMMAND_EXECUTE: {
4081 command_execute_ = true;
4082 minibuffer_focus_ = true;
4085 case LFUN_DROP_LAYOUTS_CHOICE:
4086 d.layout_->showPopup();
4089 case LFUN_MENU_OPEN:
4090 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4091 menu->exec(QCursor::pos());
4094 case LFUN_FILE_INSERT: {
4095 if (cmd.getArg(1) == "ignorelang")
4096 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4098 insertLyXFile(cmd.argument());
4102 case LFUN_FILE_INSERT_PLAINTEXT:
4103 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4104 string const fname = to_utf8(cmd.argument());
4105 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4106 dr.setMessage(_("Absolute filename expected."));
4110 FileName filename(fname);
4111 if (fname.empty()) {
4112 FileDialog dlg(qt_("Select file to insert"));
4114 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4115 QStringList(qt_("All Files (*)")));
4117 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4118 dr.setMessage(_("Canceled."));
4122 filename.set(fromqstr(result.second));
4126 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4127 bv->dispatch(new_cmd, dr);
4132 case LFUN_BUFFER_RELOAD: {
4133 LASSERT(doc_buffer, break);
4136 bool drop = (cmd.argument() == "dump");
4139 if (!drop && !doc_buffer->isClean()) {
4140 docstring const file =
4141 makeDisplayPath(doc_buffer->absFileName(), 20);
4142 if (doc_buffer->notifiesExternalModification()) {
4143 docstring text = _("The current version will be lost. "
4144 "Are you sure you want to load the version on disk "
4145 "of the document %1$s?");
4146 ret = Alert::prompt(_("Reload saved document?"),
4147 bformat(text, file), 1, 1,
4148 _("&Reload"), _("&Cancel"));
4150 docstring text = _("Any changes will be lost. "
4151 "Are you sure you want to revert to the saved version "
4152 "of the document %1$s?");
4153 ret = Alert::prompt(_("Revert to saved document?"),
4154 bformat(text, file), 1, 1,
4155 _("&Revert"), _("&Cancel"));
4160 doc_buffer->markClean();
4161 reloadBuffer(*doc_buffer);
4162 dr.forceBufferUpdate();
4167 case LFUN_BUFFER_RESET_EXPORT:
4168 LASSERT(doc_buffer, break);
4169 doc_buffer->requireFreshStart(true);
4170 dr.setMessage(_("Buffer export reset."));
4173 case LFUN_BUFFER_WRITE:
4174 LASSERT(doc_buffer, break);
4175 saveBuffer(*doc_buffer);
4178 case LFUN_BUFFER_WRITE_AS:
4179 LASSERT(doc_buffer, break);
4180 renameBuffer(*doc_buffer, cmd.argument());
4183 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4184 LASSERT(doc_buffer, break);
4185 renameBuffer(*doc_buffer, cmd.argument(),
4186 LV_WRITE_AS_TEMPLATE);
4189 case LFUN_BUFFER_WRITE_ALL: {
4190 Buffer * first = theBufferList().first();
4193 message(_("Saving all documents..."));
4194 // We cannot use a for loop as the buffer list cycles.
4197 if (!b->isClean()) {
4199 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4201 b = theBufferList().next(b);
4202 } while (b != first);
4203 dr.setMessage(_("All documents saved."));
4207 case LFUN_MASTER_BUFFER_FORALL: {
4211 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4212 funcToRun.allowAsync(false);
4214 for (Buffer const * buf : doc_buffer->allRelatives()) {
4215 // Switch to other buffer view and resend cmd
4216 lyx::dispatch(FuncRequest(
4217 LFUN_BUFFER_SWITCH, buf->absFileName()));
4218 lyx::dispatch(funcToRun);
4221 lyx::dispatch(FuncRequest(
4222 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4226 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4227 LASSERT(doc_buffer, break);
4228 doc_buffer->clearExternalModification();
4231 case LFUN_BUFFER_CLOSE:
4235 case LFUN_BUFFER_CLOSE_ALL:
4239 case LFUN_DEVEL_MODE_TOGGLE:
4240 devel_mode_ = !devel_mode_;
4242 dr.setMessage(_("Developer mode is now enabled."));
4244 dr.setMessage(_("Developer mode is now disabled."));
4247 case LFUN_TOOLBAR_TOGGLE: {
4248 string const name = cmd.getArg(0);
4249 if (GuiToolbar * t = toolbar(name))
4254 case LFUN_TOOLBAR_MOVABLE: {
4255 string const name = cmd.getArg(0);
4257 // toggle (all) toolbars movablility
4258 toolbarsMovable_ = !toolbarsMovable_;
4259 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4260 GuiToolbar * tb = toolbar(ti.name);
4261 if (tb && tb->isMovable() != toolbarsMovable_)
4262 // toggle toolbar movablity if it does not fit lock
4263 // (all) toolbars positions state silent = true, since
4264 // status bar notifications are slow
4267 if (toolbarsMovable_)
4268 dr.setMessage(_("Toolbars unlocked."));
4270 dr.setMessage(_("Toolbars locked."));
4271 } else if (GuiToolbar * t = toolbar(name)) {
4272 // toggle current toolbar movablity
4274 // update lock (all) toolbars positions
4275 updateLockToolbars();
4280 case LFUN_ICON_SIZE: {
4281 QSize size = d.iconSize(cmd.argument());
4283 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4284 size.width(), size.height()));
4288 case LFUN_DIALOG_UPDATE: {
4289 string const name = to_utf8(cmd.argument());
4290 if (name == "prefs" || name == "document")
4291 updateDialog(name, string());
4292 else if (name == "paragraph")
4293 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4294 else if (currentBufferView()) {
4295 Inset * inset = currentBufferView()->editedInset(name);
4296 // Can only update a dialog connected to an existing inset
4298 // FIXME: get rid of this indirection; GuiView ask the inset
4299 // if he is kind enough to update itself...
4300 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4301 //FIXME: pass DispatchResult here?
4302 inset->dispatch(currentBufferView()->cursor(), fr);
4308 case LFUN_DIALOG_TOGGLE: {
4309 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4310 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4311 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4315 case LFUN_DIALOG_DISCONNECT_INSET:
4316 disconnectDialog(to_utf8(cmd.argument()));
4319 case LFUN_DIALOG_HIDE: {
4320 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4324 case LFUN_DIALOG_SHOW: {
4325 string const name = cmd.getArg(0);
4326 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4328 if (name == "latexlog") {
4329 // gettatus checks that
4330 LATTEST(doc_buffer);
4331 Buffer::LogType type;
4332 string const logfile = doc_buffer->logName(&type);
4334 case Buffer::latexlog:
4337 case Buffer::buildlog:
4338 sdata = "literate ";
4341 sdata += Lexer::quoteString(logfile);
4342 showDialog("log", sdata);
4343 } else if (name == "vclog") {
4344 // getStatus checks that
4345 LATTEST(doc_buffer);
4346 string const sdata2 = "vc " +
4347 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4348 showDialog("log", sdata2);
4349 } else if (name == "symbols") {
4350 sdata = bv->cursor().getEncoding()->name();
4352 showDialog("symbols", sdata);
4354 } else if (name == "prefs" && isFullScreen()) {
4355 lfunUiToggle("fullscreen");
4356 showDialog("prefs", sdata);
4358 showDialog(name, sdata);
4363 dr.setMessage(cmd.argument());
4366 case LFUN_UI_TOGGLE: {
4367 string arg = cmd.getArg(0);
4368 if (!lfunUiToggle(arg)) {
4369 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4370 dr.setMessage(bformat(msg, from_utf8(arg)));
4372 // Make sure the keyboard focus stays in the work area.
4377 case LFUN_VIEW_SPLIT: {
4378 LASSERT(doc_buffer, break);
4379 string const orientation = cmd.getArg(0);
4380 d.splitter_->setOrientation(orientation == "vertical"
4381 ? Qt::Vertical : Qt::Horizontal);
4382 TabWorkArea * twa = addTabWorkArea();
4383 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4384 setCurrentWorkArea(wa);
4387 case LFUN_TAB_GROUP_CLOSE:
4388 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4389 closeTabWorkArea(twa);
4390 d.current_work_area_ = 0;
4391 twa = d.currentTabWorkArea();
4392 // Switch to the next GuiWorkArea in the found TabWorkArea.
4394 // Make sure the work area is up to date.
4395 setCurrentWorkArea(twa->currentWorkArea());
4397 setCurrentWorkArea(0);
4402 case LFUN_VIEW_CLOSE:
4403 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4404 closeWorkArea(twa->currentWorkArea());
4405 d.current_work_area_ = 0;
4406 twa = d.currentTabWorkArea();
4407 // Switch to the next GuiWorkArea in the found TabWorkArea.
4409 // Make sure the work area is up to date.
4410 setCurrentWorkArea(twa->currentWorkArea());
4412 setCurrentWorkArea(0);
4417 case LFUN_COMPLETION_INLINE:
4418 if (d.current_work_area_)
4419 d.current_work_area_->completer().showInline();
4422 case LFUN_COMPLETION_POPUP:
4423 if (d.current_work_area_)
4424 d.current_work_area_->completer().showPopup();
4429 if (d.current_work_area_)
4430 d.current_work_area_->completer().tab();
4433 case LFUN_COMPLETION_CANCEL:
4434 if (d.current_work_area_) {
4435 if (d.current_work_area_->completer().popupVisible())
4436 d.current_work_area_->completer().hidePopup();
4438 d.current_work_area_->completer().hideInline();
4442 case LFUN_COMPLETION_ACCEPT:
4443 if (d.current_work_area_)
4444 d.current_work_area_->completer().activate();
4447 case LFUN_BUFFER_ZOOM_IN:
4448 case LFUN_BUFFER_ZOOM_OUT:
4449 case LFUN_BUFFER_ZOOM: {
4450 if (cmd.argument().empty()) {
4451 if (cmd.action() == LFUN_BUFFER_ZOOM)
4453 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4458 if (cmd.action() == LFUN_BUFFER_ZOOM)
4459 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4460 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4461 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4463 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4466 // Actual zoom value: default zoom + fractional extra value
4467 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4468 if (zoom < static_cast<int>(zoom_min_))
4471 lyxrc.currentZoom = zoom;
4473 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4474 lyxrc.currentZoom, lyxrc.defaultZoom));
4476 guiApp->fontLoader().update();
4477 dr.screenUpdate(Update::Force | Update::FitCursor);
4481 case LFUN_VC_REGISTER:
4482 case LFUN_VC_RENAME:
4484 case LFUN_VC_CHECK_IN:
4485 case LFUN_VC_CHECK_OUT:
4486 case LFUN_VC_REPO_UPDATE:
4487 case LFUN_VC_LOCKING_TOGGLE:
4488 case LFUN_VC_REVERT:
4489 case LFUN_VC_UNDO_LAST:
4490 case LFUN_VC_COMMAND:
4491 case LFUN_VC_COMPARE:
4492 dispatchVC(cmd, dr);
4495 case LFUN_SERVER_GOTO_FILE_ROW:
4496 if(goToFileRow(to_utf8(cmd.argument())))
4497 dr.screenUpdate(Update::Force | Update::FitCursor);
4500 case LFUN_LYX_ACTIVATE:
4504 case LFUN_FORWARD_SEARCH: {
4505 // it seems safe to assume we have a document buffer, since
4506 // getStatus wants one.
4507 LATTEST(doc_buffer);
4508 Buffer const * doc_master = doc_buffer->masterBuffer();
4509 FileName const path(doc_master->temppath());
4510 string const texname = doc_master->isChild(doc_buffer)
4511 ? DocFileName(changeExtension(
4512 doc_buffer->absFileName(),
4513 "tex")).mangledFileName()
4514 : doc_buffer->latexName();
4515 string const fulltexname =
4516 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4517 string const mastername =
4518 removeExtension(doc_master->latexName());
4519 FileName const dviname(addName(path.absFileName(),
4520 addExtension(mastername, "dvi")));
4521 FileName const pdfname(addName(path.absFileName(),
4522 addExtension(mastername, "pdf")));
4523 bool const have_dvi = dviname.exists();
4524 bool const have_pdf = pdfname.exists();
4525 if (!have_dvi && !have_pdf) {
4526 dr.setMessage(_("Please, preview the document first."));
4529 string outname = dviname.onlyFileName();
4530 string command = lyxrc.forward_search_dvi;
4531 if (!have_dvi || (have_pdf &&
4532 pdfname.lastModified() > dviname.lastModified())) {
4533 outname = pdfname.onlyFileName();
4534 command = lyxrc.forward_search_pdf;
4537 DocIterator cur = bv->cursor();
4538 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4539 LYXERR(Debug::ACTION, "Forward search: row:" << row
4541 if (row == -1 || command.empty()) {
4542 dr.setMessage(_("Couldn't proceed."));
4545 string texrow = convert<string>(row);
4547 command = subst(command, "$$n", texrow);
4548 command = subst(command, "$$f", fulltexname);
4549 command = subst(command, "$$t", texname);
4550 command = subst(command, "$$o", outname);
4552 volatile PathChanger p(path);
4554 one.startscript(Systemcall::DontWait, command);
4558 case LFUN_SPELLING_CONTINUOUSLY:
4559 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4560 dr.screenUpdate(Update::Force);
4564 // The LFUN must be for one of BufferView, Buffer or Cursor;
4566 dispatchToBufferView(cmd, dr);
4570 // Part of automatic menu appearance feature.
4571 if (isFullScreen()) {
4572 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4576 // Need to update bv because many LFUNs here might have destroyed it
4577 bv = currentBufferView();
4579 // Clear non-empty selections
4580 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4582 Cursor & cur = bv->cursor();
4583 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4584 cur.clearSelection();
4590 bool GuiView::lfunUiToggle(string const & ui_component)
4592 if (ui_component == "scrollbar") {
4593 // hide() is of no help
4594 if (d.current_work_area_->verticalScrollBarPolicy() ==
4595 Qt::ScrollBarAlwaysOff)
4597 d.current_work_area_->setVerticalScrollBarPolicy(
4598 Qt::ScrollBarAsNeeded);
4600 d.current_work_area_->setVerticalScrollBarPolicy(
4601 Qt::ScrollBarAlwaysOff);
4602 } else if (ui_component == "statusbar") {
4603 statusBar()->setVisible(!statusBar()->isVisible());
4604 } else if (ui_component == "menubar") {
4605 menuBar()->setVisible(!menuBar()->isVisible());
4607 if (ui_component == "frame") {
4609 getContentsMargins(&l, &t, &r, &b);
4610 //are the frames in default state?
4611 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4613 setContentsMargins(-2, -2, -2, -2);
4615 setContentsMargins(0, 0, 0, 0);
4618 if (ui_component == "fullscreen") {
4626 void GuiView::toggleFullScreen()
4628 if (isFullScreen()) {
4629 for (int i = 0; i != d.splitter_->count(); ++i)
4630 d.tabWorkArea(i)->setFullScreen(false);
4631 setContentsMargins(0, 0, 0, 0);
4632 setWindowState(windowState() ^ Qt::WindowFullScreen);
4635 statusBar()->show();
4638 hideDialogs("prefs", 0);
4639 for (int i = 0; i != d.splitter_->count(); ++i)
4640 d.tabWorkArea(i)->setFullScreen(true);
4641 setContentsMargins(-2, -2, -2, -2);
4643 setWindowState(windowState() ^ Qt::WindowFullScreen);
4644 if (lyxrc.full_screen_statusbar)
4645 statusBar()->hide();
4646 if (lyxrc.full_screen_menubar)
4648 if (lyxrc.full_screen_toolbars) {
4649 ToolbarMap::iterator end = d.toolbars_.end();
4650 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4655 // give dialogs like the TOC a chance to adapt
4660 Buffer const * GuiView::updateInset(Inset const * inset)
4665 Buffer const * inset_buffer = &(inset->buffer());
4667 for (int i = 0; i != d.splitter_->count(); ++i) {
4668 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4671 Buffer const * buffer = &(wa->bufferView().buffer());
4672 if (inset_buffer == buffer)
4673 wa->scheduleRedraw(true);
4675 return inset_buffer;
4679 void GuiView::restartCaret()
4681 /* When we move around, or type, it's nice to be able to see
4682 * the caret immediately after the keypress.
4684 if (d.current_work_area_)
4685 d.current_work_area_->startBlinkingCaret();
4687 // Take this occasion to update the other GUI elements.
4693 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4695 if (d.current_work_area_)
4696 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4701 // This list should be kept in sync with the list of insets in
4702 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4703 // dialog should have the same name as the inset.
4704 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4705 // docs in LyXAction.cpp.
4707 char const * const dialognames[] = {
4709 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4710 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4711 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4712 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4713 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4714 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4715 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4716 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4718 char const * const * const end_dialognames =
4719 dialognames + (sizeof(dialognames) / sizeof(char *));
4723 cmpCStr(char const * name) : name_(name) {}
4724 bool operator()(char const * other) {
4725 return strcmp(other, name_) == 0;
4732 bool isValidName(string const & name)
4734 return find_if(dialognames, end_dialognames,
4735 cmpCStr(name.c_str())) != end_dialognames;
4741 void GuiView::resetDialogs()
4743 // Make sure that no LFUN uses any GuiView.
4744 guiApp->setCurrentView(0);
4748 constructToolbars();
4749 guiApp->menus().fillMenuBar(menuBar(), this, false);
4750 d.layout_->updateContents(true);
4751 // Now update controls with current buffer.
4752 guiApp->setCurrentView(this);
4758 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4760 if (!isValidName(name))
4763 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4765 if (it != d.dialogs_.end()) {
4767 it->second->hideView();
4768 return it->second.get();
4771 Dialog * dialog = build(name);
4772 d.dialogs_[name].reset(dialog);
4773 if (lyxrc.allow_geometry_session)
4774 dialog->restoreSession();
4781 void GuiView::showDialog(string const & name, string const & sdata,
4784 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4788 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4794 const string name = fromqstr(qname);
4795 const string sdata = fromqstr(qdata);
4799 Dialog * dialog = findOrBuild(name, false);
4801 bool const visible = dialog->isVisibleView();
4802 dialog->showData(sdata);
4803 if (currentBufferView())
4804 currentBufferView()->editInset(name, inset);
4805 // We only set the focus to the new dialog if it was not yet
4806 // visible in order not to change the existing previous behaviour
4808 // activateWindow is needed for floating dockviews
4809 dialog->asQWidget()->raise();
4810 dialog->asQWidget()->activateWindow();
4811 dialog->asQWidget()->setFocus();
4815 catch (ExceptionMessage const & ex) {
4823 bool GuiView::isDialogVisible(string const & name) const
4825 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4826 if (it == d.dialogs_.end())
4828 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4832 void GuiView::hideDialog(string const & name, Inset * inset)
4834 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4835 if (it == d.dialogs_.end())
4839 if (!currentBufferView())
4841 if (inset != currentBufferView()->editedInset(name))
4845 Dialog * const dialog = it->second.get();
4846 if (dialog->isVisibleView())
4848 if (currentBufferView())
4849 currentBufferView()->editInset(name, 0);
4853 void GuiView::disconnectDialog(string const & name)
4855 if (!isValidName(name))
4857 if (currentBufferView())
4858 currentBufferView()->editInset(name, 0);
4862 void GuiView::hideAll() const
4864 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4865 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4867 for(; it != end; ++it)
4868 it->second->hideView();
4872 void GuiView::updateDialogs()
4874 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4875 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4877 for(; it != end; ++it) {
4878 Dialog * dialog = it->second.get();
4880 if (dialog->needBufferOpen() && !documentBufferView())
4881 hideDialog(fromqstr(dialog->name()), 0);
4882 else if (dialog->isVisibleView())
4883 dialog->checkStatus();
4890 Dialog * createDialog(GuiView & lv, string const & name);
4892 // will be replaced by a proper factory...
4893 Dialog * createGuiAbout(GuiView & lv);
4894 Dialog * createGuiBibtex(GuiView & lv);
4895 Dialog * createGuiChanges(GuiView & lv);
4896 Dialog * createGuiCharacter(GuiView & lv);
4897 Dialog * createGuiCitation(GuiView & lv);
4898 Dialog * createGuiCompare(GuiView & lv);
4899 Dialog * createGuiCompareHistory(GuiView & lv);
4900 Dialog * createGuiDelimiter(GuiView & lv);
4901 Dialog * createGuiDocument(GuiView & lv);
4902 Dialog * createGuiErrorList(GuiView & lv);
4903 Dialog * createGuiExternal(GuiView & lv);
4904 Dialog * createGuiGraphics(GuiView & lv);
4905 Dialog * createGuiInclude(GuiView & lv);
4906 Dialog * createGuiIndex(GuiView & lv);
4907 Dialog * createGuiListings(GuiView & lv);
4908 Dialog * createGuiLog(GuiView & lv);
4909 Dialog * createGuiLyXFiles(GuiView & lv);
4910 Dialog * createGuiMathMatrix(GuiView & lv);
4911 Dialog * createGuiNote(GuiView & lv);
4912 Dialog * createGuiParagraph(GuiView & lv);
4913 Dialog * createGuiPhantom(GuiView & lv);
4914 Dialog * createGuiPreferences(GuiView & lv);
4915 Dialog * createGuiPrint(GuiView & lv);
4916 Dialog * createGuiPrintindex(GuiView & lv);
4917 Dialog * createGuiRef(GuiView & lv);
4918 Dialog * createGuiSearch(GuiView & lv);
4919 Dialog * createGuiSearchAdv(GuiView & lv);
4920 Dialog * createGuiSendTo(GuiView & lv);
4921 Dialog * createGuiShowFile(GuiView & lv);
4922 Dialog * createGuiSpellchecker(GuiView & lv);
4923 Dialog * createGuiSymbols(GuiView & lv);
4924 Dialog * createGuiTabularCreate(GuiView & lv);
4925 Dialog * createGuiTexInfo(GuiView & lv);
4926 Dialog * createGuiToc(GuiView & lv);
4927 Dialog * createGuiThesaurus(GuiView & lv);
4928 Dialog * createGuiViewSource(GuiView & lv);
4929 Dialog * createGuiWrap(GuiView & lv);
4930 Dialog * createGuiProgressView(GuiView & lv);
4934 Dialog * GuiView::build(string const & name)
4936 LASSERT(isValidName(name), return 0);
4938 Dialog * dialog = createDialog(*this, name);
4942 if (name == "aboutlyx")
4943 return createGuiAbout(*this);
4944 if (name == "bibtex")
4945 return createGuiBibtex(*this);
4946 if (name == "changes")
4947 return createGuiChanges(*this);
4948 if (name == "character")
4949 return createGuiCharacter(*this);
4950 if (name == "citation")
4951 return createGuiCitation(*this);
4952 if (name == "compare")
4953 return createGuiCompare(*this);
4954 if (name == "comparehistory")
4955 return createGuiCompareHistory(*this);
4956 if (name == "document")
4957 return createGuiDocument(*this);
4958 if (name == "errorlist")
4959 return createGuiErrorList(*this);
4960 if (name == "external")
4961 return createGuiExternal(*this);
4963 return createGuiShowFile(*this);
4964 if (name == "findreplace")
4965 return createGuiSearch(*this);
4966 if (name == "findreplaceadv")
4967 return createGuiSearchAdv(*this);
4968 if (name == "graphics")
4969 return createGuiGraphics(*this);
4970 if (name == "include")
4971 return createGuiInclude(*this);
4972 if (name == "index")
4973 return createGuiIndex(*this);
4974 if (name == "index_print")
4975 return createGuiPrintindex(*this);
4976 if (name == "listings")
4977 return createGuiListings(*this);
4979 return createGuiLog(*this);
4980 if (name == "lyxfiles")
4981 return createGuiLyXFiles(*this);
4982 if (name == "mathdelimiter")
4983 return createGuiDelimiter(*this);
4984 if (name == "mathmatrix")
4985 return createGuiMathMatrix(*this);
4987 return createGuiNote(*this);
4988 if (name == "paragraph")
4989 return createGuiParagraph(*this);
4990 if (name == "phantom")
4991 return createGuiPhantom(*this);
4992 if (name == "prefs")
4993 return createGuiPreferences(*this);
4995 return createGuiRef(*this);
4996 if (name == "sendto")
4997 return createGuiSendTo(*this);
4998 if (name == "spellchecker")
4999 return createGuiSpellchecker(*this);
5000 if (name == "symbols")
5001 return createGuiSymbols(*this);
5002 if (name == "tabularcreate")
5003 return createGuiTabularCreate(*this);
5004 if (name == "texinfo")
5005 return createGuiTexInfo(*this);
5006 if (name == "thesaurus")
5007 return createGuiThesaurus(*this);
5009 return createGuiToc(*this);
5010 if (name == "view-source")
5011 return createGuiViewSource(*this);
5013 return createGuiWrap(*this);
5014 if (name == "progress")
5015 return createGuiProgressView(*this);
5021 SEMenu::SEMenu(QWidget * parent)
5023 QAction * action = addAction(qt_("Disable Shell Escape"));
5024 connect(action, SIGNAL(triggered()),
5025 parent, SLOT(disableShellEscape()));
5028 } // namespace frontend
5031 #include "moc_GuiView.cpp"