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_CHILD_OPEN:
1963 enable = doc_buffer != 0;
1966 case LFUN_BUFFER_WRITE:
1967 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1970 //FIXME: This LFUN should be moved to GuiApplication.
1971 case LFUN_BUFFER_WRITE_ALL: {
1972 // We enable the command only if there are some modified buffers
1973 Buffer * first = theBufferList().first();
1978 // We cannot use a for loop as the buffer list is a cycle.
1980 if (!b->isClean()) {
1984 b = theBufferList().next(b);
1985 } while (b != first);
1989 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1990 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1993 case LFUN_BUFFER_EXPORT: {
1994 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1998 return doc_buffer->getStatus(cmd, flag);
2002 case LFUN_BUFFER_EXPORT_AS:
2003 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
2008 case LFUN_BUFFER_WRITE_AS:
2009 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
2010 enable = doc_buffer != 0;
2013 case LFUN_EXPORT_CANCEL:
2014 enable = d.processing_thread_watcher_.isRunning();
2017 case LFUN_BUFFER_CLOSE:
2018 case LFUN_VIEW_CLOSE:
2019 enable = doc_buffer != 0;
2022 case LFUN_BUFFER_CLOSE_ALL:
2023 enable = theBufferList().last() != theBufferList().first();
2026 case LFUN_BUFFER_CHKTEX: {
2027 // hide if we have no checktex command
2028 if (lyxrc.chktex_command.empty()) {
2029 flag.setUnknown(true);
2033 if (!doc_buffer || !doc_buffer->params().isLatex()
2034 || d.processing_thread_watcher_.isRunning()) {
2035 // grey out, don't hide
2043 case LFUN_VIEW_SPLIT:
2044 if (cmd.getArg(0) == "vertical")
2045 enable = doc_buffer && (d.splitter_->count() == 1 ||
2046 d.splitter_->orientation() == Qt::Vertical);
2048 enable = doc_buffer && (d.splitter_->count() == 1 ||
2049 d.splitter_->orientation() == Qt::Horizontal);
2052 case LFUN_TAB_GROUP_CLOSE:
2053 enable = d.tabWorkAreaCount() > 1;
2056 case LFUN_DEVEL_MODE_TOGGLE:
2057 flag.setOnOff(devel_mode_);
2060 case LFUN_TOOLBAR_TOGGLE: {
2061 string const name = cmd.getArg(0);
2062 if (GuiToolbar * t = toolbar(name))
2063 flag.setOnOff(t->isVisible());
2066 docstring const msg =
2067 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2073 case LFUN_TOOLBAR_MOVABLE: {
2074 string const name = cmd.getArg(0);
2075 // use negation since locked == !movable
2077 // toolbar name * locks all toolbars
2078 flag.setOnOff(!toolbarsMovable_);
2079 else if (GuiToolbar * t = toolbar(name))
2080 flag.setOnOff(!(t->isMovable()));
2083 docstring const msg =
2084 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2090 case LFUN_ICON_SIZE:
2091 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2094 case LFUN_DROP_LAYOUTS_CHOICE:
2098 case LFUN_UI_TOGGLE:
2099 flag.setOnOff(isFullScreen());
2102 case LFUN_DIALOG_DISCONNECT_INSET:
2105 case LFUN_DIALOG_HIDE:
2106 // FIXME: should we check if the dialog is shown?
2109 case LFUN_DIALOG_TOGGLE:
2110 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2113 case LFUN_DIALOG_SHOW: {
2114 string const name = cmd.getArg(0);
2116 enable = name == "aboutlyx"
2117 || name == "file" //FIXME: should be removed.
2118 || name == "lyxfiles"
2120 || name == "texinfo"
2121 || name == "progress"
2122 || name == "compare";
2123 else if (name == "character" || name == "symbols"
2124 || name == "mathdelimiter" || name == "mathmatrix") {
2125 if (!buf || buf->isReadonly())
2128 Cursor const & cur = currentBufferView()->cursor();
2129 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2132 else if (name == "latexlog")
2133 enable = FileName(doc_buffer->logName()).isReadableFile();
2134 else if (name == "spellchecker")
2135 enable = theSpellChecker()
2136 && !doc_buffer->isReadonly()
2137 && !doc_buffer->text().empty();
2138 else if (name == "vclog")
2139 enable = doc_buffer->lyxvc().inUse();
2143 case LFUN_DIALOG_UPDATE: {
2144 string const name = cmd.getArg(0);
2146 enable = name == "prefs";
2150 case LFUN_COMMAND_EXECUTE:
2152 case LFUN_MENU_OPEN:
2153 // Nothing to check.
2156 case LFUN_COMPLETION_INLINE:
2157 if (!d.current_work_area_
2158 || !d.current_work_area_->completer().inlinePossible(
2159 currentBufferView()->cursor()))
2163 case LFUN_COMPLETION_POPUP:
2164 if (!d.current_work_area_
2165 || !d.current_work_area_->completer().popupPossible(
2166 currentBufferView()->cursor()))
2171 if (!d.current_work_area_
2172 || !d.current_work_area_->completer().inlinePossible(
2173 currentBufferView()->cursor()))
2177 case LFUN_COMPLETION_ACCEPT:
2178 if (!d.current_work_area_
2179 || (!d.current_work_area_->completer().popupVisible()
2180 && !d.current_work_area_->completer().inlineVisible()
2181 && !d.current_work_area_->completer().completionAvailable()))
2185 case LFUN_COMPLETION_CANCEL:
2186 if (!d.current_work_area_
2187 || (!d.current_work_area_->completer().popupVisible()
2188 && !d.current_work_area_->completer().inlineVisible()))
2192 case LFUN_BUFFER_ZOOM_OUT:
2193 case LFUN_BUFFER_ZOOM_IN: {
2194 // only diff between these two is that the default for ZOOM_OUT
2196 bool const neg_zoom =
2197 convert<int>(cmd.argument()) < 0 ||
2198 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2199 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2200 docstring const msg =
2201 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2205 enable = doc_buffer;
2209 case LFUN_BUFFER_ZOOM: {
2210 bool const less_than_min_zoom =
2211 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2212 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2213 docstring const msg =
2214 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2219 enable = doc_buffer;
2223 case LFUN_BUFFER_MOVE_NEXT:
2224 case LFUN_BUFFER_MOVE_PREVIOUS:
2225 // we do not cycle when moving
2226 case LFUN_BUFFER_NEXT:
2227 case LFUN_BUFFER_PREVIOUS:
2228 // because we cycle, it doesn't matter whether on first or last
2229 enable = (d.currentTabWorkArea()->count() > 1);
2231 case LFUN_BUFFER_SWITCH:
2232 // toggle on the current buffer, but do not toggle off
2233 // the other ones (is that a good idea?)
2235 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2236 flag.setOnOff(true);
2239 case LFUN_VC_REGISTER:
2240 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2242 case LFUN_VC_RENAME:
2243 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2246 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2248 case LFUN_VC_CHECK_IN:
2249 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2251 case LFUN_VC_CHECK_OUT:
2252 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2254 case LFUN_VC_LOCKING_TOGGLE:
2255 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2256 && doc_buffer->lyxvc().lockingToggleEnabled();
2257 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2259 case LFUN_VC_REVERT:
2260 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2261 && !doc_buffer->hasReadonlyFlag();
2263 case LFUN_VC_UNDO_LAST:
2264 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2266 case LFUN_VC_REPO_UPDATE:
2267 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2269 case LFUN_VC_COMMAND: {
2270 if (cmd.argument().empty())
2272 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2276 case LFUN_VC_COMPARE:
2277 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2280 case LFUN_SERVER_GOTO_FILE_ROW:
2281 case LFUN_LYX_ACTIVATE:
2283 case LFUN_FORWARD_SEARCH:
2284 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2287 case LFUN_FILE_INSERT_PLAINTEXT:
2288 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2289 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2292 case LFUN_SPELLING_CONTINUOUSLY:
2293 flag.setOnOff(lyxrc.spellcheck_continuously);
2301 flag.setEnabled(false);
2307 static FileName selectTemplateFile()
2309 FileDialog dlg(qt_("Select template file"));
2310 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2311 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2313 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2314 QStringList(qt_("LyX Documents (*.lyx)")));
2316 if (result.first == FileDialog::Later)
2318 if (result.second.isEmpty())
2320 return FileName(fromqstr(result.second));
2324 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2328 Buffer * newBuffer = 0;
2330 newBuffer = checkAndLoadLyXFile(filename);
2331 } catch (ExceptionMessage const & e) {
2338 message(_("Document not loaded."));
2342 setBuffer(newBuffer);
2343 newBuffer->errors("Parse");
2346 theSession().lastFiles().add(filename);
2347 theSession().writeFile();
2354 void GuiView::openDocument(string const & fname)
2356 string initpath = lyxrc.document_path;
2358 if (documentBufferView()) {
2359 string const trypath = documentBufferView()->buffer().filePath();
2360 // If directory is writeable, use this as default.
2361 if (FileName(trypath).isDirWritable())
2367 if (fname.empty()) {
2368 FileDialog dlg(qt_("Select document to open"));
2369 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2370 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2372 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2373 FileDialog::Result result =
2374 dlg.open(toqstr(initpath), filter);
2376 if (result.first == FileDialog::Later)
2379 filename = fromqstr(result.second);
2381 // check selected filename
2382 if (filename.empty()) {
2383 message(_("Canceled."));
2389 // get absolute path of file and add ".lyx" to the filename if
2391 FileName const fullname =
2392 fileSearch(string(), filename, "lyx", support::may_not_exist);
2393 if (!fullname.empty())
2394 filename = fullname.absFileName();
2396 if (!fullname.onlyPath().isDirectory()) {
2397 Alert::warning(_("Invalid filename"),
2398 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2399 from_utf8(fullname.absFileName())));
2403 // if the file doesn't exist and isn't already open (bug 6645),
2404 // let the user create one
2405 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2406 !LyXVC::file_not_found_hook(fullname)) {
2407 // the user specifically chose this name. Believe him.
2408 Buffer * const b = newFile(filename, string(), true);
2414 docstring const disp_fn = makeDisplayPath(filename);
2415 message(bformat(_("Opening document %1$s..."), disp_fn));
2418 Buffer * buf = loadDocument(fullname);
2420 str2 = bformat(_("Document %1$s opened."), disp_fn);
2421 if (buf->lyxvc().inUse())
2422 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2423 " " + _("Version control detected.");
2425 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2430 // FIXME: clean that
2431 static bool import(GuiView * lv, FileName const & filename,
2432 string const & format, ErrorList & errorList)
2434 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2436 string loader_format;
2437 vector<string> loaders = theConverters().loaders();
2438 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2439 vector<string>::const_iterator it = loaders.begin();
2440 vector<string>::const_iterator en = loaders.end();
2441 for (; it != en; ++it) {
2442 if (!theConverters().isReachable(format, *it))
2445 string const tofile =
2446 support::changeExtension(filename.absFileName(),
2447 theFormats().extension(*it));
2448 if (theConverters().convert(0, filename, FileName(tofile),
2449 filename, format, *it, errorList) != Converters::SUCCESS)
2451 loader_format = *it;
2454 if (loader_format.empty()) {
2455 frontend::Alert::error(_("Couldn't import file"),
2456 bformat(_("No information for importing the format %1$s."),
2457 theFormats().prettyName(format)));
2461 loader_format = format;
2463 if (loader_format == "lyx") {
2464 Buffer * buf = lv->loadDocument(lyxfile);
2468 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2472 bool as_paragraphs = loader_format == "textparagraph";
2473 string filename2 = (loader_format == format) ? filename.absFileName()
2474 : support::changeExtension(filename.absFileName(),
2475 theFormats().extension(loader_format));
2476 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2478 guiApp->setCurrentView(lv);
2479 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2486 void GuiView::importDocument(string const & argument)
2489 string filename = split(argument, format, ' ');
2491 LYXERR(Debug::INFO, format << " file: " << filename);
2493 // need user interaction
2494 if (filename.empty()) {
2495 string initpath = lyxrc.document_path;
2496 if (documentBufferView()) {
2497 string const trypath = documentBufferView()->buffer().filePath();
2498 // If directory is writeable, use this as default.
2499 if (FileName(trypath).isDirWritable())
2503 docstring const text = bformat(_("Select %1$s file to import"),
2504 theFormats().prettyName(format));
2506 FileDialog dlg(toqstr(text));
2507 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2508 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2510 docstring filter = theFormats().prettyName(format);
2513 filter += from_utf8(theFormats().extensions(format));
2516 FileDialog::Result result =
2517 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2519 if (result.first == FileDialog::Later)
2522 filename = fromqstr(result.second);
2524 // check selected filename
2525 if (filename.empty())
2526 message(_("Canceled."));
2529 if (filename.empty())
2532 // get absolute path of file
2533 FileName const fullname(support::makeAbsPath(filename));
2535 // Can happen if the user entered a path into the dialog
2537 if (fullname.onlyFileName().empty()) {
2538 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2539 "Aborting import."),
2540 from_utf8(fullname.absFileName()));
2541 frontend::Alert::error(_("File name error"), msg);
2542 message(_("Canceled."));
2547 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2549 // Check if the document already is open
2550 Buffer * buf = theBufferList().getBuffer(lyxfile);
2553 if (!closeBuffer()) {
2554 message(_("Canceled."));
2559 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2561 // if the file exists already, and we didn't do
2562 // -i lyx thefile.lyx, warn
2563 if (lyxfile.exists() && fullname != lyxfile) {
2565 docstring text = bformat(_("The document %1$s already exists.\n\n"
2566 "Do you want to overwrite that document?"), displaypath);
2567 int const ret = Alert::prompt(_("Overwrite document?"),
2568 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2571 message(_("Canceled."));
2576 message(bformat(_("Importing %1$s..."), displaypath));
2577 ErrorList errorList;
2578 if (import(this, fullname, format, errorList))
2579 message(_("imported."));
2581 message(_("file not imported!"));
2583 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2587 void GuiView::newDocument(string const & filename, string templatefile,
2590 FileName initpath(lyxrc.document_path);
2591 if (documentBufferView()) {
2592 FileName const trypath(documentBufferView()->buffer().filePath());
2593 // If directory is writeable, use this as default.
2594 if (trypath.isDirWritable())
2598 if (from_template) {
2599 if (templatefile.empty())
2600 templatefile = selectTemplateFile().absFileName();
2601 if (templatefile.empty())
2606 if (filename.empty())
2607 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2609 b = newFile(filename, templatefile, true);
2614 // If no new document could be created, it is unsure
2615 // whether there is a valid BufferView.
2616 if (currentBufferView())
2617 // Ensure the cursor is correctly positioned on screen.
2618 currentBufferView()->showCursor();
2622 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2624 BufferView * bv = documentBufferView();
2629 FileName filename(to_utf8(fname));
2630 if (filename.empty()) {
2631 // Launch a file browser
2633 string initpath = lyxrc.document_path;
2634 string const trypath = bv->buffer().filePath();
2635 // If directory is writeable, use this as default.
2636 if (FileName(trypath).isDirWritable())
2640 FileDialog dlg(qt_("Select LyX document to insert"));
2641 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2642 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2644 FileDialog::Result result = dlg.open(toqstr(initpath),
2645 QStringList(qt_("LyX Documents (*.lyx)")));
2647 if (result.first == FileDialog::Later)
2651 filename.set(fromqstr(result.second));
2653 // check selected filename
2654 if (filename.empty()) {
2655 // emit message signal.
2656 message(_("Canceled."));
2661 bv->insertLyXFile(filename, ignorelang);
2662 bv->buffer().errors("Parse");
2666 string const GuiView::getTemplatesPath(Buffer & b)
2668 // We start off with the user's templates path
2669 string result = addPath(package().user_support().absFileName(), "templates");
2670 // Check for the document language
2671 string const langcode = b.params().language->code();
2672 string const shortcode = langcode.substr(0, 2);
2673 if (!langcode.empty() && shortcode != "en") {
2674 string subpath = addPath(result, shortcode);
2675 string subpath_long = addPath(result, langcode);
2676 // If we have a subdirectory for the language already,
2678 FileName sp = FileName(subpath);
2679 if (sp.isDirectory())
2681 else if (FileName(subpath_long).isDirectory())
2682 result = subpath_long;
2684 // Ask whether we should create such a subdirectory
2685 docstring const text =
2686 bformat(_("It is suggested to save the template in a subdirectory\n"
2687 "appropriate to the document language (%1$s).\n"
2688 "This subdirectory does not exists yet.\n"
2689 "Do you want to create it?"),
2690 _(b.params().language->display()));
2691 if (Alert::prompt(_("Create Language Directory?"),
2692 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2693 // If the user agreed, we try to create it and report if this failed.
2694 if (!sp.createDirectory(0777))
2695 Alert::error(_("Subdirectory creation failed!"),
2696 _("Could not create subdirectory.\n"
2697 "The template will be saved in the parent directory."));
2703 // Do we have a layout category?
2704 string const cat = b.params().baseClass() ?
2705 b.params().baseClass()->category()
2708 string subpath = addPath(result, cat);
2709 // If we have a subdirectory for the category already,
2711 FileName sp = FileName(subpath);
2712 if (sp.isDirectory())
2715 // Ask whether we should create such a subdirectory
2716 docstring const text =
2717 bformat(_("It is suggested to save the template in a subdirectory\n"
2718 "appropriate to the layout category (%1$s).\n"
2719 "This subdirectory does not exists yet.\n"
2720 "Do you want to create it?"),
2722 if (Alert::prompt(_("Create Category Directory?"),
2723 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2724 // If the user agreed, we try to create it and report if this failed.
2725 if (!sp.createDirectory(0777))
2726 Alert::error(_("Subdirectory creation failed!"),
2727 _("Could not create subdirectory.\n"
2728 "The template will be saved in the parent directory."));
2738 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2740 FileName fname = b.fileName();
2741 FileName const oldname = fname;
2742 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2744 if (!newname.empty()) {
2747 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2749 fname = support::makeAbsPath(to_utf8(newname),
2750 oldname.onlyPath().absFileName());
2752 // Switch to this Buffer.
2755 // No argument? Ask user through dialog.
2757 QString const title = as_template ? qt_("Choose a filename to save template as")
2758 : qt_("Choose a filename to save document as");
2759 FileDialog dlg(title);
2760 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2761 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2763 if (!isLyXFileName(fname.absFileName()))
2764 fname.changeExtension(".lyx");
2766 string const path = as_template ?
2768 : fname.onlyPath().absFileName();
2769 FileDialog::Result result =
2770 dlg.save(toqstr(path),
2771 QStringList(qt_("LyX Documents (*.lyx)")),
2772 toqstr(fname.onlyFileName()));
2774 if (result.first == FileDialog::Later)
2777 fname.set(fromqstr(result.second));
2782 if (!isLyXFileName(fname.absFileName()))
2783 fname.changeExtension(".lyx");
2786 // fname is now the new Buffer location.
2788 // if there is already a Buffer open with this name, we do not want
2789 // to have another one. (the second test makes sure we're not just
2790 // trying to overwrite ourselves, which is fine.)
2791 if (theBufferList().exists(fname) && fname != oldname
2792 && theBufferList().getBuffer(fname) != &b) {
2793 docstring const text =
2794 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2795 "Please close it before attempting to overwrite it.\n"
2796 "Do you want to choose a new filename?"),
2797 from_utf8(fname.absFileName()));
2798 int const ret = Alert::prompt(_("Chosen File Already Open"),
2799 text, 0, 1, _("&Rename"), _("&Cancel"));
2801 case 0: return renameBuffer(b, docstring(), kind);
2802 case 1: return false;
2807 bool const existsLocal = fname.exists();
2808 bool const existsInVC = LyXVC::fileInVC(fname);
2809 if (existsLocal || existsInVC) {
2810 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2811 if (kind != LV_WRITE_AS && existsInVC) {
2812 // renaming to a name that is already in VC
2814 docstring text = bformat(_("The document %1$s "
2815 "is already registered.\n\n"
2816 "Do you want to choose a new name?"),
2818 docstring const title = (kind == LV_VC_RENAME) ?
2819 _("Rename document?") : _("Copy document?");
2820 docstring const button = (kind == LV_VC_RENAME) ?
2821 _("&Rename") : _("&Copy");
2822 int const ret = Alert::prompt(title, text, 0, 1,
2823 button, _("&Cancel"));
2825 case 0: return renameBuffer(b, docstring(), kind);
2826 case 1: return false;
2831 docstring text = bformat(_("The document %1$s "
2832 "already exists.\n\n"
2833 "Do you want to overwrite that document?"),
2835 int const ret = Alert::prompt(_("Overwrite document?"),
2836 text, 0, 2, _("&Overwrite"),
2837 _("&Rename"), _("&Cancel"));
2840 case 1: return renameBuffer(b, docstring(), kind);
2841 case 2: return false;
2847 case LV_VC_RENAME: {
2848 string msg = b.lyxvc().rename(fname);
2851 message(from_utf8(msg));
2855 string msg = b.lyxvc().copy(fname);
2858 message(from_utf8(msg));
2862 case LV_WRITE_AS_TEMPLATE:
2865 // LyXVC created the file already in case of LV_VC_RENAME or
2866 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2867 // relative paths of included stuff right if we moved e.g. from
2868 // /a/b.lyx to /a/c/b.lyx.
2870 bool const saved = saveBuffer(b, fname);
2877 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2879 FileName fname = b.fileName();
2881 FileDialog dlg(qt_("Choose a filename to export the document as"));
2882 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2885 QString const anyformat = qt_("Guess from extension (*.*)");
2888 vector<Format const *> export_formats;
2889 for (Format const & f : theFormats())
2890 if (f.documentFormat())
2891 export_formats.push_back(&f);
2892 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2893 map<QString, string> fmap;
2896 for (Format const * f : export_formats) {
2897 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2898 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2900 from_ascii(f->extension())));
2901 types << loc_filter;
2902 fmap[loc_filter] = f->name();
2903 if (from_ascii(f->name()) == iformat) {
2904 filter = loc_filter;
2905 ext = f->extension();
2908 string ofname = fname.onlyFileName();
2910 ofname = support::changeExtension(ofname, ext);
2911 FileDialog::Result result =
2912 dlg.save(toqstr(fname.onlyPath().absFileName()),
2916 if (result.first != FileDialog::Chosen)
2920 fname.set(fromqstr(result.second));
2921 if (filter == anyformat)
2922 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2924 fmt_name = fmap[filter];
2925 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2926 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2928 if (fmt_name.empty() || fname.empty())
2931 // fname is now the new Buffer location.
2932 if (FileName(fname).exists()) {
2933 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2934 docstring text = bformat(_("The document %1$s already "
2935 "exists.\n\nDo you want to "
2936 "overwrite that document?"),
2938 int const ret = Alert::prompt(_("Overwrite document?"),
2939 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2942 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2943 case 2: return false;
2947 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2950 return dr.dispatched();
2954 bool GuiView::saveBuffer(Buffer & b)
2956 return saveBuffer(b, FileName());
2960 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2962 if (workArea(b) && workArea(b)->inDialogMode())
2965 if (fn.empty() && b.isUnnamed())
2966 return renameBuffer(b, docstring());
2968 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2970 theSession().lastFiles().add(b.fileName());
2971 theSession().writeFile();
2975 // Switch to this Buffer.
2978 // FIXME: we don't tell the user *WHY* the save failed !!
2979 docstring const file = makeDisplayPath(b.absFileName(), 30);
2980 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2981 "Do you want to rename the document and "
2982 "try again?"), file);
2983 int const ret = Alert::prompt(_("Rename and save?"),
2984 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2987 if (!renameBuffer(b, docstring()))
2996 return saveBuffer(b, fn);
3000 bool GuiView::hideWorkArea(GuiWorkArea * wa)
3002 return closeWorkArea(wa, false);
3006 // We only want to close the buffer if it is not visible in other workareas
3007 // of the same view, nor in other views, and if this is not a child
3008 bool GuiView::closeWorkArea(GuiWorkArea * wa)
3010 Buffer & buf = wa->bufferView().buffer();
3012 bool last_wa = d.countWorkAreasOf(buf) == 1
3013 && !inOtherView(buf) && !buf.parent();
3015 bool close_buffer = last_wa;
3018 if (lyxrc.close_buffer_with_last_view == "yes")
3020 else if (lyxrc.close_buffer_with_last_view == "no")
3021 close_buffer = false;
3024 if (buf.isUnnamed())
3025 file = from_utf8(buf.fileName().onlyFileName());
3027 file = buf.fileName().displayName(30);
3028 docstring const text = bformat(
3029 _("Last view on document %1$s is being closed.\n"
3030 "Would you like to close or hide the document?\n"
3032 "Hidden documents can be displayed back through\n"
3033 "the menu: View->Hidden->...\n"
3035 "To remove this question, set your preference in:\n"
3036 " Tools->Preferences->Look&Feel->UserInterface\n"
3038 int ret = Alert::prompt(_("Close or hide document?"),
3039 text, 0, 1, _("&Close"), _("&Hide"));
3040 close_buffer = (ret == 0);
3044 return closeWorkArea(wa, close_buffer);
3048 bool GuiView::closeBuffer()
3050 GuiWorkArea * wa = currentMainWorkArea();
3051 // coverity complained about this
3052 // it seems unnecessary, but perhaps is worth the check
3053 LASSERT(wa, return false);
3055 setCurrentWorkArea(wa);
3056 Buffer & buf = wa->bufferView().buffer();
3057 return closeWorkArea(wa, !buf.parent());
3061 void GuiView::writeSession() const {
3062 GuiWorkArea const * active_wa = currentMainWorkArea();
3063 for (int i = 0; i < d.splitter_->count(); ++i) {
3064 TabWorkArea * twa = d.tabWorkArea(i);
3065 for (int j = 0; j < twa->count(); ++j) {
3066 GuiWorkArea * wa = twa->workArea(j);
3067 Buffer & buf = wa->bufferView().buffer();
3068 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3074 bool GuiView::closeBufferAll()
3076 // Close the workareas in all other views
3077 QList<int> const ids = guiApp->viewIds();
3078 for (int i = 0; i != ids.size(); ++i) {
3079 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3083 // Close our own workareas
3084 if (!closeWorkAreaAll())
3087 // Now close the hidden buffers. We prevent hidden buffers from being
3088 // dirty, so we can just close them.
3089 theBufferList().closeAll();
3094 bool GuiView::closeWorkAreaAll()
3096 setCurrentWorkArea(currentMainWorkArea());
3098 // We might be in a situation that there is still a tabWorkArea, but
3099 // there are no tabs anymore. This can happen when we get here after a
3100 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3101 // many TabWorkArea's have no documents anymore.
3104 // We have to call count() each time, because it can happen that
3105 // more than one splitter will disappear in one iteration (bug 5998).
3106 while (d.splitter_->count() > empty_twa) {
3107 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3109 if (twa->count() == 0)
3112 setCurrentWorkArea(twa->currentWorkArea());
3113 if (!closeTabWorkArea(twa))
3121 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3126 Buffer & buf = wa->bufferView().buffer();
3128 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3129 Alert::warning(_("Close document"),
3130 _("Document could not be closed because it is being processed by LyX."));
3135 return closeBuffer(buf);
3137 if (!inMultiTabs(wa))
3138 if (!saveBufferIfNeeded(buf, true))
3146 bool GuiView::closeBuffer(Buffer & buf)
3148 bool success = true;
3149 ListOfBuffers clist = buf.getChildren();
3150 ListOfBuffers::const_iterator it = clist.begin();
3151 ListOfBuffers::const_iterator const bend = clist.end();
3152 for (; it != bend; ++it) {
3153 Buffer * child_buf = *it;
3154 if (theBufferList().isOthersChild(&buf, child_buf)) {
3155 child_buf->setParent(0);
3159 // FIXME: should we look in other tabworkareas?
3160 // ANSWER: I don't think so. I've tested, and if the child is
3161 // open in some other window, it closes without a problem.
3162 GuiWorkArea * child_wa = workArea(*child_buf);
3165 // If we are in a close_event all children will be closed in some time,
3166 // so no need to do it here. This will ensure that the children end up
3167 // in the session file in the correct order. If we close the master
3168 // buffer, we can close or release the child buffers here too.
3170 success = closeWorkArea(child_wa, true);
3174 // In this case the child buffer is open but hidden.
3175 // Even in this case, children can be dirty (e.g.,
3176 // after a label change in the master, see #11405).
3177 // Therefore, check this
3178 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3179 // If we are in a close_event all children will be closed in some time,
3180 // so no need to do it here. This will ensure that the children end up
3181 // in the session file in the correct order. If we close the master
3182 // buffer, we can close or release the child buffers here too.
3184 // Save dirty buffers also if closing_!
3185 if (saveBufferIfNeeded(*child_buf, false)) {
3186 child_buf->removeAutosaveFile();
3187 theBufferList().release(child_buf);
3189 // Saving of dirty children has been cancelled.
3190 // Cancel the whole process.
3197 // goto bookmark to update bookmark pit.
3198 // FIXME: we should update only the bookmarks related to this buffer!
3199 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3200 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3201 guiApp->gotoBookmark(i+1, false, false);
3203 if (saveBufferIfNeeded(buf, false)) {
3204 buf.removeAutosaveFile();
3205 theBufferList().release(&buf);
3209 // open all children again to avoid a crash because of dangling
3210 // pointers (bug 6603)
3216 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3218 while (twa == d.currentTabWorkArea()) {
3219 twa->setCurrentIndex(twa->count() - 1);
3221 GuiWorkArea * wa = twa->currentWorkArea();
3222 Buffer & b = wa->bufferView().buffer();
3224 // We only want to close the buffer if the same buffer is not visible
3225 // in another view, and if this is not a child and if we are closing
3226 // a view (not a tabgroup).
3227 bool const close_buffer =
3228 !inOtherView(b) && !b.parent() && closing_;
3230 if (!closeWorkArea(wa, close_buffer))
3237 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3239 if (buf.isClean() || buf.paragraphs().empty())
3242 // Switch to this Buffer.
3248 if (buf.isUnnamed()) {
3249 file = from_utf8(buf.fileName().onlyFileName());
3252 FileName filename = buf.fileName();
3254 file = filename.displayName(30);
3255 exists = filename.exists();
3258 // Bring this window to top before asking questions.
3263 if (hiding && buf.isUnnamed()) {
3264 docstring const text = bformat(_("The document %1$s has not been "
3265 "saved yet.\n\nDo you want to save "
3266 "the document?"), file);
3267 ret = Alert::prompt(_("Save new document?"),
3268 text, 0, 1, _("&Save"), _("&Cancel"));
3272 docstring const text = exists ?
3273 bformat(_("The document %1$s has unsaved changes."
3274 "\n\nDo you want to save the document or "
3275 "discard the changes?"), file) :
3276 bformat(_("The document %1$s has not been saved yet."
3277 "\n\nDo you want to save the document or "
3278 "discard it entirely?"), file);
3279 docstring const title = exists ?
3280 _("Save changed document?") : _("Save document?");
3281 ret = Alert::prompt(title, text, 0, 2,
3282 _("&Save"), _("&Discard"), _("&Cancel"));
3287 if (!saveBuffer(buf))
3291 // If we crash after this we could have no autosave file
3292 // but I guess this is really improbable (Jug).
3293 // Sometimes improbable things happen:
3294 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3295 // buf.removeAutosaveFile();
3297 // revert all changes
3308 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3310 Buffer & buf = wa->bufferView().buffer();
3312 for (int i = 0; i != d.splitter_->count(); ++i) {
3313 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3314 if (wa_ && wa_ != wa)
3317 return inOtherView(buf);
3321 bool GuiView::inOtherView(Buffer & buf)
3323 QList<int> const ids = guiApp->viewIds();
3325 for (int i = 0; i != ids.size(); ++i) {
3329 if (guiApp->view(ids[i]).workArea(buf))
3336 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3338 if (!documentBufferView())
3341 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3342 Buffer * const curbuf = &documentBufferView()->buffer();
3343 int nwa = twa->count();
3344 for (int i = 0; i < nwa; ++i) {
3345 if (&workArea(i)->bufferView().buffer() == curbuf) {
3347 if (np == NEXTBUFFER)
3348 next_index = (i == nwa - 1 ? 0 : i + 1);
3350 next_index = (i == 0 ? nwa - 1 : i - 1);
3352 twa->moveTab(i, next_index);
3354 setBuffer(&workArea(next_index)->bufferView().buffer());
3362 /// make sure the document is saved
3363 static bool ensureBufferClean(Buffer * buffer)
3365 LASSERT(buffer, return false);
3366 if (buffer->isClean() && !buffer->isUnnamed())
3369 docstring const file = buffer->fileName().displayName(30);
3372 if (!buffer->isUnnamed()) {
3373 text = bformat(_("The document %1$s has unsaved "
3374 "changes.\n\nDo you want to save "
3375 "the document?"), file);
3376 title = _("Save changed document?");
3379 text = bformat(_("The document %1$s has not been "
3380 "saved yet.\n\nDo you want to save "
3381 "the document?"), file);
3382 title = _("Save new document?");
3384 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3387 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3389 return buffer->isClean() && !buffer->isUnnamed();
3393 bool GuiView::reloadBuffer(Buffer & buf)
3395 currentBufferView()->cursor().reset();
3396 Buffer::ReadStatus status = buf.reload();
3397 return status == Buffer::ReadSuccess;
3401 void GuiView::checkExternallyModifiedBuffers()
3403 BufferList::iterator bit = theBufferList().begin();
3404 BufferList::iterator const bend = theBufferList().end();
3405 for (; bit != bend; ++bit) {
3406 Buffer * buf = *bit;
3407 if (buf->fileName().exists() && buf->isChecksumModified()) {
3408 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3409 " Reload now? Any local changes will be lost."),
3410 from_utf8(buf->absFileName()));
3411 int const ret = Alert::prompt(_("Reload externally changed document?"),
3412 text, 0, 1, _("&Reload"), _("&Cancel"));
3420 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3422 Buffer * buffer = documentBufferView()
3423 ? &(documentBufferView()->buffer()) : 0;
3425 switch (cmd.action()) {
3426 case LFUN_VC_REGISTER:
3427 if (!buffer || !ensureBufferClean(buffer))
3429 if (!buffer->lyxvc().inUse()) {
3430 if (buffer->lyxvc().registrer()) {
3431 reloadBuffer(*buffer);
3432 dr.clearMessageUpdate();
3437 case LFUN_VC_RENAME:
3438 case LFUN_VC_COPY: {
3439 if (!buffer || !ensureBufferClean(buffer))
3441 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3442 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3443 // Some changes are not yet committed.
3444 // We test here and not in getStatus(), since
3445 // this test is expensive.
3447 LyXVC::CommandResult ret =
3448 buffer->lyxvc().checkIn(log);
3450 if (ret == LyXVC::ErrorCommand ||
3451 ret == LyXVC::VCSuccess)
3452 reloadBuffer(*buffer);
3453 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3454 frontend::Alert::error(
3455 _("Revision control error."),
3456 _("Document could not be checked in."));
3460 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3461 LV_VC_RENAME : LV_VC_COPY;
3462 renameBuffer(*buffer, cmd.argument(), kind);
3467 case LFUN_VC_CHECK_IN:
3468 if (!buffer || !ensureBufferClean(buffer))
3470 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3472 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3474 // Only skip reloading if the checkin was cancelled or
3475 // an error occurred before the real checkin VCS command
3476 // was executed, since the VCS might have changed the
3477 // file even if it could not checkin successfully.
3478 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3479 reloadBuffer(*buffer);
3483 case LFUN_VC_CHECK_OUT:
3484 if (!buffer || !ensureBufferClean(buffer))
3486 if (buffer->lyxvc().inUse()) {
3487 dr.setMessage(buffer->lyxvc().checkOut());
3488 reloadBuffer(*buffer);
3492 case LFUN_VC_LOCKING_TOGGLE:
3493 LASSERT(buffer, return);
3494 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3496 if (buffer->lyxvc().inUse()) {
3497 string res = buffer->lyxvc().lockingToggle();
3499 frontend::Alert::error(_("Revision control error."),
3500 _("Error when setting the locking property."));
3503 reloadBuffer(*buffer);
3508 case LFUN_VC_REVERT:
3509 LASSERT(buffer, return);
3510 if (buffer->lyxvc().revert()) {
3511 reloadBuffer(*buffer);
3512 dr.clearMessageUpdate();
3516 case LFUN_VC_UNDO_LAST:
3517 LASSERT(buffer, return);
3518 buffer->lyxvc().undoLast();
3519 reloadBuffer(*buffer);
3520 dr.clearMessageUpdate();
3523 case LFUN_VC_REPO_UPDATE:
3524 LASSERT(buffer, return);
3525 if (ensureBufferClean(buffer)) {
3526 dr.setMessage(buffer->lyxvc().repoUpdate());
3527 checkExternallyModifiedBuffers();
3531 case LFUN_VC_COMMAND: {
3532 string flag = cmd.getArg(0);
3533 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3536 if (contains(flag, 'M')) {
3537 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3540 string path = cmd.getArg(1);
3541 if (contains(path, "$$p") && buffer)
3542 path = subst(path, "$$p", buffer->filePath());
3543 LYXERR(Debug::LYXVC, "Directory: " << path);
3545 if (!pp.isReadableDirectory()) {
3546 lyxerr << _("Directory is not accessible.") << endl;
3549 support::PathChanger p(pp);
3551 string command = cmd.getArg(2);
3552 if (command.empty())
3555 command = subst(command, "$$i", buffer->absFileName());
3556 command = subst(command, "$$p", buffer->filePath());
3558 command = subst(command, "$$m", to_utf8(message));
3559 LYXERR(Debug::LYXVC, "Command: " << command);
3561 one.startscript(Systemcall::Wait, command);
3565 if (contains(flag, 'I'))
3566 buffer->markDirty();
3567 if (contains(flag, 'R'))
3568 reloadBuffer(*buffer);
3573 case LFUN_VC_COMPARE: {
3574 if (cmd.argument().empty()) {
3575 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3579 string rev1 = cmd.getArg(0);
3584 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3587 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3588 f2 = buffer->absFileName();
3590 string rev2 = cmd.getArg(1);
3594 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3598 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3599 f1 << "\n" << f2 << "\n" );
3600 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3601 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3611 void GuiView::openChildDocument(string const & fname)
3613 LASSERT(documentBufferView(), return);
3614 Buffer & buffer = documentBufferView()->buffer();
3615 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3616 documentBufferView()->saveBookmark(false);
3618 if (theBufferList().exists(filename)) {
3619 child = theBufferList().getBuffer(filename);
3622 message(bformat(_("Opening child document %1$s..."),
3623 makeDisplayPath(filename.absFileName())));
3624 child = loadDocument(filename, false);
3626 // Set the parent name of the child document.
3627 // This makes insertion of citations and references in the child work,
3628 // when the target is in the parent or another child document.
3630 child->setParent(&buffer);
3634 bool GuiView::goToFileRow(string const & argument)
3638 size_t i = argument.find_last_of(' ');
3639 if (i != string::npos) {
3640 file_name = os::internal_path(trim(argument.substr(0, i)));
3641 istringstream is(argument.substr(i + 1));
3646 if (i == string::npos) {
3647 LYXERR0("Wrong argument: " << argument);
3651 string const abstmp = package().temp_dir().absFileName();
3652 string const realtmp = package().temp_dir().realPath();
3653 // We have to use os::path_prefix_is() here, instead of
3654 // simply prefixIs(), because the file name comes from
3655 // an external application and may need case adjustment.
3656 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3657 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3658 // Needed by inverse dvi search. If it is a file
3659 // in tmpdir, call the apropriated function.
3660 // If tmpdir is a symlink, we may have the real
3661 // path passed back, so we correct for that.
3662 if (!prefixIs(file_name, abstmp))
3663 file_name = subst(file_name, realtmp, abstmp);
3664 buf = theBufferList().getBufferFromTmp(file_name);
3666 // Must replace extension of the file to be .lyx
3667 // and get full path
3668 FileName const s = fileSearch(string(),
3669 support::changeExtension(file_name, ".lyx"), "lyx");
3670 // Either change buffer or load the file
3671 if (theBufferList().exists(s))
3672 buf = theBufferList().getBuffer(s);
3673 else if (s.exists()) {
3674 buf = loadDocument(s);
3679 _("File does not exist: %1$s"),
3680 makeDisplayPath(file_name)));
3686 _("No buffer for file: %1$s."),
3687 makeDisplayPath(file_name))
3692 bool success = documentBufferView()->setCursorFromRow(row);
3694 LYXERR(Debug::LATEX,
3695 "setCursorFromRow: invalid position for row " << row);
3696 frontend::Alert::error(_("Inverse Search Failed"),
3697 _("Invalid position requested by inverse search.\n"
3698 "You may need to update the viewed document."));
3704 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3706 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3707 menu->exec(QCursor::pos());
3712 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3713 Buffer const * orig, Buffer * clone, string const & format)
3715 Buffer::ExportStatus const status = func(format);
3717 // the cloning operation will have produced a clone of the entire set of
3718 // documents, starting from the master. so we must delete those.
3719 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3721 busyBuffers.remove(orig);
3726 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3727 Buffer const * orig, Buffer * clone, string const & format)
3729 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3731 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3735 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3736 Buffer const * orig, Buffer * clone, string const & format)
3738 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3740 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3744 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3745 Buffer const * orig, Buffer * clone, string const & format)
3747 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3749 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3753 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3754 string const & argument,
3755 Buffer const * used_buffer,
3756 docstring const & msg,
3757 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3758 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3759 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3765 string format = argument;
3767 format = used_buffer->params().getDefaultOutputFormat();
3768 processing_format = format;
3770 progress_->clearMessages();
3773 #if EXPORT_in_THREAD
3775 GuiViewPrivate::busyBuffers.insert(used_buffer);
3776 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3777 if (!cloned_buffer) {
3778 Alert::error(_("Export Error"),
3779 _("Error cloning the Buffer."));
3782 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3787 setPreviewFuture(f);
3788 last_export_format = used_buffer->params().bufferFormat();
3791 // We are asynchronous, so we don't know here anything about the success
3794 Buffer::ExportStatus status;
3796 status = (used_buffer->*syncFunc)(format, false);
3797 } else if (previewFunc) {
3798 status = (used_buffer->*previewFunc)(format);
3801 handleExportStatus(gv_, status, format);
3803 return (status == Buffer::ExportSuccess
3804 || status == Buffer::PreviewSuccess);
3808 Buffer::ExportStatus status;
3810 status = (used_buffer->*syncFunc)(format, true);
3811 } else if (previewFunc) {
3812 status = (used_buffer->*previewFunc)(format);
3815 handleExportStatus(gv_, status, format);
3817 return (status == Buffer::ExportSuccess
3818 || status == Buffer::PreviewSuccess);
3822 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3824 BufferView * bv = currentBufferView();
3825 LASSERT(bv, return);
3827 // Let the current BufferView dispatch its own actions.
3828 bv->dispatch(cmd, dr);
3829 if (dr.dispatched()) {
3830 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3831 updateDialog("document", "");
3835 // Try with the document BufferView dispatch if any.
3836 BufferView * doc_bv = documentBufferView();
3837 if (doc_bv && doc_bv != bv) {
3838 doc_bv->dispatch(cmd, dr);
3839 if (dr.dispatched()) {
3840 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3841 updateDialog("document", "");
3846 // Then let the current Cursor dispatch its own actions.
3847 bv->cursor().dispatch(cmd);
3849 // update completion. We do it here and not in
3850 // processKeySym to avoid another redraw just for a
3851 // changed inline completion
3852 if (cmd.origin() == FuncRequest::KEYBOARD) {
3853 if (cmd.action() == LFUN_SELF_INSERT
3854 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3855 updateCompletion(bv->cursor(), true, true);
3856 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3857 updateCompletion(bv->cursor(), false, true);
3859 updateCompletion(bv->cursor(), false, false);
3862 dr = bv->cursor().result();
3866 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3868 BufferView * bv = currentBufferView();
3869 // By default we won't need any update.
3870 dr.screenUpdate(Update::None);
3871 // assume cmd will be dispatched
3872 dr.dispatched(true);
3874 Buffer * doc_buffer = documentBufferView()
3875 ? &(documentBufferView()->buffer()) : 0;
3877 if (cmd.origin() == FuncRequest::TOC) {
3878 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3879 // FIXME: do we need to pass a DispatchResult object here?
3880 toc->doDispatch(bv->cursor(), cmd);
3884 string const argument = to_utf8(cmd.argument());
3886 switch(cmd.action()) {
3887 case LFUN_BUFFER_CHILD_OPEN:
3888 openChildDocument(to_utf8(cmd.argument()));
3891 case LFUN_BUFFER_IMPORT:
3892 importDocument(to_utf8(cmd.argument()));
3895 case LFUN_MASTER_BUFFER_EXPORT:
3897 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3899 case LFUN_BUFFER_EXPORT: {
3902 // GCC only sees strfwd.h when building merged
3903 if (::lyx::operator==(cmd.argument(), "custom")) {
3904 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3905 // so the following test should not be needed.
3906 // In principle, we could try to switch to such a view...
3907 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3908 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3912 string const dest = cmd.getArg(1);
3913 FileName target_dir;
3914 if (!dest.empty() && FileName::isAbsolute(dest))
3915 target_dir = FileName(support::onlyPath(dest));
3917 target_dir = doc_buffer->fileName().onlyPath();
3919 string const format = (argument.empty() || argument == "default") ?
3920 doc_buffer->params().getDefaultOutputFormat() : argument;
3922 if ((dest.empty() && doc_buffer->isUnnamed())
3923 || !target_dir.isDirWritable()) {
3924 exportBufferAs(*doc_buffer, from_utf8(format));
3927 /* TODO/Review: Is it a problem to also export the children?
3928 See the update_unincluded flag */
3929 d.asyncBufferProcessing(format,
3932 &GuiViewPrivate::exportAndDestroy,
3934 0, cmd.allowAsync());
3935 // TODO Inform user about success
3939 case LFUN_BUFFER_EXPORT_AS: {
3940 LASSERT(doc_buffer, break);
3941 docstring f = cmd.argument();
3943 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3944 exportBufferAs(*doc_buffer, f);
3948 case LFUN_BUFFER_UPDATE: {
3949 d.asyncBufferProcessing(argument,
3952 &GuiViewPrivate::compileAndDestroy,
3954 0, cmd.allowAsync());
3957 case LFUN_BUFFER_VIEW: {
3958 d.asyncBufferProcessing(argument,
3960 _("Previewing ..."),
3961 &GuiViewPrivate::previewAndDestroy,
3963 &Buffer::preview, cmd.allowAsync());
3966 case LFUN_MASTER_BUFFER_UPDATE: {
3967 d.asyncBufferProcessing(argument,
3968 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3970 &GuiViewPrivate::compileAndDestroy,
3972 0, cmd.allowAsync());
3975 case LFUN_MASTER_BUFFER_VIEW: {
3976 d.asyncBufferProcessing(argument,
3977 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3979 &GuiViewPrivate::previewAndDestroy,
3980 0, &Buffer::preview, cmd.allowAsync());
3983 case LFUN_EXPORT_CANCEL: {
3984 Systemcall::killscript();
3987 case LFUN_BUFFER_SWITCH: {
3988 string const file_name = to_utf8(cmd.argument());
3989 if (!FileName::isAbsolute(file_name)) {
3991 dr.setMessage(_("Absolute filename expected."));
3995 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3998 dr.setMessage(_("Document not loaded"));
4002 // Do we open or switch to the buffer in this view ?
4003 if (workArea(*buffer)
4004 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
4009 // Look for the buffer in other views
4010 QList<int> const ids = guiApp->viewIds();
4012 for (; i != ids.size(); ++i) {
4013 GuiView & gv = guiApp->view(ids[i]);
4014 if (gv.workArea(*buffer)) {
4016 gv.activateWindow();
4018 gv.setBuffer(buffer);
4023 // If necessary, open a new window as a last resort
4024 if (i == ids.size()) {
4025 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4031 case LFUN_BUFFER_NEXT:
4032 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4035 case LFUN_BUFFER_MOVE_NEXT:
4036 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4039 case LFUN_BUFFER_PREVIOUS:
4040 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4043 case LFUN_BUFFER_MOVE_PREVIOUS:
4044 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4047 case LFUN_BUFFER_CHKTEX:
4048 LASSERT(doc_buffer, break);
4049 doc_buffer->runChktex();
4052 case LFUN_COMMAND_EXECUTE: {
4053 command_execute_ = true;
4054 minibuffer_focus_ = true;
4057 case LFUN_DROP_LAYOUTS_CHOICE:
4058 d.layout_->showPopup();
4061 case LFUN_MENU_OPEN:
4062 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4063 menu->exec(QCursor::pos());
4066 case LFUN_FILE_INSERT: {
4067 if (cmd.getArg(1) == "ignorelang")
4068 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4070 insertLyXFile(cmd.argument());
4074 case LFUN_FILE_INSERT_PLAINTEXT:
4075 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4076 string const fname = to_utf8(cmd.argument());
4077 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4078 dr.setMessage(_("Absolute filename expected."));
4082 FileName filename(fname);
4083 if (fname.empty()) {
4084 FileDialog dlg(qt_("Select file to insert"));
4086 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4087 QStringList(qt_("All Files (*)")));
4089 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4090 dr.setMessage(_("Canceled."));
4094 filename.set(fromqstr(result.second));
4098 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4099 bv->dispatch(new_cmd, dr);
4104 case LFUN_BUFFER_RELOAD: {
4105 LASSERT(doc_buffer, break);
4108 bool drop = (cmd.argument() == "dump");
4111 if (!drop && !doc_buffer->isClean()) {
4112 docstring const file =
4113 makeDisplayPath(doc_buffer->absFileName(), 20);
4114 if (doc_buffer->notifiesExternalModification()) {
4115 docstring text = _("The current version will be lost. "
4116 "Are you sure you want to load the version on disk "
4117 "of the document %1$s?");
4118 ret = Alert::prompt(_("Reload saved document?"),
4119 bformat(text, file), 1, 1,
4120 _("&Reload"), _("&Cancel"));
4122 docstring text = _("Any changes will be lost. "
4123 "Are you sure you want to revert to the saved version "
4124 "of the document %1$s?");
4125 ret = Alert::prompt(_("Revert to saved document?"),
4126 bformat(text, file), 1, 1,
4127 _("&Revert"), _("&Cancel"));
4132 doc_buffer->markClean();
4133 reloadBuffer(*doc_buffer);
4134 dr.forceBufferUpdate();
4139 case LFUN_BUFFER_WRITE:
4140 LASSERT(doc_buffer, break);
4141 saveBuffer(*doc_buffer);
4144 case LFUN_BUFFER_WRITE_AS:
4145 LASSERT(doc_buffer, break);
4146 renameBuffer(*doc_buffer, cmd.argument());
4149 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4150 LASSERT(doc_buffer, break);
4151 renameBuffer(*doc_buffer, cmd.argument(),
4152 LV_WRITE_AS_TEMPLATE);
4155 case LFUN_BUFFER_WRITE_ALL: {
4156 Buffer * first = theBufferList().first();
4159 message(_("Saving all documents..."));
4160 // We cannot use a for loop as the buffer list cycles.
4163 if (!b->isClean()) {
4165 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4167 b = theBufferList().next(b);
4168 } while (b != first);
4169 dr.setMessage(_("All documents saved."));
4173 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4174 LASSERT(doc_buffer, break);
4175 doc_buffer->clearExternalModification();
4178 case LFUN_BUFFER_CLOSE:
4182 case LFUN_BUFFER_CLOSE_ALL:
4186 case LFUN_DEVEL_MODE_TOGGLE:
4187 devel_mode_ = !devel_mode_;
4189 dr.setMessage(_("Developer mode is now enabled."));
4191 dr.setMessage(_("Developer mode is now disabled."));
4194 case LFUN_TOOLBAR_TOGGLE: {
4195 string const name = cmd.getArg(0);
4196 if (GuiToolbar * t = toolbar(name))
4201 case LFUN_TOOLBAR_MOVABLE: {
4202 string const name = cmd.getArg(0);
4204 // toggle (all) toolbars movablility
4205 toolbarsMovable_ = !toolbarsMovable_;
4206 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4207 GuiToolbar * tb = toolbar(ti.name);
4208 if (tb && tb->isMovable() != toolbarsMovable_)
4209 // toggle toolbar movablity if it does not fit lock
4210 // (all) toolbars positions state silent = true, since
4211 // status bar notifications are slow
4214 if (toolbarsMovable_)
4215 dr.setMessage(_("Toolbars unlocked."));
4217 dr.setMessage(_("Toolbars locked."));
4218 } else if (GuiToolbar * t = toolbar(name)) {
4219 // toggle current toolbar movablity
4221 // update lock (all) toolbars positions
4222 updateLockToolbars();
4227 case LFUN_ICON_SIZE: {
4228 QSize size = d.iconSize(cmd.argument());
4230 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4231 size.width(), size.height()));
4235 case LFUN_DIALOG_UPDATE: {
4236 string const name = to_utf8(cmd.argument());
4237 if (name == "prefs" || name == "document")
4238 updateDialog(name, string());
4239 else if (name == "paragraph")
4240 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4241 else if (currentBufferView()) {
4242 Inset * inset = currentBufferView()->editedInset(name);
4243 // Can only update a dialog connected to an existing inset
4245 // FIXME: get rid of this indirection; GuiView ask the inset
4246 // if he is kind enough to update itself...
4247 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4248 //FIXME: pass DispatchResult here?
4249 inset->dispatch(currentBufferView()->cursor(), fr);
4255 case LFUN_DIALOG_TOGGLE: {
4256 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4257 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4258 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4262 case LFUN_DIALOG_DISCONNECT_INSET:
4263 disconnectDialog(to_utf8(cmd.argument()));
4266 case LFUN_DIALOG_HIDE: {
4267 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4271 case LFUN_DIALOG_SHOW: {
4272 string const name = cmd.getArg(0);
4273 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4275 if (name == "latexlog") {
4276 // gettatus checks that
4277 LATTEST(doc_buffer);
4278 Buffer::LogType type;
4279 string const logfile = doc_buffer->logName(&type);
4281 case Buffer::latexlog:
4284 case Buffer::buildlog:
4285 sdata = "literate ";
4288 sdata += Lexer::quoteString(logfile);
4289 showDialog("log", sdata);
4290 } else if (name == "vclog") {
4291 // getStatus checks that
4292 LATTEST(doc_buffer);
4293 string const sdata2 = "vc " +
4294 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4295 showDialog("log", sdata2);
4296 } else if (name == "symbols") {
4297 sdata = bv->cursor().getEncoding()->name();
4299 showDialog("symbols", sdata);
4301 } else if (name == "prefs" && isFullScreen()) {
4302 lfunUiToggle("fullscreen");
4303 showDialog("prefs", sdata);
4305 showDialog(name, sdata);
4310 dr.setMessage(cmd.argument());
4313 case LFUN_UI_TOGGLE: {
4314 string arg = cmd.getArg(0);
4315 if (!lfunUiToggle(arg)) {
4316 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4317 dr.setMessage(bformat(msg, from_utf8(arg)));
4319 // Make sure the keyboard focus stays in the work area.
4324 case LFUN_VIEW_SPLIT: {
4325 LASSERT(doc_buffer, break);
4326 string const orientation = cmd.getArg(0);
4327 d.splitter_->setOrientation(orientation == "vertical"
4328 ? Qt::Vertical : Qt::Horizontal);
4329 TabWorkArea * twa = addTabWorkArea();
4330 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4331 setCurrentWorkArea(wa);
4334 case LFUN_TAB_GROUP_CLOSE:
4335 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4336 closeTabWorkArea(twa);
4337 d.current_work_area_ = 0;
4338 twa = d.currentTabWorkArea();
4339 // Switch to the next GuiWorkArea in the found TabWorkArea.
4341 // Make sure the work area is up to date.
4342 setCurrentWorkArea(twa->currentWorkArea());
4344 setCurrentWorkArea(0);
4349 case LFUN_VIEW_CLOSE:
4350 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4351 closeWorkArea(twa->currentWorkArea());
4352 d.current_work_area_ = 0;
4353 twa = d.currentTabWorkArea();
4354 // Switch to the next GuiWorkArea in the found TabWorkArea.
4356 // Make sure the work area is up to date.
4357 setCurrentWorkArea(twa->currentWorkArea());
4359 setCurrentWorkArea(0);
4364 case LFUN_COMPLETION_INLINE:
4365 if (d.current_work_area_)
4366 d.current_work_area_->completer().showInline();
4369 case LFUN_COMPLETION_POPUP:
4370 if (d.current_work_area_)
4371 d.current_work_area_->completer().showPopup();
4376 if (d.current_work_area_)
4377 d.current_work_area_->completer().tab();
4380 case LFUN_COMPLETION_CANCEL:
4381 if (d.current_work_area_) {
4382 if (d.current_work_area_->completer().popupVisible())
4383 d.current_work_area_->completer().hidePopup();
4385 d.current_work_area_->completer().hideInline();
4389 case LFUN_COMPLETION_ACCEPT:
4390 if (d.current_work_area_)
4391 d.current_work_area_->completer().activate();
4394 case LFUN_BUFFER_ZOOM_IN:
4395 case LFUN_BUFFER_ZOOM_OUT:
4396 case LFUN_BUFFER_ZOOM: {
4397 if (cmd.argument().empty()) {
4398 if (cmd.action() == LFUN_BUFFER_ZOOM)
4400 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4405 if (cmd.action() == LFUN_BUFFER_ZOOM)
4406 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4407 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4408 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4410 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4413 // Actual zoom value: default zoom + fractional extra value
4414 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4415 if (zoom < static_cast<int>(zoom_min_))
4418 lyxrc.currentZoom = zoom;
4420 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4421 lyxrc.currentZoom, lyxrc.defaultZoom));
4423 guiApp->fontLoader().update();
4424 dr.screenUpdate(Update::Force | Update::FitCursor);
4428 case LFUN_VC_REGISTER:
4429 case LFUN_VC_RENAME:
4431 case LFUN_VC_CHECK_IN:
4432 case LFUN_VC_CHECK_OUT:
4433 case LFUN_VC_REPO_UPDATE:
4434 case LFUN_VC_LOCKING_TOGGLE:
4435 case LFUN_VC_REVERT:
4436 case LFUN_VC_UNDO_LAST:
4437 case LFUN_VC_COMMAND:
4438 case LFUN_VC_COMPARE:
4439 dispatchVC(cmd, dr);
4442 case LFUN_SERVER_GOTO_FILE_ROW:
4443 if(goToFileRow(to_utf8(cmd.argument())))
4444 dr.screenUpdate(Update::Force | Update::FitCursor);
4447 case LFUN_LYX_ACTIVATE:
4451 case LFUN_FORWARD_SEARCH: {
4452 // it seems safe to assume we have a document buffer, since
4453 // getStatus wants one.
4454 LATTEST(doc_buffer);
4455 Buffer const * doc_master = doc_buffer->masterBuffer();
4456 FileName const path(doc_master->temppath());
4457 string const texname = doc_master->isChild(doc_buffer)
4458 ? DocFileName(changeExtension(
4459 doc_buffer->absFileName(),
4460 "tex")).mangledFileName()
4461 : doc_buffer->latexName();
4462 string const fulltexname =
4463 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4464 string const mastername =
4465 removeExtension(doc_master->latexName());
4466 FileName const dviname(addName(path.absFileName(),
4467 addExtension(mastername, "dvi")));
4468 FileName const pdfname(addName(path.absFileName(),
4469 addExtension(mastername, "pdf")));
4470 bool const have_dvi = dviname.exists();
4471 bool const have_pdf = pdfname.exists();
4472 if (!have_dvi && !have_pdf) {
4473 dr.setMessage(_("Please, preview the document first."));
4476 string outname = dviname.onlyFileName();
4477 string command = lyxrc.forward_search_dvi;
4478 if (!have_dvi || (have_pdf &&
4479 pdfname.lastModified() > dviname.lastModified())) {
4480 outname = pdfname.onlyFileName();
4481 command = lyxrc.forward_search_pdf;
4484 DocIterator cur = bv->cursor();
4485 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4486 LYXERR(Debug::ACTION, "Forward search: row:" << row
4488 if (row == -1 || command.empty()) {
4489 dr.setMessage(_("Couldn't proceed."));
4492 string texrow = convert<string>(row);
4494 command = subst(command, "$$n", texrow);
4495 command = subst(command, "$$f", fulltexname);
4496 command = subst(command, "$$t", texname);
4497 command = subst(command, "$$o", outname);
4499 volatile PathChanger p(path);
4501 one.startscript(Systemcall::DontWait, command);
4505 case LFUN_SPELLING_CONTINUOUSLY:
4506 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4507 dr.screenUpdate(Update::Force);
4511 // The LFUN must be for one of BufferView, Buffer or Cursor;
4513 dispatchToBufferView(cmd, dr);
4517 // Part of automatic menu appearance feature.
4518 if (isFullScreen()) {
4519 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4523 // Need to update bv because many LFUNs here might have destroyed it
4524 bv = currentBufferView();
4526 // Clear non-empty selections
4527 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4529 Cursor & cur = bv->cursor();
4530 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4531 cur.clearSelection();
4537 bool GuiView::lfunUiToggle(string const & ui_component)
4539 if (ui_component == "scrollbar") {
4540 // hide() is of no help
4541 if (d.current_work_area_->verticalScrollBarPolicy() ==
4542 Qt::ScrollBarAlwaysOff)
4544 d.current_work_area_->setVerticalScrollBarPolicy(
4545 Qt::ScrollBarAsNeeded);
4547 d.current_work_area_->setVerticalScrollBarPolicy(
4548 Qt::ScrollBarAlwaysOff);
4549 } else if (ui_component == "statusbar") {
4550 statusBar()->setVisible(!statusBar()->isVisible());
4551 } else if (ui_component == "menubar") {
4552 menuBar()->setVisible(!menuBar()->isVisible());
4554 if (ui_component == "frame") {
4556 getContentsMargins(&l, &t, &r, &b);
4557 //are the frames in default state?
4558 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4560 setContentsMargins(-2, -2, -2, -2);
4562 setContentsMargins(0, 0, 0, 0);
4565 if (ui_component == "fullscreen") {
4573 void GuiView::toggleFullScreen()
4575 if (isFullScreen()) {
4576 for (int i = 0; i != d.splitter_->count(); ++i)
4577 d.tabWorkArea(i)->setFullScreen(false);
4578 setContentsMargins(0, 0, 0, 0);
4579 setWindowState(windowState() ^ Qt::WindowFullScreen);
4582 statusBar()->show();
4585 hideDialogs("prefs", 0);
4586 for (int i = 0; i != d.splitter_->count(); ++i)
4587 d.tabWorkArea(i)->setFullScreen(true);
4588 setContentsMargins(-2, -2, -2, -2);
4590 setWindowState(windowState() ^ Qt::WindowFullScreen);
4591 if (lyxrc.full_screen_statusbar)
4592 statusBar()->hide();
4593 if (lyxrc.full_screen_menubar)
4595 if (lyxrc.full_screen_toolbars) {
4596 ToolbarMap::iterator end = d.toolbars_.end();
4597 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4602 // give dialogs like the TOC a chance to adapt
4607 Buffer const * GuiView::updateInset(Inset const * inset)
4612 Buffer const * inset_buffer = &(inset->buffer());
4614 for (int i = 0; i != d.splitter_->count(); ++i) {
4615 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4618 Buffer const * buffer = &(wa->bufferView().buffer());
4619 if (inset_buffer == buffer)
4620 wa->scheduleRedraw(true);
4622 return inset_buffer;
4626 void GuiView::restartCaret()
4628 /* When we move around, or type, it's nice to be able to see
4629 * the caret immediately after the keypress.
4631 if (d.current_work_area_)
4632 d.current_work_area_->startBlinkingCaret();
4634 // Take this occasion to update the other GUI elements.
4640 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4642 if (d.current_work_area_)
4643 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4648 // This list should be kept in sync with the list of insets in
4649 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4650 // dialog should have the same name as the inset.
4651 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4652 // docs in LyXAction.cpp.
4654 char const * const dialognames[] = {
4656 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4657 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4658 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4659 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4660 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4661 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4662 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4663 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4665 char const * const * const end_dialognames =
4666 dialognames + (sizeof(dialognames) / sizeof(char *));
4670 cmpCStr(char const * name) : name_(name) {}
4671 bool operator()(char const * other) {
4672 return strcmp(other, name_) == 0;
4679 bool isValidName(string const & name)
4681 return find_if(dialognames, end_dialognames,
4682 cmpCStr(name.c_str())) != end_dialognames;
4688 void GuiView::resetDialogs()
4690 // Make sure that no LFUN uses any GuiView.
4691 guiApp->setCurrentView(0);
4695 constructToolbars();
4696 guiApp->menus().fillMenuBar(menuBar(), this, false);
4697 d.layout_->updateContents(true);
4698 // Now update controls with current buffer.
4699 guiApp->setCurrentView(this);
4705 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4707 if (!isValidName(name))
4710 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4712 if (it != d.dialogs_.end()) {
4714 it->second->hideView();
4715 return it->second.get();
4718 Dialog * dialog = build(name);
4719 d.dialogs_[name].reset(dialog);
4720 if (lyxrc.allow_geometry_session)
4721 dialog->restoreSession();
4728 void GuiView::showDialog(string const & name, string const & sdata,
4731 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4735 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4741 const string name = fromqstr(qname);
4742 const string sdata = fromqstr(qdata);
4746 Dialog * dialog = findOrBuild(name, false);
4748 bool const visible = dialog->isVisibleView();
4749 dialog->showData(sdata);
4750 if (currentBufferView())
4751 currentBufferView()->editInset(name, inset);
4752 // We only set the focus to the new dialog if it was not yet
4753 // visible in order not to change the existing previous behaviour
4755 // activateWindow is needed for floating dockviews
4756 dialog->asQWidget()->raise();
4757 dialog->asQWidget()->activateWindow();
4758 dialog->asQWidget()->setFocus();
4762 catch (ExceptionMessage const & ex) {
4770 bool GuiView::isDialogVisible(string const & name) const
4772 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4773 if (it == d.dialogs_.end())
4775 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4779 void GuiView::hideDialog(string const & name, Inset * inset)
4781 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4782 if (it == d.dialogs_.end())
4786 if (!currentBufferView())
4788 if (inset != currentBufferView()->editedInset(name))
4792 Dialog * const dialog = it->second.get();
4793 if (dialog->isVisibleView())
4795 if (currentBufferView())
4796 currentBufferView()->editInset(name, 0);
4800 void GuiView::disconnectDialog(string const & name)
4802 if (!isValidName(name))
4804 if (currentBufferView())
4805 currentBufferView()->editInset(name, 0);
4809 void GuiView::hideAll() const
4811 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4812 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4814 for(; it != end; ++it)
4815 it->second->hideView();
4819 void GuiView::updateDialogs()
4821 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4822 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4824 for(; it != end; ++it) {
4825 Dialog * dialog = it->second.get();
4827 if (dialog->needBufferOpen() && !documentBufferView())
4828 hideDialog(fromqstr(dialog->name()), 0);
4829 else if (dialog->isVisibleView())
4830 dialog->checkStatus();
4837 Dialog * createDialog(GuiView & lv, string const & name);
4839 // will be replaced by a proper factory...
4840 Dialog * createGuiAbout(GuiView & lv);
4841 Dialog * createGuiBibtex(GuiView & lv);
4842 Dialog * createGuiChanges(GuiView & lv);
4843 Dialog * createGuiCharacter(GuiView & lv);
4844 Dialog * createGuiCitation(GuiView & lv);
4845 Dialog * createGuiCompare(GuiView & lv);
4846 Dialog * createGuiCompareHistory(GuiView & lv);
4847 Dialog * createGuiDelimiter(GuiView & lv);
4848 Dialog * createGuiDocument(GuiView & lv);
4849 Dialog * createGuiErrorList(GuiView & lv);
4850 Dialog * createGuiExternal(GuiView & lv);
4851 Dialog * createGuiGraphics(GuiView & lv);
4852 Dialog * createGuiInclude(GuiView & lv);
4853 Dialog * createGuiIndex(GuiView & lv);
4854 Dialog * createGuiListings(GuiView & lv);
4855 Dialog * createGuiLog(GuiView & lv);
4856 Dialog * createGuiLyXFiles(GuiView & lv);
4857 Dialog * createGuiMathMatrix(GuiView & lv);
4858 Dialog * createGuiNote(GuiView & lv);
4859 Dialog * createGuiParagraph(GuiView & lv);
4860 Dialog * createGuiPhantom(GuiView & lv);
4861 Dialog * createGuiPreferences(GuiView & lv);
4862 Dialog * createGuiPrint(GuiView & lv);
4863 Dialog * createGuiPrintindex(GuiView & lv);
4864 Dialog * createGuiRef(GuiView & lv);
4865 Dialog * createGuiSearch(GuiView & lv);
4866 Dialog * createGuiSearchAdv(GuiView & lv);
4867 Dialog * createGuiSendTo(GuiView & lv);
4868 Dialog * createGuiShowFile(GuiView & lv);
4869 Dialog * createGuiSpellchecker(GuiView & lv);
4870 Dialog * createGuiSymbols(GuiView & lv);
4871 Dialog * createGuiTabularCreate(GuiView & lv);
4872 Dialog * createGuiTexInfo(GuiView & lv);
4873 Dialog * createGuiToc(GuiView & lv);
4874 Dialog * createGuiThesaurus(GuiView & lv);
4875 Dialog * createGuiViewSource(GuiView & lv);
4876 Dialog * createGuiWrap(GuiView & lv);
4877 Dialog * createGuiProgressView(GuiView & lv);
4881 Dialog * GuiView::build(string const & name)
4883 LASSERT(isValidName(name), return 0);
4885 Dialog * dialog = createDialog(*this, name);
4889 if (name == "aboutlyx")
4890 return createGuiAbout(*this);
4891 if (name == "bibtex")
4892 return createGuiBibtex(*this);
4893 if (name == "changes")
4894 return createGuiChanges(*this);
4895 if (name == "character")
4896 return createGuiCharacter(*this);
4897 if (name == "citation")
4898 return createGuiCitation(*this);
4899 if (name == "compare")
4900 return createGuiCompare(*this);
4901 if (name == "comparehistory")
4902 return createGuiCompareHistory(*this);
4903 if (name == "document")
4904 return createGuiDocument(*this);
4905 if (name == "errorlist")
4906 return createGuiErrorList(*this);
4907 if (name == "external")
4908 return createGuiExternal(*this);
4910 return createGuiShowFile(*this);
4911 if (name == "findreplace")
4912 return createGuiSearch(*this);
4913 if (name == "findreplaceadv")
4914 return createGuiSearchAdv(*this);
4915 if (name == "graphics")
4916 return createGuiGraphics(*this);
4917 if (name == "include")
4918 return createGuiInclude(*this);
4919 if (name == "index")
4920 return createGuiIndex(*this);
4921 if (name == "index_print")
4922 return createGuiPrintindex(*this);
4923 if (name == "listings")
4924 return createGuiListings(*this);
4926 return createGuiLog(*this);
4927 if (name == "lyxfiles")
4928 return createGuiLyXFiles(*this);
4929 if (name == "mathdelimiter")
4930 return createGuiDelimiter(*this);
4931 if (name == "mathmatrix")
4932 return createGuiMathMatrix(*this);
4934 return createGuiNote(*this);
4935 if (name == "paragraph")
4936 return createGuiParagraph(*this);
4937 if (name == "phantom")
4938 return createGuiPhantom(*this);
4939 if (name == "prefs")
4940 return createGuiPreferences(*this);
4942 return createGuiRef(*this);
4943 if (name == "sendto")
4944 return createGuiSendTo(*this);
4945 if (name == "spellchecker")
4946 return createGuiSpellchecker(*this);
4947 if (name == "symbols")
4948 return createGuiSymbols(*this);
4949 if (name == "tabularcreate")
4950 return createGuiTabularCreate(*this);
4951 if (name == "texinfo")
4952 return createGuiTexInfo(*this);
4953 if (name == "thesaurus")
4954 return createGuiThesaurus(*this);
4956 return createGuiToc(*this);
4957 if (name == "view-source")
4958 return createGuiViewSource(*this);
4960 return createGuiWrap(*this);
4961 if (name == "progress")
4962 return createGuiProgressView(*this);
4968 SEMenu::SEMenu(QWidget * parent)
4970 QAction * action = addAction(qt_("Disable Shell Escape"));
4971 connect(action, SIGNAL(triggered()),
4972 parent, SLOT(disableShellEscape()));
4975 } // namespace frontend
4978 #include "moc_GuiView.cpp"