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 enable = doc_buffer != 0;
1974 case LFUN_BUFFER_WRITE:
1975 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1978 //FIXME: This LFUN should be moved to GuiApplication.
1979 case LFUN_BUFFER_WRITE_ALL: {
1980 // We enable the command only if there are some modified buffers
1981 Buffer * first = theBufferList().first();
1986 // We cannot use a for loop as the buffer list is a cycle.
1988 if (!b->isClean()) {
1992 b = theBufferList().next(b);
1993 } while (b != first);
1997 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1998 enable = doc_buffer && doc_buffer->notifiesExternalModification();
2001 case LFUN_BUFFER_EXPORT: {
2002 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2006 return doc_buffer->getStatus(cmd, flag);
2009 case LFUN_BUFFER_EXPORT_AS:
2010 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2015 case LFUN_BUFFER_WRITE_AS:
2016 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2017 enable = doc_buffer != 0;
2020 case LFUN_EXPORT_CANCEL:
2021 enable = d.processing_thread_watcher_.isRunning();
2024 case LFUN_BUFFER_CLOSE:
2025 case LFUN_VIEW_CLOSE:
2026 enable = doc_buffer != 0;
2029 case LFUN_BUFFER_CLOSE_ALL:
2030 enable = theBufferList().last() != theBufferList().first();
2033 case LFUN_BUFFER_CHKTEX: {
2034 // hide if we have no checktex command
2035 if (lyxrc.chktex_command.empty()) {
2036 flag.setUnknown(true);
2040 if (!doc_buffer || !doc_buffer->params().isLatex()
2041 || d.processing_thread_watcher_.isRunning()) {
2042 // grey out, don't hide
2050 case LFUN_VIEW_SPLIT:
2051 if (cmd.getArg(0) == "vertical")
2052 enable = doc_buffer && (d.splitter_->count() == 1 ||
2053 d.splitter_->orientation() == Qt::Vertical);
2055 enable = doc_buffer && (d.splitter_->count() == 1 ||
2056 d.splitter_->orientation() == Qt::Horizontal);
2059 case LFUN_TAB_GROUP_CLOSE:
2060 enable = d.tabWorkAreaCount() > 1;
2063 case LFUN_DEVEL_MODE_TOGGLE:
2064 flag.setOnOff(devel_mode_);
2067 case LFUN_TOOLBAR_TOGGLE: {
2068 string const name = cmd.getArg(0);
2069 if (GuiToolbar * t = toolbar(name))
2070 flag.setOnOff(t->isVisible());
2073 docstring const msg =
2074 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2080 case LFUN_TOOLBAR_MOVABLE: {
2081 string const name = cmd.getArg(0);
2082 // use negation since locked == !movable
2084 // toolbar name * locks all toolbars
2085 flag.setOnOff(!toolbarsMovable_);
2086 else if (GuiToolbar * t = toolbar(name))
2087 flag.setOnOff(!(t->isMovable()));
2090 docstring const msg =
2091 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2097 case LFUN_ICON_SIZE:
2098 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2101 case LFUN_DROP_LAYOUTS_CHOICE:
2105 case LFUN_UI_TOGGLE:
2106 flag.setOnOff(isFullScreen());
2109 case LFUN_DIALOG_DISCONNECT_INSET:
2112 case LFUN_DIALOG_HIDE:
2113 // FIXME: should we check if the dialog is shown?
2116 case LFUN_DIALOG_TOGGLE:
2117 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2120 case LFUN_DIALOG_SHOW: {
2121 string const name = cmd.getArg(0);
2123 enable = name == "aboutlyx"
2124 || name == "file" //FIXME: should be removed.
2125 || name == "lyxfiles"
2127 || name == "texinfo"
2128 || name == "progress"
2129 || name == "compare";
2130 else if (name == "character" || name == "symbols"
2131 || name == "mathdelimiter" || name == "mathmatrix") {
2132 if (!buf || buf->isReadonly())
2135 Cursor const & cur = currentBufferView()->cursor();
2136 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2139 else if (name == "latexlog")
2140 enable = FileName(doc_buffer->logName()).isReadableFile();
2141 else if (name == "spellchecker")
2142 enable = theSpellChecker()
2143 && !doc_buffer->isReadonly()
2144 && !doc_buffer->text().empty();
2145 else if (name == "vclog")
2146 enable = doc_buffer->lyxvc().inUse();
2150 case LFUN_DIALOG_UPDATE: {
2151 string const name = cmd.getArg(0);
2153 enable = name == "prefs";
2157 case LFUN_COMMAND_EXECUTE:
2159 case LFUN_MENU_OPEN:
2160 // Nothing to check.
2163 case LFUN_COMPLETION_INLINE:
2164 if (!d.current_work_area_
2165 || !d.current_work_area_->completer().inlinePossible(
2166 currentBufferView()->cursor()))
2170 case LFUN_COMPLETION_POPUP:
2171 if (!d.current_work_area_
2172 || !d.current_work_area_->completer().popupPossible(
2173 currentBufferView()->cursor()))
2178 if (!d.current_work_area_
2179 || !d.current_work_area_->completer().inlinePossible(
2180 currentBufferView()->cursor()))
2184 case LFUN_COMPLETION_ACCEPT:
2185 if (!d.current_work_area_
2186 || (!d.current_work_area_->completer().popupVisible()
2187 && !d.current_work_area_->completer().inlineVisible()
2188 && !d.current_work_area_->completer().completionAvailable()))
2192 case LFUN_COMPLETION_CANCEL:
2193 if (!d.current_work_area_
2194 || (!d.current_work_area_->completer().popupVisible()
2195 && !d.current_work_area_->completer().inlineVisible()))
2199 case LFUN_BUFFER_ZOOM_OUT:
2200 case LFUN_BUFFER_ZOOM_IN: {
2201 // only diff between these two is that the default for ZOOM_OUT
2203 bool const neg_zoom =
2204 convert<int>(cmd.argument()) < 0 ||
2205 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2206 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2207 docstring const msg =
2208 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2212 enable = doc_buffer;
2216 case LFUN_BUFFER_ZOOM: {
2217 bool const less_than_min_zoom =
2218 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2219 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2220 docstring const msg =
2221 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2226 enable = doc_buffer;
2230 case LFUN_BUFFER_MOVE_NEXT:
2231 case LFUN_BUFFER_MOVE_PREVIOUS:
2232 // we do not cycle when moving
2233 case LFUN_BUFFER_NEXT:
2234 case LFUN_BUFFER_PREVIOUS:
2235 // because we cycle, it doesn't matter whether on first or last
2236 enable = (d.currentTabWorkArea()->count() > 1);
2238 case LFUN_BUFFER_SWITCH:
2239 // toggle on the current buffer, but do not toggle off
2240 // the other ones (is that a good idea?)
2242 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2243 flag.setOnOff(true);
2246 case LFUN_VC_REGISTER:
2247 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2249 case LFUN_VC_RENAME:
2250 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2253 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2255 case LFUN_VC_CHECK_IN:
2256 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2258 case LFUN_VC_CHECK_OUT:
2259 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2261 case LFUN_VC_LOCKING_TOGGLE:
2262 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2263 && doc_buffer->lyxvc().lockingToggleEnabled();
2264 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2266 case LFUN_VC_REVERT:
2267 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2268 && !doc_buffer->hasReadonlyFlag();
2270 case LFUN_VC_UNDO_LAST:
2271 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2273 case LFUN_VC_REPO_UPDATE:
2274 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2276 case LFUN_VC_COMMAND: {
2277 if (cmd.argument().empty())
2279 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2283 case LFUN_VC_COMPARE:
2284 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2287 case LFUN_SERVER_GOTO_FILE_ROW:
2288 case LFUN_LYX_ACTIVATE:
2290 case LFUN_FORWARD_SEARCH:
2291 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2294 case LFUN_FILE_INSERT_PLAINTEXT:
2295 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2296 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2299 case LFUN_SPELLING_CONTINUOUSLY:
2300 flag.setOnOff(lyxrc.spellcheck_continuously);
2308 flag.setEnabled(false);
2314 static FileName selectTemplateFile()
2316 FileDialog dlg(qt_("Select template file"));
2317 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2318 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2320 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2321 QStringList(qt_("LyX Documents (*.lyx)")));
2323 if (result.first == FileDialog::Later)
2325 if (result.second.isEmpty())
2327 return FileName(fromqstr(result.second));
2331 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2335 Buffer * newBuffer = 0;
2337 newBuffer = checkAndLoadLyXFile(filename);
2338 } catch (ExceptionMessage const & e) {
2345 message(_("Document not loaded."));
2349 setBuffer(newBuffer);
2350 newBuffer->errors("Parse");
2353 theSession().lastFiles().add(filename);
2354 theSession().writeFile();
2361 void GuiView::openDocument(string const & fname)
2363 string initpath = lyxrc.document_path;
2365 if (documentBufferView()) {
2366 string const trypath = documentBufferView()->buffer().filePath();
2367 // If directory is writeable, use this as default.
2368 if (FileName(trypath).isDirWritable())
2374 if (fname.empty()) {
2375 FileDialog dlg(qt_("Select document to open"));
2376 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2377 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2379 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2380 FileDialog::Result result =
2381 dlg.open(toqstr(initpath), filter);
2383 if (result.first == FileDialog::Later)
2386 filename = fromqstr(result.second);
2388 // check selected filename
2389 if (filename.empty()) {
2390 message(_("Canceled."));
2396 // get absolute path of file and add ".lyx" to the filename if
2398 FileName const fullname =
2399 fileSearch(string(), filename, "lyx", support::may_not_exist);
2400 if (!fullname.empty())
2401 filename = fullname.absFileName();
2403 if (!fullname.onlyPath().isDirectory()) {
2404 Alert::warning(_("Invalid filename"),
2405 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2406 from_utf8(fullname.absFileName())));
2410 // if the file doesn't exist and isn't already open (bug 6645),
2411 // let the user create one
2412 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2413 !LyXVC::file_not_found_hook(fullname)) {
2414 // the user specifically chose this name. Believe him.
2415 Buffer * const b = newFile(filename, string(), true);
2421 docstring const disp_fn = makeDisplayPath(filename);
2422 message(bformat(_("Opening document %1$s..."), disp_fn));
2425 Buffer * buf = loadDocument(fullname);
2427 str2 = bformat(_("Document %1$s opened."), disp_fn);
2428 if (buf->lyxvc().inUse())
2429 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2430 " " + _("Version control detected.");
2432 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2437 // FIXME: clean that
2438 static bool import(GuiView * lv, FileName const & filename,
2439 string const & format, ErrorList & errorList)
2441 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2443 string loader_format;
2444 vector<string> loaders = theConverters().loaders();
2445 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2446 vector<string>::const_iterator it = loaders.begin();
2447 vector<string>::const_iterator en = loaders.end();
2448 for (; it != en; ++it) {
2449 if (!theConverters().isReachable(format, *it))
2452 string const tofile =
2453 support::changeExtension(filename.absFileName(),
2454 theFormats().extension(*it));
2455 if (theConverters().convert(0, filename, FileName(tofile),
2456 filename, format, *it, errorList) != Converters::SUCCESS)
2458 loader_format = *it;
2461 if (loader_format.empty()) {
2462 frontend::Alert::error(_("Couldn't import file"),
2463 bformat(_("No information for importing the format %1$s."),
2464 theFormats().prettyName(format)));
2468 loader_format = format;
2470 if (loader_format == "lyx") {
2471 Buffer * buf = lv->loadDocument(lyxfile);
2475 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2479 bool as_paragraphs = loader_format == "textparagraph";
2480 string filename2 = (loader_format == format) ? filename.absFileName()
2481 : support::changeExtension(filename.absFileName(),
2482 theFormats().extension(loader_format));
2483 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2485 guiApp->setCurrentView(lv);
2486 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2493 void GuiView::importDocument(string const & argument)
2496 string filename = split(argument, format, ' ');
2498 LYXERR(Debug::INFO, format << " file: " << filename);
2500 // need user interaction
2501 if (filename.empty()) {
2502 string initpath = lyxrc.document_path;
2503 if (documentBufferView()) {
2504 string const trypath = documentBufferView()->buffer().filePath();
2505 // If directory is writeable, use this as default.
2506 if (FileName(trypath).isDirWritable())
2510 docstring const text = bformat(_("Select %1$s file to import"),
2511 theFormats().prettyName(format));
2513 FileDialog dlg(toqstr(text));
2514 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2515 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2517 docstring filter = theFormats().prettyName(format);
2520 filter += from_utf8(theFormats().extensions(format));
2523 FileDialog::Result result =
2524 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2526 if (result.first == FileDialog::Later)
2529 filename = fromqstr(result.second);
2531 // check selected filename
2532 if (filename.empty())
2533 message(_("Canceled."));
2536 if (filename.empty())
2539 // get absolute path of file
2540 FileName const fullname(support::makeAbsPath(filename));
2542 // Can happen if the user entered a path into the dialog
2544 if (fullname.onlyFileName().empty()) {
2545 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2546 "Aborting import."),
2547 from_utf8(fullname.absFileName()));
2548 frontend::Alert::error(_("File name error"), msg);
2549 message(_("Canceled."));
2554 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2556 // Check if the document already is open
2557 Buffer * buf = theBufferList().getBuffer(lyxfile);
2560 if (!closeBuffer()) {
2561 message(_("Canceled."));
2566 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2568 // if the file exists already, and we didn't do
2569 // -i lyx thefile.lyx, warn
2570 if (lyxfile.exists() && fullname != lyxfile) {
2572 docstring text = bformat(_("The document %1$s already exists.\n\n"
2573 "Do you want to overwrite that document?"), displaypath);
2574 int const ret = Alert::prompt(_("Overwrite document?"),
2575 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2578 message(_("Canceled."));
2583 message(bformat(_("Importing %1$s..."), displaypath));
2584 ErrorList errorList;
2585 if (import(this, fullname, format, errorList))
2586 message(_("imported."));
2588 message(_("file not imported!"));
2590 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2594 void GuiView::newDocument(string const & filename, string templatefile,
2597 FileName initpath(lyxrc.document_path);
2598 if (documentBufferView()) {
2599 FileName const trypath(documentBufferView()->buffer().filePath());
2600 // If directory is writeable, use this as default.
2601 if (trypath.isDirWritable())
2605 if (from_template) {
2606 if (templatefile.empty())
2607 templatefile = selectTemplateFile().absFileName();
2608 if (templatefile.empty())
2613 if (filename.empty())
2614 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2616 b = newFile(filename, templatefile, true);
2621 // If no new document could be created, it is unsure
2622 // whether there is a valid BufferView.
2623 if (currentBufferView())
2624 // Ensure the cursor is correctly positioned on screen.
2625 currentBufferView()->showCursor();
2629 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2631 BufferView * bv = documentBufferView();
2636 FileName filename(to_utf8(fname));
2637 if (filename.empty()) {
2638 // Launch a file browser
2640 string initpath = lyxrc.document_path;
2641 string const trypath = bv->buffer().filePath();
2642 // If directory is writeable, use this as default.
2643 if (FileName(trypath).isDirWritable())
2647 FileDialog dlg(qt_("Select LyX document to insert"));
2648 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2649 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2651 FileDialog::Result result = dlg.open(toqstr(initpath),
2652 QStringList(qt_("LyX Documents (*.lyx)")));
2654 if (result.first == FileDialog::Later)
2658 filename.set(fromqstr(result.second));
2660 // check selected filename
2661 if (filename.empty()) {
2662 // emit message signal.
2663 message(_("Canceled."));
2668 bv->insertLyXFile(filename, ignorelang);
2669 bv->buffer().errors("Parse");
2673 string const GuiView::getTemplatesPath(Buffer & b)
2675 // We start off with the user's templates path
2676 string result = addPath(package().user_support().absFileName(), "templates");
2677 // Check for the document language
2678 string const langcode = b.params().language->code();
2679 string const shortcode = langcode.substr(0, 2);
2680 if (!langcode.empty() && shortcode != "en") {
2681 string subpath = addPath(result, shortcode);
2682 string subpath_long = addPath(result, langcode);
2683 // If we have a subdirectory for the language already,
2685 FileName sp = FileName(subpath);
2686 if (sp.isDirectory())
2688 else if (FileName(subpath_long).isDirectory())
2689 result = subpath_long;
2691 // Ask whether we should create such a subdirectory
2692 docstring const text =
2693 bformat(_("It is suggested to save the template in a subdirectory\n"
2694 "appropriate to the document language (%1$s).\n"
2695 "This subdirectory does not exists yet.\n"
2696 "Do you want to create it?"),
2697 _(b.params().language->display()));
2698 if (Alert::prompt(_("Create Language Directory?"),
2699 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2700 // If the user agreed, we try to create it and report if this failed.
2701 if (!sp.createDirectory(0777))
2702 Alert::error(_("Subdirectory creation failed!"),
2703 _("Could not create subdirectory.\n"
2704 "The template will be saved in the parent directory."));
2710 // Do we have a layout category?
2711 string const cat = b.params().baseClass() ?
2712 b.params().baseClass()->category()
2715 string subpath = addPath(result, cat);
2716 // If we have a subdirectory for the category already,
2718 FileName sp = FileName(subpath);
2719 if (sp.isDirectory())
2722 // Ask whether we should create such a subdirectory
2723 docstring const text =
2724 bformat(_("It is suggested to save the template in a subdirectory\n"
2725 "appropriate to the layout category (%1$s).\n"
2726 "This subdirectory does not exists yet.\n"
2727 "Do you want to create it?"),
2729 if (Alert::prompt(_("Create Category Directory?"),
2730 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2731 // If the user agreed, we try to create it and report if this failed.
2732 if (!sp.createDirectory(0777))
2733 Alert::error(_("Subdirectory creation failed!"),
2734 _("Could not create subdirectory.\n"
2735 "The template will be saved in the parent directory."));
2745 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2747 FileName fname = b.fileName();
2748 FileName const oldname = fname;
2749 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2751 if (!newname.empty()) {
2754 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2756 fname = support::makeAbsPath(to_utf8(newname),
2757 oldname.onlyPath().absFileName());
2759 // Switch to this Buffer.
2762 // No argument? Ask user through dialog.
2764 QString const title = as_template ? qt_("Choose a filename to save template as")
2765 : qt_("Choose a filename to save document as");
2766 FileDialog dlg(title);
2767 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2768 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2770 if (!isLyXFileName(fname.absFileName()))
2771 fname.changeExtension(".lyx");
2773 string const path = as_template ?
2775 : fname.onlyPath().absFileName();
2776 FileDialog::Result result =
2777 dlg.save(toqstr(path),
2778 QStringList(qt_("LyX Documents (*.lyx)")),
2779 toqstr(fname.onlyFileName()));
2781 if (result.first == FileDialog::Later)
2784 fname.set(fromqstr(result.second));
2789 if (!isLyXFileName(fname.absFileName()))
2790 fname.changeExtension(".lyx");
2793 // fname is now the new Buffer location.
2795 // if there is already a Buffer open with this name, we do not want
2796 // to have another one. (the second test makes sure we're not just
2797 // trying to overwrite ourselves, which is fine.)
2798 if (theBufferList().exists(fname) && fname != oldname
2799 && theBufferList().getBuffer(fname) != &b) {
2800 docstring const text =
2801 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2802 "Please close it before attempting to overwrite it.\n"
2803 "Do you want to choose a new filename?"),
2804 from_utf8(fname.absFileName()));
2805 int const ret = Alert::prompt(_("Chosen File Already Open"),
2806 text, 0, 1, _("&Rename"), _("&Cancel"));
2808 case 0: return renameBuffer(b, docstring(), kind);
2809 case 1: return false;
2814 bool const existsLocal = fname.exists();
2815 bool const existsInVC = LyXVC::fileInVC(fname);
2816 if (existsLocal || existsInVC) {
2817 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2818 if (kind != LV_WRITE_AS && existsInVC) {
2819 // renaming to a name that is already in VC
2821 docstring text = bformat(_("The document %1$s "
2822 "is already registered.\n\n"
2823 "Do you want to choose a new name?"),
2825 docstring const title = (kind == LV_VC_RENAME) ?
2826 _("Rename document?") : _("Copy document?");
2827 docstring const button = (kind == LV_VC_RENAME) ?
2828 _("&Rename") : _("&Copy");
2829 int const ret = Alert::prompt(title, text, 0, 1,
2830 button, _("&Cancel"));
2832 case 0: return renameBuffer(b, docstring(), kind);
2833 case 1: return false;
2838 docstring text = bformat(_("The document %1$s "
2839 "already exists.\n\n"
2840 "Do you want to overwrite that document?"),
2842 int const ret = Alert::prompt(_("Overwrite document?"),
2843 text, 0, 2, _("&Overwrite"),
2844 _("&Rename"), _("&Cancel"));
2847 case 1: return renameBuffer(b, docstring(), kind);
2848 case 2: return false;
2854 case LV_VC_RENAME: {
2855 string msg = b.lyxvc().rename(fname);
2858 message(from_utf8(msg));
2862 string msg = b.lyxvc().copy(fname);
2865 message(from_utf8(msg));
2869 case LV_WRITE_AS_TEMPLATE:
2872 // LyXVC created the file already in case of LV_VC_RENAME or
2873 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2874 // relative paths of included stuff right if we moved e.g. from
2875 // /a/b.lyx to /a/c/b.lyx.
2877 bool const saved = saveBuffer(b, fname);
2884 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2886 FileName fname = b.fileName();
2888 FileDialog dlg(qt_("Choose a filename to export the document as"));
2889 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2892 QString const anyformat = qt_("Guess from extension (*.*)");
2895 vector<Format const *> export_formats;
2896 for (Format const & f : theFormats())
2897 if (f.documentFormat())
2898 export_formats.push_back(&f);
2899 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2900 map<QString, string> fmap;
2903 for (Format const * f : export_formats) {
2904 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2905 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2907 from_ascii(f->extension())));
2908 types << loc_filter;
2909 fmap[loc_filter] = f->name();
2910 if (from_ascii(f->name()) == iformat) {
2911 filter = loc_filter;
2912 ext = f->extension();
2915 string ofname = fname.onlyFileName();
2917 ofname = support::changeExtension(ofname, ext);
2918 FileDialog::Result result =
2919 dlg.save(toqstr(fname.onlyPath().absFileName()),
2923 if (result.first != FileDialog::Chosen)
2927 fname.set(fromqstr(result.second));
2928 if (filter == anyformat)
2929 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2931 fmt_name = fmap[filter];
2932 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2933 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2935 if (fmt_name.empty() || fname.empty())
2938 // fname is now the new Buffer location.
2939 if (FileName(fname).exists()) {
2940 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2941 docstring text = bformat(_("The document %1$s already "
2942 "exists.\n\nDo you want to "
2943 "overwrite that document?"),
2945 int const ret = Alert::prompt(_("Overwrite document?"),
2946 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2949 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2950 case 2: return false;
2954 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2957 return dr.dispatched();
2961 bool GuiView::saveBuffer(Buffer & b)
2963 return saveBuffer(b, FileName());
2967 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2969 if (workArea(b) && workArea(b)->inDialogMode())
2972 if (fn.empty() && b.isUnnamed())
2973 return renameBuffer(b, docstring());
2975 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2977 theSession().lastFiles().add(b.fileName());
2978 theSession().writeFile();
2982 // Switch to this Buffer.
2985 // FIXME: we don't tell the user *WHY* the save failed !!
2986 docstring const file = makeDisplayPath(b.absFileName(), 30);
2987 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2988 "Do you want to rename the document and "
2989 "try again?"), file);
2990 int const ret = Alert::prompt(_("Rename and save?"),
2991 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2994 if (!renameBuffer(b, docstring()))
3003 return saveBuffer(b, fn);
3007 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3009 return closeWorkArea(wa, false);
3013 // We only want to close the buffer if it is not visible in other workareas
3014 // of the same view, nor in other views, and if this is not a child
3015 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3017 Buffer & buf = wa->bufferView().buffer();
3019 bool last_wa = d.countWorkAreasOf(buf) == 1
3020 && !inOtherView(buf) && !buf.parent();
3022 bool close_buffer = last_wa;
3025 if (lyxrc.close_buffer_with_last_view == "yes")
3027 else if (lyxrc.close_buffer_with_last_view == "no")
3028 close_buffer = false;
3031 if (buf.isUnnamed())
3032 file = from_utf8(buf.fileName().onlyFileName());
3034 file = buf.fileName().displayName(30);
3035 docstring const text = bformat(
3036 _("Last view on document %1$s is being closed.\n"
3037 "Would you like to close or hide the document?\n"
3039 "Hidden documents can be displayed back through\n"
3040 "the menu: View->Hidden->...\n"
3042 "To remove this question, set your preference in:\n"
3043 " Tools->Preferences->Look&Feel->UserInterface\n"
3045 int ret = Alert::prompt(_("Close or hide document?"),
3046 text, 0, 1, _("&Close"), _("&Hide"));
3047 close_buffer = (ret == 0);
3051 return closeWorkArea(wa, close_buffer);
3055 bool GuiView::closeBuffer()
3057 GuiWorkArea * wa = currentMainWorkArea();
3058 // coverity complained about this
3059 // it seems unnecessary, but perhaps is worth the check
3060 LASSERT(wa, return false);
3062 setCurrentWorkArea(wa);
3063 Buffer & buf = wa->bufferView().buffer();
3064 return closeWorkArea(wa, !buf.parent());
3068 void GuiView::writeSession() const {
3069 GuiWorkArea const * active_wa = currentMainWorkArea();
3070 for (int i = 0; i < d.splitter_->count(); ++i) {
3071 TabWorkArea * twa = d.tabWorkArea(i);
3072 for (int j = 0; j < twa->count(); ++j) {
3073 GuiWorkArea * wa = twa->workArea(j);
3074 Buffer & buf = wa->bufferView().buffer();
3075 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3081 bool GuiView::closeBufferAll()
3083 // Close the workareas in all other views
3084 QList<int> const ids = guiApp->viewIds();
3085 for (int i = 0; i != ids.size(); ++i) {
3086 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3090 // Close our own workareas
3091 if (!closeWorkAreaAll())
3094 // Now close the hidden buffers. We prevent hidden buffers from being
3095 // dirty, so we can just close them.
3096 theBufferList().closeAll();
3101 bool GuiView::closeWorkAreaAll()
3103 setCurrentWorkArea(currentMainWorkArea());
3105 // We might be in a situation that there is still a tabWorkArea, but
3106 // there are no tabs anymore. This can happen when we get here after a
3107 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3108 // many TabWorkArea's have no documents anymore.
3111 // We have to call count() each time, because it can happen that
3112 // more than one splitter will disappear in one iteration (bug 5998).
3113 while (d.splitter_->count() > empty_twa) {
3114 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3116 if (twa->count() == 0)
3119 setCurrentWorkArea(twa->currentWorkArea());
3120 if (!closeTabWorkArea(twa))
3128 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3133 Buffer & buf = wa->bufferView().buffer();
3135 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3136 Alert::warning(_("Close document"),
3137 _("Document could not be closed because it is being processed by LyX."));
3142 return closeBuffer(buf);
3144 if (!inMultiTabs(wa))
3145 if (!saveBufferIfNeeded(buf, true))
3153 bool GuiView::closeBuffer(Buffer & buf)
3155 bool success = true;
3156 ListOfBuffers clist = buf.getChildren();
3157 ListOfBuffers::const_iterator it = clist.begin();
3158 ListOfBuffers::const_iterator const bend = clist.end();
3159 for (; it != bend; ++it) {
3160 Buffer * child_buf = *it;
3161 if (theBufferList().isOthersChild(&buf, child_buf)) {
3162 child_buf->setParent(0);
3166 // FIXME: should we look in other tabworkareas?
3167 // ANSWER: I don't think so. I've tested, and if the child is
3168 // open in some other window, it closes without a problem.
3169 GuiWorkArea * child_wa = workArea(*child_buf);
3172 // If we are in a close_event all children will be closed in some time,
3173 // so no need to do it here. This will ensure that the children end up
3174 // in the session file in the correct order. If we close the master
3175 // buffer, we can close or release the child buffers here too.
3177 success = closeWorkArea(child_wa, true);
3181 // In this case the child buffer is open but hidden.
3182 // Even in this case, children can be dirty (e.g.,
3183 // after a label change in the master, see #11405).
3184 // Therefore, check this
3185 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3186 // If we are in a close_event all children will be closed in some time,
3187 // so no need to do it here. This will ensure that the children end up
3188 // in the session file in the correct order. If we close the master
3189 // buffer, we can close or release the child buffers here too.
3191 // Save dirty buffers also if closing_!
3192 if (saveBufferIfNeeded(*child_buf, false)) {
3193 child_buf->removeAutosaveFile();
3194 theBufferList().release(child_buf);
3196 // Saving of dirty children has been cancelled.
3197 // Cancel the whole process.
3204 // goto bookmark to update bookmark pit.
3205 // FIXME: we should update only the bookmarks related to this buffer!
3206 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3207 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3208 guiApp->gotoBookmark(i+1, false, false);
3210 if (saveBufferIfNeeded(buf, false)) {
3211 buf.removeAutosaveFile();
3212 theBufferList().release(&buf);
3216 // open all children again to avoid a crash because of dangling
3217 // pointers (bug 6603)
3223 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3225 while (twa == d.currentTabWorkArea()) {
3226 twa->setCurrentIndex(twa->count() - 1);
3228 GuiWorkArea * wa = twa->currentWorkArea();
3229 Buffer & b = wa->bufferView().buffer();
3231 // We only want to close the buffer if the same buffer is not visible
3232 // in another view, and if this is not a child and if we are closing
3233 // a view (not a tabgroup).
3234 bool const close_buffer =
3235 !inOtherView(b) && !b.parent() && closing_;
3237 if (!closeWorkArea(wa, close_buffer))
3244 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3246 if (buf.isClean() || buf.paragraphs().empty())
3249 // Switch to this Buffer.
3255 if (buf.isUnnamed()) {
3256 file = from_utf8(buf.fileName().onlyFileName());
3259 FileName filename = buf.fileName();
3261 file = filename.displayName(30);
3262 exists = filename.exists();
3265 // Bring this window to top before asking questions.
3270 if (hiding && buf.isUnnamed()) {
3271 docstring const text = bformat(_("The document %1$s has not been "
3272 "saved yet.\n\nDo you want to save "
3273 "the document?"), file);
3274 ret = Alert::prompt(_("Save new document?"),
3275 text, 0, 1, _("&Save"), _("&Cancel"));
3279 docstring const text = exists ?
3280 bformat(_("The document %1$s has unsaved changes."
3281 "\n\nDo you want to save the document or "
3282 "discard the changes?"), file) :
3283 bformat(_("The document %1$s has not been saved yet."
3284 "\n\nDo you want to save the document or "
3285 "discard it entirely?"), file);
3286 docstring const title = exists ?
3287 _("Save changed document?") : _("Save document?");
3288 ret = Alert::prompt(title, text, 0, 2,
3289 _("&Save"), _("&Discard"), _("&Cancel"));
3294 if (!saveBuffer(buf))
3298 // If we crash after this we could have no autosave file
3299 // but I guess this is really improbable (Jug).
3300 // Sometimes improbable things happen:
3301 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3302 // buf.removeAutosaveFile();
3304 // revert all changes
3315 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3317 Buffer & buf = wa->bufferView().buffer();
3319 for (int i = 0; i != d.splitter_->count(); ++i) {
3320 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3321 if (wa_ && wa_ != wa)
3324 return inOtherView(buf);
3328 bool GuiView::inOtherView(Buffer & buf)
3330 QList<int> const ids = guiApp->viewIds();
3332 for (int i = 0; i != ids.size(); ++i) {
3336 if (guiApp->view(ids[i]).workArea(buf))
3343 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3345 if (!documentBufferView())
3348 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3349 Buffer * const curbuf = &documentBufferView()->buffer();
3350 int nwa = twa->count();
3351 for (int i = 0; i < nwa; ++i) {
3352 if (&workArea(i)->bufferView().buffer() == curbuf) {
3354 if (np == NEXTBUFFER)
3355 next_index = (i == nwa - 1 ? 0 : i + 1);
3357 next_index = (i == 0 ? nwa - 1 : i - 1);
3359 twa->moveTab(i, next_index);
3361 setBuffer(&workArea(next_index)->bufferView().buffer());
3369 /// make sure the document is saved
3370 static bool ensureBufferClean(Buffer * buffer)
3372 LASSERT(buffer, return false);
3373 if (buffer->isClean() && !buffer->isUnnamed())
3376 docstring const file = buffer->fileName().displayName(30);
3379 if (!buffer->isUnnamed()) {
3380 text = bformat(_("The document %1$s has unsaved "
3381 "changes.\n\nDo you want to save "
3382 "the document?"), file);
3383 title = _("Save changed document?");
3386 text = bformat(_("The document %1$s has not been "
3387 "saved yet.\n\nDo you want to save "
3388 "the document?"), file);
3389 title = _("Save new document?");
3391 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3394 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3396 return buffer->isClean() && !buffer->isUnnamed();
3400 bool GuiView::reloadBuffer(Buffer & buf)
3402 currentBufferView()->cursor().reset();
3403 Buffer::ReadStatus status = buf.reload();
3404 return status == Buffer::ReadSuccess;
3408 void GuiView::checkExternallyModifiedBuffers()
3410 BufferList::iterator bit = theBufferList().begin();
3411 BufferList::iterator const bend = theBufferList().end();
3412 for (; bit != bend; ++bit) {
3413 Buffer * buf = *bit;
3414 if (buf->fileName().exists() && buf->isChecksumModified()) {
3415 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3416 " Reload now? Any local changes will be lost."),
3417 from_utf8(buf->absFileName()));
3418 int const ret = Alert::prompt(_("Reload externally changed document?"),
3419 text, 0, 1, _("&Reload"), _("&Cancel"));
3427 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3429 Buffer * buffer = documentBufferView()
3430 ? &(documentBufferView()->buffer()) : 0;
3432 switch (cmd.action()) {
3433 case LFUN_VC_REGISTER:
3434 if (!buffer || !ensureBufferClean(buffer))
3436 if (!buffer->lyxvc().inUse()) {
3437 if (buffer->lyxvc().registrer()) {
3438 reloadBuffer(*buffer);
3439 dr.clearMessageUpdate();
3444 case LFUN_VC_RENAME:
3445 case LFUN_VC_COPY: {
3446 if (!buffer || !ensureBufferClean(buffer))
3448 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3449 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3450 // Some changes are not yet committed.
3451 // We test here and not in getStatus(), since
3452 // this test is expensive.
3454 LyXVC::CommandResult ret =
3455 buffer->lyxvc().checkIn(log);
3457 if (ret == LyXVC::ErrorCommand ||
3458 ret == LyXVC::VCSuccess)
3459 reloadBuffer(*buffer);
3460 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3461 frontend::Alert::error(
3462 _("Revision control error."),
3463 _("Document could not be checked in."));
3467 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3468 LV_VC_RENAME : LV_VC_COPY;
3469 renameBuffer(*buffer, cmd.argument(), kind);
3474 case LFUN_VC_CHECK_IN:
3475 if (!buffer || !ensureBufferClean(buffer))
3477 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3479 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3481 // Only skip reloading if the checkin was cancelled or
3482 // an error occurred before the real checkin VCS command
3483 // was executed, since the VCS might have changed the
3484 // file even if it could not checkin successfully.
3485 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3486 reloadBuffer(*buffer);
3490 case LFUN_VC_CHECK_OUT:
3491 if (!buffer || !ensureBufferClean(buffer))
3493 if (buffer->lyxvc().inUse()) {
3494 dr.setMessage(buffer->lyxvc().checkOut());
3495 reloadBuffer(*buffer);
3499 case LFUN_VC_LOCKING_TOGGLE:
3500 LASSERT(buffer, return);
3501 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3503 if (buffer->lyxvc().inUse()) {
3504 string res = buffer->lyxvc().lockingToggle();
3506 frontend::Alert::error(_("Revision control error."),
3507 _("Error when setting the locking property."));
3510 reloadBuffer(*buffer);
3515 case LFUN_VC_REVERT:
3516 LASSERT(buffer, return);
3517 if (buffer->lyxvc().revert()) {
3518 reloadBuffer(*buffer);
3519 dr.clearMessageUpdate();
3523 case LFUN_VC_UNDO_LAST:
3524 LASSERT(buffer, return);
3525 buffer->lyxvc().undoLast();
3526 reloadBuffer(*buffer);
3527 dr.clearMessageUpdate();
3530 case LFUN_VC_REPO_UPDATE:
3531 LASSERT(buffer, return);
3532 if (ensureBufferClean(buffer)) {
3533 dr.setMessage(buffer->lyxvc().repoUpdate());
3534 checkExternallyModifiedBuffers();
3538 case LFUN_VC_COMMAND: {
3539 string flag = cmd.getArg(0);
3540 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3543 if (contains(flag, 'M')) {
3544 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3547 string path = cmd.getArg(1);
3548 if (contains(path, "$$p") && buffer)
3549 path = subst(path, "$$p", buffer->filePath());
3550 LYXERR(Debug::LYXVC, "Directory: " << path);
3552 if (!pp.isReadableDirectory()) {
3553 lyxerr << _("Directory is not accessible.") << endl;
3556 support::PathChanger p(pp);
3558 string command = cmd.getArg(2);
3559 if (command.empty())
3562 command = subst(command, "$$i", buffer->absFileName());
3563 command = subst(command, "$$p", buffer->filePath());
3565 command = subst(command, "$$m", to_utf8(message));
3566 LYXERR(Debug::LYXVC, "Command: " << command);
3568 one.startscript(Systemcall::Wait, command);
3572 if (contains(flag, 'I'))
3573 buffer->markDirty();
3574 if (contains(flag, 'R'))
3575 reloadBuffer(*buffer);
3580 case LFUN_VC_COMPARE: {
3581 if (cmd.argument().empty()) {
3582 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3586 string rev1 = cmd.getArg(0);
3591 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3594 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3595 f2 = buffer->absFileName();
3597 string rev2 = cmd.getArg(1);
3601 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3605 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3606 f1 << "\n" << f2 << "\n" );
3607 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3608 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3618 void GuiView::openChildDocument(string const & fname)
3620 LASSERT(documentBufferView(), return);
3621 Buffer & buffer = documentBufferView()->buffer();
3622 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3623 documentBufferView()->saveBookmark(false);
3625 if (theBufferList().exists(filename)) {
3626 child = theBufferList().getBuffer(filename);
3629 message(bformat(_("Opening child document %1$s..."),
3630 makeDisplayPath(filename.absFileName())));
3631 child = loadDocument(filename, false);
3633 // Set the parent name of the child document.
3634 // This makes insertion of citations and references in the child work,
3635 // when the target is in the parent or another child document.
3637 child->setParent(&buffer);
3641 bool GuiView::goToFileRow(string const & argument)
3645 size_t i = argument.find_last_of(' ');
3646 if (i != string::npos) {
3647 file_name = os::internal_path(trim(argument.substr(0, i)));
3648 istringstream is(argument.substr(i + 1));
3653 if (i == string::npos) {
3654 LYXERR0("Wrong argument: " << argument);
3658 string const abstmp = package().temp_dir().absFileName();
3659 string const realtmp = package().temp_dir().realPath();
3660 // We have to use os::path_prefix_is() here, instead of
3661 // simply prefixIs(), because the file name comes from
3662 // an external application and may need case adjustment.
3663 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3664 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3665 // Needed by inverse dvi search. If it is a file
3666 // in tmpdir, call the apropriated function.
3667 // If tmpdir is a symlink, we may have the real
3668 // path passed back, so we correct for that.
3669 if (!prefixIs(file_name, abstmp))
3670 file_name = subst(file_name, realtmp, abstmp);
3671 buf = theBufferList().getBufferFromTmp(file_name);
3673 // Must replace extension of the file to be .lyx
3674 // and get full path
3675 FileName const s = fileSearch(string(),
3676 support::changeExtension(file_name, ".lyx"), "lyx");
3677 // Either change buffer or load the file
3678 if (theBufferList().exists(s))
3679 buf = theBufferList().getBuffer(s);
3680 else if (s.exists()) {
3681 buf = loadDocument(s);
3686 _("File does not exist: %1$s"),
3687 makeDisplayPath(file_name)));
3693 _("No buffer for file: %1$s."),
3694 makeDisplayPath(file_name))
3699 bool success = documentBufferView()->setCursorFromRow(row);
3701 LYXERR(Debug::LATEX,
3702 "setCursorFromRow: invalid position for row " << row);
3703 frontend::Alert::error(_("Inverse Search Failed"),
3704 _("Invalid position requested by inverse search.\n"
3705 "You may need to update the viewed document."));
3711 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3713 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3714 menu->exec(QCursor::pos());
3719 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3720 Buffer const * orig, Buffer * clone, string const & format)
3722 Buffer::ExportStatus const status = func(format);
3724 // the cloning operation will have produced a clone of the entire set of
3725 // documents, starting from the master. so we must delete those.
3726 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3728 busyBuffers.remove(orig);
3733 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3734 Buffer const * orig, Buffer * clone, string const & format)
3736 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3738 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3742 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3743 Buffer const * orig, Buffer * clone, string const & format)
3745 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3747 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3751 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3752 Buffer const * orig, Buffer * clone, string const & format)
3754 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3756 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3760 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3761 string const & argument,
3762 Buffer const * used_buffer,
3763 docstring const & msg,
3764 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3765 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3766 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3772 string format = argument;
3774 format = used_buffer->params().getDefaultOutputFormat();
3775 processing_format = format;
3777 progress_->clearMessages();
3780 #if EXPORT_in_THREAD
3782 GuiViewPrivate::busyBuffers.insert(used_buffer);
3783 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3784 if (!cloned_buffer) {
3785 Alert::error(_("Export Error"),
3786 _("Error cloning the Buffer."));
3789 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3794 setPreviewFuture(f);
3795 last_export_format = used_buffer->params().bufferFormat();
3798 // We are asynchronous, so we don't know here anything about the success
3801 Buffer::ExportStatus status;
3803 status = (used_buffer->*syncFunc)(format, false);
3804 } else if (previewFunc) {
3805 status = (used_buffer->*previewFunc)(format);
3808 handleExportStatus(gv_, status, format);
3810 return (status == Buffer::ExportSuccess
3811 || status == Buffer::PreviewSuccess);
3815 Buffer::ExportStatus status;
3817 status = (used_buffer->*syncFunc)(format, true);
3818 } else if (previewFunc) {
3819 status = (used_buffer->*previewFunc)(format);
3822 handleExportStatus(gv_, status, format);
3824 return (status == Buffer::ExportSuccess
3825 || status == Buffer::PreviewSuccess);
3829 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3831 BufferView * bv = currentBufferView();
3832 LASSERT(bv, return);
3834 // Let the current BufferView dispatch its own actions.
3835 bv->dispatch(cmd, dr);
3836 if (dr.dispatched()) {
3837 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3838 updateDialog("document", "");
3842 // Try with the document BufferView dispatch if any.
3843 BufferView * doc_bv = documentBufferView();
3844 if (doc_bv && doc_bv != bv) {
3845 doc_bv->dispatch(cmd, dr);
3846 if (dr.dispatched()) {
3847 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3848 updateDialog("document", "");
3853 // Then let the current Cursor dispatch its own actions.
3854 bv->cursor().dispatch(cmd);
3856 // update completion. We do it here and not in
3857 // processKeySym to avoid another redraw just for a
3858 // changed inline completion
3859 if (cmd.origin() == FuncRequest::KEYBOARD) {
3860 if (cmd.action() == LFUN_SELF_INSERT
3861 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3862 updateCompletion(bv->cursor(), true, true);
3863 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3864 updateCompletion(bv->cursor(), false, true);
3866 updateCompletion(bv->cursor(), false, false);
3869 dr = bv->cursor().result();
3873 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3875 BufferView * bv = currentBufferView();
3876 // By default we won't need any update.
3877 dr.screenUpdate(Update::None);
3878 // assume cmd will be dispatched
3879 dr.dispatched(true);
3881 Buffer * doc_buffer = documentBufferView()
3882 ? &(documentBufferView()->buffer()) : 0;
3884 if (cmd.origin() == FuncRequest::TOC) {
3885 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3886 // FIXME: do we need to pass a DispatchResult object here?
3887 toc->doDispatch(bv->cursor(), cmd);
3891 string const argument = to_utf8(cmd.argument());
3893 switch(cmd.action()) {
3894 case LFUN_BUFFER_CHILD_OPEN:
3895 openChildDocument(to_utf8(cmd.argument()));
3898 case LFUN_BUFFER_IMPORT:
3899 importDocument(to_utf8(cmd.argument()));
3902 case LFUN_MASTER_BUFFER_EXPORT:
3904 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3906 case LFUN_BUFFER_EXPORT: {
3909 // GCC only sees strfwd.h when building merged
3910 if (::lyx::operator==(cmd.argument(), "custom")) {
3911 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3912 // so the following test should not be needed.
3913 // In principle, we could try to switch to such a view...
3914 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3915 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3919 string const dest = cmd.getArg(1);
3920 FileName target_dir;
3921 if (!dest.empty() && FileName::isAbsolute(dest))
3922 target_dir = FileName(support::onlyPath(dest));
3924 target_dir = doc_buffer->fileName().onlyPath();
3926 string const format = (argument.empty() || argument == "default") ?
3927 doc_buffer->params().getDefaultOutputFormat() : argument;
3929 if ((dest.empty() && doc_buffer->isUnnamed())
3930 || !target_dir.isDirWritable()) {
3931 exportBufferAs(*doc_buffer, from_utf8(format));
3934 /* TODO/Review: Is it a problem to also export the children?
3935 See the update_unincluded flag */
3936 d.asyncBufferProcessing(format,
3939 &GuiViewPrivate::exportAndDestroy,
3941 0, cmd.allowAsync());
3942 // TODO Inform user about success
3946 case LFUN_BUFFER_EXPORT_AS: {
3947 LASSERT(doc_buffer, break);
3948 docstring f = cmd.argument();
3950 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3951 exportBufferAs(*doc_buffer, f);
3955 case LFUN_BUFFER_UPDATE: {
3956 d.asyncBufferProcessing(argument,
3959 &GuiViewPrivate::compileAndDestroy,
3961 0, cmd.allowAsync());
3964 case LFUN_BUFFER_VIEW: {
3965 d.asyncBufferProcessing(argument,
3967 _("Previewing ..."),
3968 &GuiViewPrivate::previewAndDestroy,
3970 &Buffer::preview, cmd.allowAsync());
3973 case LFUN_MASTER_BUFFER_UPDATE: {
3974 d.asyncBufferProcessing(argument,
3975 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3977 &GuiViewPrivate::compileAndDestroy,
3979 0, cmd.allowAsync());
3982 case LFUN_MASTER_BUFFER_VIEW: {
3983 d.asyncBufferProcessing(argument,
3984 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3986 &GuiViewPrivate::previewAndDestroy,
3987 0, &Buffer::preview, cmd.allowAsync());
3990 case LFUN_EXPORT_CANCEL: {
3991 Systemcall::killscript();
3994 case LFUN_BUFFER_SWITCH: {
3995 string const file_name = to_utf8(cmd.argument());
3996 if (!FileName::isAbsolute(file_name)) {
3998 dr.setMessage(_("Absolute filename expected."));
4002 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
4005 dr.setMessage(_("Document not loaded"));
4009 // Do we open or switch to the buffer in this view ?
4010 if (workArea(*buffer)
4011 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4016 // Look for the buffer in other views
4017 QList<int> const ids = guiApp->viewIds();
4019 for (; i != ids.size(); ++i) {
4020 GuiView & gv = guiApp->view(ids[i]);
4021 if (gv.workArea(*buffer)) {
4023 gv.activateWindow();
4025 gv.setBuffer(buffer);
4030 // If necessary, open a new window as a last resort
4031 if (i == ids.size()) {
4032 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4038 case LFUN_BUFFER_NEXT:
4039 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4042 case LFUN_BUFFER_MOVE_NEXT:
4043 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4046 case LFUN_BUFFER_PREVIOUS:
4047 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4050 case LFUN_BUFFER_MOVE_PREVIOUS:
4051 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4054 case LFUN_BUFFER_CHKTEX:
4055 LASSERT(doc_buffer, break);
4056 doc_buffer->runChktex();
4059 case LFUN_COMMAND_EXECUTE: {
4060 command_execute_ = true;
4061 minibuffer_focus_ = true;
4064 case LFUN_DROP_LAYOUTS_CHOICE:
4065 d.layout_->showPopup();
4068 case LFUN_MENU_OPEN:
4069 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4070 menu->exec(QCursor::pos());
4073 case LFUN_FILE_INSERT: {
4074 if (cmd.getArg(1) == "ignorelang")
4075 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4077 insertLyXFile(cmd.argument());
4081 case LFUN_FILE_INSERT_PLAINTEXT:
4082 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4083 string const fname = to_utf8(cmd.argument());
4084 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4085 dr.setMessage(_("Absolute filename expected."));
4089 FileName filename(fname);
4090 if (fname.empty()) {
4091 FileDialog dlg(qt_("Select file to insert"));
4093 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4094 QStringList(qt_("All Files (*)")));
4096 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4097 dr.setMessage(_("Canceled."));
4101 filename.set(fromqstr(result.second));
4105 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4106 bv->dispatch(new_cmd, dr);
4111 case LFUN_BUFFER_RELOAD: {
4112 LASSERT(doc_buffer, break);
4115 bool drop = (cmd.argument() == "dump");
4118 if (!drop && !doc_buffer->isClean()) {
4119 docstring const file =
4120 makeDisplayPath(doc_buffer->absFileName(), 20);
4121 if (doc_buffer->notifiesExternalModification()) {
4122 docstring text = _("The current version will be lost. "
4123 "Are you sure you want to load the version on disk "
4124 "of the document %1$s?");
4125 ret = Alert::prompt(_("Reload saved document?"),
4126 bformat(text, file), 1, 1,
4127 _("&Reload"), _("&Cancel"));
4129 docstring text = _("Any changes will be lost. "
4130 "Are you sure you want to revert to the saved version "
4131 "of the document %1$s?");
4132 ret = Alert::prompt(_("Revert to saved document?"),
4133 bformat(text, file), 1, 1,
4134 _("&Revert"), _("&Cancel"));
4139 doc_buffer->markClean();
4140 reloadBuffer(*doc_buffer);
4141 dr.forceBufferUpdate();
4146 case LFUN_BUFFER_RESET_EXPORT:
4147 LASSERT(doc_buffer, break);
4148 doc_buffer->requireFreshStart(true);
4149 dr.setMessage(_("Buffer export reset."));
4152 case LFUN_BUFFER_WRITE:
4153 LASSERT(doc_buffer, break);
4154 saveBuffer(*doc_buffer);
4157 case LFUN_BUFFER_WRITE_AS:
4158 LASSERT(doc_buffer, break);
4159 renameBuffer(*doc_buffer, cmd.argument());
4162 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4163 LASSERT(doc_buffer, break);
4164 renameBuffer(*doc_buffer, cmd.argument(),
4165 LV_WRITE_AS_TEMPLATE);
4168 case LFUN_BUFFER_WRITE_ALL: {
4169 Buffer * first = theBufferList().first();
4172 message(_("Saving all documents..."));
4173 // We cannot use a for loop as the buffer list cycles.
4176 if (!b->isClean()) {
4178 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4180 b = theBufferList().next(b);
4181 } while (b != first);
4182 dr.setMessage(_("All documents saved."));
4186 case LFUN_MASTER_BUFFER_FORALL: {
4190 FuncRequest funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
4191 funcToRun.allowAsync(false);
4193 for (Buffer const * buf : doc_buffer->allRelatives()) {
4194 // Switch to other buffer view and resend cmd
4195 lyx::dispatch(FuncRequest(
4196 LFUN_BUFFER_SWITCH, buf->absFileName()));
4197 lyx::dispatch(funcToRun);
4200 lyx::dispatch(FuncRequest(
4201 LFUN_BUFFER_SWITCH, doc_buffer->absFileName()));
4205 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4206 LASSERT(doc_buffer, break);
4207 doc_buffer->clearExternalModification();
4210 case LFUN_BUFFER_CLOSE:
4214 case LFUN_BUFFER_CLOSE_ALL:
4218 case LFUN_DEVEL_MODE_TOGGLE:
4219 devel_mode_ = !devel_mode_;
4221 dr.setMessage(_("Developer mode is now enabled."));
4223 dr.setMessage(_("Developer mode is now disabled."));
4226 case LFUN_TOOLBAR_TOGGLE: {
4227 string const name = cmd.getArg(0);
4228 if (GuiToolbar * t = toolbar(name))
4233 case LFUN_TOOLBAR_MOVABLE: {
4234 string const name = cmd.getArg(0);
4236 // toggle (all) toolbars movablility
4237 toolbarsMovable_ = !toolbarsMovable_;
4238 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4239 GuiToolbar * tb = toolbar(ti.name);
4240 if (tb && tb->isMovable() != toolbarsMovable_)
4241 // toggle toolbar movablity if it does not fit lock
4242 // (all) toolbars positions state silent = true, since
4243 // status bar notifications are slow
4246 if (toolbarsMovable_)
4247 dr.setMessage(_("Toolbars unlocked."));
4249 dr.setMessage(_("Toolbars locked."));
4250 } else if (GuiToolbar * t = toolbar(name)) {
4251 // toggle current toolbar movablity
4253 // update lock (all) toolbars positions
4254 updateLockToolbars();
4259 case LFUN_ICON_SIZE: {
4260 QSize size = d.iconSize(cmd.argument());
4262 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4263 size.width(), size.height()));
4267 case LFUN_DIALOG_UPDATE: {
4268 string const name = to_utf8(cmd.argument());
4269 if (name == "prefs" || name == "document")
4270 updateDialog(name, string());
4271 else if (name == "paragraph")
4272 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4273 else if (currentBufferView()) {
4274 Inset * inset = currentBufferView()->editedInset(name);
4275 // Can only update a dialog connected to an existing inset
4277 // FIXME: get rid of this indirection; GuiView ask the inset
4278 // if he is kind enough to update itself...
4279 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4280 //FIXME: pass DispatchResult here?
4281 inset->dispatch(currentBufferView()->cursor(), fr);
4287 case LFUN_DIALOG_TOGGLE: {
4288 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4289 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4290 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4294 case LFUN_DIALOG_DISCONNECT_INSET:
4295 disconnectDialog(to_utf8(cmd.argument()));
4298 case LFUN_DIALOG_HIDE: {
4299 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4303 case LFUN_DIALOG_SHOW: {
4304 string const name = cmd.getArg(0);
4305 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4307 if (name == "latexlog") {
4308 // gettatus checks that
4309 LATTEST(doc_buffer);
4310 Buffer::LogType type;
4311 string const logfile = doc_buffer->logName(&type);
4313 case Buffer::latexlog:
4316 case Buffer::buildlog:
4317 sdata = "literate ";
4320 sdata += Lexer::quoteString(logfile);
4321 showDialog("log", sdata);
4322 } else if (name == "vclog") {
4323 // getStatus checks that
4324 LATTEST(doc_buffer);
4325 string const sdata2 = "vc " +
4326 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4327 showDialog("log", sdata2);
4328 } else if (name == "symbols") {
4329 sdata = bv->cursor().getEncoding()->name();
4331 showDialog("symbols", sdata);
4333 } else if (name == "prefs" && isFullScreen()) {
4334 lfunUiToggle("fullscreen");
4335 showDialog("prefs", sdata);
4337 showDialog(name, sdata);
4342 dr.setMessage(cmd.argument());
4345 case LFUN_UI_TOGGLE: {
4346 string arg = cmd.getArg(0);
4347 if (!lfunUiToggle(arg)) {
4348 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4349 dr.setMessage(bformat(msg, from_utf8(arg)));
4351 // Make sure the keyboard focus stays in the work area.
4356 case LFUN_VIEW_SPLIT: {
4357 LASSERT(doc_buffer, break);
4358 string const orientation = cmd.getArg(0);
4359 d.splitter_->setOrientation(orientation == "vertical"
4360 ? Qt::Vertical : Qt::Horizontal);
4361 TabWorkArea * twa = addTabWorkArea();
4362 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4363 setCurrentWorkArea(wa);
4366 case LFUN_TAB_GROUP_CLOSE:
4367 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4368 closeTabWorkArea(twa);
4369 d.current_work_area_ = 0;
4370 twa = d.currentTabWorkArea();
4371 // Switch to the next GuiWorkArea in the found TabWorkArea.
4373 // Make sure the work area is up to date.
4374 setCurrentWorkArea(twa->currentWorkArea());
4376 setCurrentWorkArea(0);
4381 case LFUN_VIEW_CLOSE:
4382 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4383 closeWorkArea(twa->currentWorkArea());
4384 d.current_work_area_ = 0;
4385 twa = d.currentTabWorkArea();
4386 // Switch to the next GuiWorkArea in the found TabWorkArea.
4388 // Make sure the work area is up to date.
4389 setCurrentWorkArea(twa->currentWorkArea());
4391 setCurrentWorkArea(0);
4396 case LFUN_COMPLETION_INLINE:
4397 if (d.current_work_area_)
4398 d.current_work_area_->completer().showInline();
4401 case LFUN_COMPLETION_POPUP:
4402 if (d.current_work_area_)
4403 d.current_work_area_->completer().showPopup();
4408 if (d.current_work_area_)
4409 d.current_work_area_->completer().tab();
4412 case LFUN_COMPLETION_CANCEL:
4413 if (d.current_work_area_) {
4414 if (d.current_work_area_->completer().popupVisible())
4415 d.current_work_area_->completer().hidePopup();
4417 d.current_work_area_->completer().hideInline();
4421 case LFUN_COMPLETION_ACCEPT:
4422 if (d.current_work_area_)
4423 d.current_work_area_->completer().activate();
4426 case LFUN_BUFFER_ZOOM_IN:
4427 case LFUN_BUFFER_ZOOM_OUT:
4428 case LFUN_BUFFER_ZOOM: {
4429 if (cmd.argument().empty()) {
4430 if (cmd.action() == LFUN_BUFFER_ZOOM)
4432 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4437 if (cmd.action() == LFUN_BUFFER_ZOOM)
4438 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4439 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4440 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4442 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4445 // Actual zoom value: default zoom + fractional extra value
4446 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4447 if (zoom < static_cast<int>(zoom_min_))
4450 lyxrc.currentZoom = zoom;
4452 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4453 lyxrc.currentZoom, lyxrc.defaultZoom));
4455 guiApp->fontLoader().update();
4456 dr.screenUpdate(Update::Force | Update::FitCursor);
4460 case LFUN_VC_REGISTER:
4461 case LFUN_VC_RENAME:
4463 case LFUN_VC_CHECK_IN:
4464 case LFUN_VC_CHECK_OUT:
4465 case LFUN_VC_REPO_UPDATE:
4466 case LFUN_VC_LOCKING_TOGGLE:
4467 case LFUN_VC_REVERT:
4468 case LFUN_VC_UNDO_LAST:
4469 case LFUN_VC_COMMAND:
4470 case LFUN_VC_COMPARE:
4471 dispatchVC(cmd, dr);
4474 case LFUN_SERVER_GOTO_FILE_ROW:
4475 if(goToFileRow(to_utf8(cmd.argument())))
4476 dr.screenUpdate(Update::Force | Update::FitCursor);
4479 case LFUN_LYX_ACTIVATE:
4483 case LFUN_FORWARD_SEARCH: {
4484 // it seems safe to assume we have a document buffer, since
4485 // getStatus wants one.
4486 LATTEST(doc_buffer);
4487 Buffer const * doc_master = doc_buffer->masterBuffer();
4488 FileName const path(doc_master->temppath());
4489 string const texname = doc_master->isChild(doc_buffer)
4490 ? DocFileName(changeExtension(
4491 doc_buffer->absFileName(),
4492 "tex")).mangledFileName()
4493 : doc_buffer->latexName();
4494 string const fulltexname =
4495 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4496 string const mastername =
4497 removeExtension(doc_master->latexName());
4498 FileName const dviname(addName(path.absFileName(),
4499 addExtension(mastername, "dvi")));
4500 FileName const pdfname(addName(path.absFileName(),
4501 addExtension(mastername, "pdf")));
4502 bool const have_dvi = dviname.exists();
4503 bool const have_pdf = pdfname.exists();
4504 if (!have_dvi && !have_pdf) {
4505 dr.setMessage(_("Please, preview the document first."));
4508 string outname = dviname.onlyFileName();
4509 string command = lyxrc.forward_search_dvi;
4510 if (!have_dvi || (have_pdf &&
4511 pdfname.lastModified() > dviname.lastModified())) {
4512 outname = pdfname.onlyFileName();
4513 command = lyxrc.forward_search_pdf;
4516 DocIterator cur = bv->cursor();
4517 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4518 LYXERR(Debug::ACTION, "Forward search: row:" << row
4520 if (row == -1 || command.empty()) {
4521 dr.setMessage(_("Couldn't proceed."));
4524 string texrow = convert<string>(row);
4526 command = subst(command, "$$n", texrow);
4527 command = subst(command, "$$f", fulltexname);
4528 command = subst(command, "$$t", texname);
4529 command = subst(command, "$$o", outname);
4531 volatile PathChanger p(path);
4533 one.startscript(Systemcall::DontWait, command);
4537 case LFUN_SPELLING_CONTINUOUSLY:
4538 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4539 dr.screenUpdate(Update::Force);
4543 // The LFUN must be for one of BufferView, Buffer or Cursor;
4545 dispatchToBufferView(cmd, dr);
4549 // Part of automatic menu appearance feature.
4550 if (isFullScreen()) {
4551 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4555 // Need to update bv because many LFUNs here might have destroyed it
4556 bv = currentBufferView();
4558 // Clear non-empty selections
4559 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4561 Cursor & cur = bv->cursor();
4562 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4563 cur.clearSelection();
4569 bool GuiView::lfunUiToggle(string const & ui_component)
4571 if (ui_component == "scrollbar") {
4572 // hide() is of no help
4573 if (d.current_work_area_->verticalScrollBarPolicy() ==
4574 Qt::ScrollBarAlwaysOff)
4576 d.current_work_area_->setVerticalScrollBarPolicy(
4577 Qt::ScrollBarAsNeeded);
4579 d.current_work_area_->setVerticalScrollBarPolicy(
4580 Qt::ScrollBarAlwaysOff);
4581 } else if (ui_component == "statusbar") {
4582 statusBar()->setVisible(!statusBar()->isVisible());
4583 } else if (ui_component == "menubar") {
4584 menuBar()->setVisible(!menuBar()->isVisible());
4586 if (ui_component == "frame") {
4588 getContentsMargins(&l, &t, &r, &b);
4589 //are the frames in default state?
4590 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4592 setContentsMargins(-2, -2, -2, -2);
4594 setContentsMargins(0, 0, 0, 0);
4597 if (ui_component == "fullscreen") {
4605 void GuiView::toggleFullScreen()
4607 if (isFullScreen()) {
4608 for (int i = 0; i != d.splitter_->count(); ++i)
4609 d.tabWorkArea(i)->setFullScreen(false);
4610 setContentsMargins(0, 0, 0, 0);
4611 setWindowState(windowState() ^ Qt::WindowFullScreen);
4614 statusBar()->show();
4617 hideDialogs("prefs", 0);
4618 for (int i = 0; i != d.splitter_->count(); ++i)
4619 d.tabWorkArea(i)->setFullScreen(true);
4620 setContentsMargins(-2, -2, -2, -2);
4622 setWindowState(windowState() ^ Qt::WindowFullScreen);
4623 if (lyxrc.full_screen_statusbar)
4624 statusBar()->hide();
4625 if (lyxrc.full_screen_menubar)
4627 if (lyxrc.full_screen_toolbars) {
4628 ToolbarMap::iterator end = d.toolbars_.end();
4629 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4634 // give dialogs like the TOC a chance to adapt
4639 Buffer const * GuiView::updateInset(Inset const * inset)
4644 Buffer const * inset_buffer = &(inset->buffer());
4646 for (int i = 0; i != d.splitter_->count(); ++i) {
4647 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4650 Buffer const * buffer = &(wa->bufferView().buffer());
4651 if (inset_buffer == buffer)
4652 wa->scheduleRedraw(true);
4654 return inset_buffer;
4658 void GuiView::restartCaret()
4660 /* When we move around, or type, it's nice to be able to see
4661 * the caret immediately after the keypress.
4663 if (d.current_work_area_)
4664 d.current_work_area_->startBlinkingCaret();
4666 // Take this occasion to update the other GUI elements.
4672 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4674 if (d.current_work_area_)
4675 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4680 // This list should be kept in sync with the list of insets in
4681 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4682 // dialog should have the same name as the inset.
4683 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4684 // docs in LyXAction.cpp.
4686 char const * const dialognames[] = {
4688 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4689 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4690 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4691 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4692 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4693 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4694 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4695 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4697 char const * const * const end_dialognames =
4698 dialognames + (sizeof(dialognames) / sizeof(char *));
4702 cmpCStr(char const * name) : name_(name) {}
4703 bool operator()(char const * other) {
4704 return strcmp(other, name_) == 0;
4711 bool isValidName(string const & name)
4713 return find_if(dialognames, end_dialognames,
4714 cmpCStr(name.c_str())) != end_dialognames;
4720 void GuiView::resetDialogs()
4722 // Make sure that no LFUN uses any GuiView.
4723 guiApp->setCurrentView(0);
4727 constructToolbars();
4728 guiApp->menus().fillMenuBar(menuBar(), this, false);
4729 d.layout_->updateContents(true);
4730 // Now update controls with current buffer.
4731 guiApp->setCurrentView(this);
4737 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4739 if (!isValidName(name))
4742 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4744 if (it != d.dialogs_.end()) {
4746 it->second->hideView();
4747 return it->second.get();
4750 Dialog * dialog = build(name);
4751 d.dialogs_[name].reset(dialog);
4752 if (lyxrc.allow_geometry_session)
4753 dialog->restoreSession();
4760 void GuiView::showDialog(string const & name, string const & sdata,
4763 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4767 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4773 const string name = fromqstr(qname);
4774 const string sdata = fromqstr(qdata);
4778 Dialog * dialog = findOrBuild(name, false);
4780 bool const visible = dialog->isVisibleView();
4781 dialog->showData(sdata);
4782 if (currentBufferView())
4783 currentBufferView()->editInset(name, inset);
4784 // We only set the focus to the new dialog if it was not yet
4785 // visible in order not to change the existing previous behaviour
4787 // activateWindow is needed for floating dockviews
4788 dialog->asQWidget()->raise();
4789 dialog->asQWidget()->activateWindow();
4790 dialog->asQWidget()->setFocus();
4794 catch (ExceptionMessage const & ex) {
4802 bool GuiView::isDialogVisible(string const & name) const
4804 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4805 if (it == d.dialogs_.end())
4807 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4811 void GuiView::hideDialog(string const & name, Inset * inset)
4813 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4814 if (it == d.dialogs_.end())
4818 if (!currentBufferView())
4820 if (inset != currentBufferView()->editedInset(name))
4824 Dialog * const dialog = it->second.get();
4825 if (dialog->isVisibleView())
4827 if (currentBufferView())
4828 currentBufferView()->editInset(name, 0);
4832 void GuiView::disconnectDialog(string const & name)
4834 if (!isValidName(name))
4836 if (currentBufferView())
4837 currentBufferView()->editInset(name, 0);
4841 void GuiView::hideAll() const
4843 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4844 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4846 for(; it != end; ++it)
4847 it->second->hideView();
4851 void GuiView::updateDialogs()
4853 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4854 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4856 for(; it != end; ++it) {
4857 Dialog * dialog = it->second.get();
4859 if (dialog->needBufferOpen() && !documentBufferView())
4860 hideDialog(fromqstr(dialog->name()), 0);
4861 else if (dialog->isVisibleView())
4862 dialog->checkStatus();
4869 Dialog * createDialog(GuiView & lv, string const & name);
4871 // will be replaced by a proper factory...
4872 Dialog * createGuiAbout(GuiView & lv);
4873 Dialog * createGuiBibtex(GuiView & lv);
4874 Dialog * createGuiChanges(GuiView & lv);
4875 Dialog * createGuiCharacter(GuiView & lv);
4876 Dialog * createGuiCitation(GuiView & lv);
4877 Dialog * createGuiCompare(GuiView & lv);
4878 Dialog * createGuiCompareHistory(GuiView & lv);
4879 Dialog * createGuiDelimiter(GuiView & lv);
4880 Dialog * createGuiDocument(GuiView & lv);
4881 Dialog * createGuiErrorList(GuiView & lv);
4882 Dialog * createGuiExternal(GuiView & lv);
4883 Dialog * createGuiGraphics(GuiView & lv);
4884 Dialog * createGuiInclude(GuiView & lv);
4885 Dialog * createGuiIndex(GuiView & lv);
4886 Dialog * createGuiListings(GuiView & lv);
4887 Dialog * createGuiLog(GuiView & lv);
4888 Dialog * createGuiLyXFiles(GuiView & lv);
4889 Dialog * createGuiMathMatrix(GuiView & lv);
4890 Dialog * createGuiNote(GuiView & lv);
4891 Dialog * createGuiParagraph(GuiView & lv);
4892 Dialog * createGuiPhantom(GuiView & lv);
4893 Dialog * createGuiPreferences(GuiView & lv);
4894 Dialog * createGuiPrint(GuiView & lv);
4895 Dialog * createGuiPrintindex(GuiView & lv);
4896 Dialog * createGuiRef(GuiView & lv);
4897 Dialog * createGuiSearch(GuiView & lv);
4898 Dialog * createGuiSearchAdv(GuiView & lv);
4899 Dialog * createGuiSendTo(GuiView & lv);
4900 Dialog * createGuiShowFile(GuiView & lv);
4901 Dialog * createGuiSpellchecker(GuiView & lv);
4902 Dialog * createGuiSymbols(GuiView & lv);
4903 Dialog * createGuiTabularCreate(GuiView & lv);
4904 Dialog * createGuiTexInfo(GuiView & lv);
4905 Dialog * createGuiToc(GuiView & lv);
4906 Dialog * createGuiThesaurus(GuiView & lv);
4907 Dialog * createGuiViewSource(GuiView & lv);
4908 Dialog * createGuiWrap(GuiView & lv);
4909 Dialog * createGuiProgressView(GuiView & lv);
4913 Dialog * GuiView::build(string const & name)
4915 LASSERT(isValidName(name), return 0);
4917 Dialog * dialog = createDialog(*this, name);
4921 if (name == "aboutlyx")
4922 return createGuiAbout(*this);
4923 if (name == "bibtex")
4924 return createGuiBibtex(*this);
4925 if (name == "changes")
4926 return createGuiChanges(*this);
4927 if (name == "character")
4928 return createGuiCharacter(*this);
4929 if (name == "citation")
4930 return createGuiCitation(*this);
4931 if (name == "compare")
4932 return createGuiCompare(*this);
4933 if (name == "comparehistory")
4934 return createGuiCompareHistory(*this);
4935 if (name == "document")
4936 return createGuiDocument(*this);
4937 if (name == "errorlist")
4938 return createGuiErrorList(*this);
4939 if (name == "external")
4940 return createGuiExternal(*this);
4942 return createGuiShowFile(*this);
4943 if (name == "findreplace")
4944 return createGuiSearch(*this);
4945 if (name == "findreplaceadv")
4946 return createGuiSearchAdv(*this);
4947 if (name == "graphics")
4948 return createGuiGraphics(*this);
4949 if (name == "include")
4950 return createGuiInclude(*this);
4951 if (name == "index")
4952 return createGuiIndex(*this);
4953 if (name == "index_print")
4954 return createGuiPrintindex(*this);
4955 if (name == "listings")
4956 return createGuiListings(*this);
4958 return createGuiLog(*this);
4959 if (name == "lyxfiles")
4960 return createGuiLyXFiles(*this);
4961 if (name == "mathdelimiter")
4962 return createGuiDelimiter(*this);
4963 if (name == "mathmatrix")
4964 return createGuiMathMatrix(*this);
4966 return createGuiNote(*this);
4967 if (name == "paragraph")
4968 return createGuiParagraph(*this);
4969 if (name == "phantom")
4970 return createGuiPhantom(*this);
4971 if (name == "prefs")
4972 return createGuiPreferences(*this);
4974 return createGuiRef(*this);
4975 if (name == "sendto")
4976 return createGuiSendTo(*this);
4977 if (name == "spellchecker")
4978 return createGuiSpellchecker(*this);
4979 if (name == "symbols")
4980 return createGuiSymbols(*this);
4981 if (name == "tabularcreate")
4982 return createGuiTabularCreate(*this);
4983 if (name == "texinfo")
4984 return createGuiTexInfo(*this);
4985 if (name == "thesaurus")
4986 return createGuiThesaurus(*this);
4988 return createGuiToc(*this);
4989 if (name == "view-source")
4990 return createGuiViewSource(*this);
4992 return createGuiWrap(*this);
4993 if (name == "progress")
4994 return createGuiProgressView(*this);
5000 SEMenu::SEMenu(QWidget * parent)
5002 QAction * action = addAction(qt_("Disable Shell Escape"));
5003 connect(action, SIGNAL(triggered()),
5004 parent, SLOT(disableShellEscape()));
5007 } // namespace frontend
5010 #include "moc_GuiView.cpp"