3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
55 #include "LayoutFile.h"
57 #include "LyXAction.h"
61 #include "Paragraph.h"
62 #include "SpellChecker.h"
65 #include "TextClass.h"
70 #include "support/convert.h"
71 #include "support/debug.h"
72 #include "support/ExceptionMessage.h"
73 #include "support/FileName.h"
74 #include "support/filetools.h"
75 #include "support/gettext.h"
76 #include "support/filetools.h"
77 #include "support/ForkedCalls.h"
78 #include "support/lassert.h"
79 #include "support/lstrings.h"
80 #include "support/os.h"
81 #include "support/Package.h"
82 #include "support/PathChanger.h"
83 #include "support/Systemcall.h"
84 #include "support/Timeout.h"
85 #include "support/ProgressInterface.h"
88 #include <QApplication>
89 #include <QCloseEvent>
91 #include <QDesktopWidget>
92 #include <QDragEnterEvent>
95 #include <QFutureWatcher>
105 #include <QPushButton>
106 #include <QScrollBar>
108 #include <QShowEvent>
110 #include <QStackedWidget>
111 #include <QStatusBar>
112 #include <QSvgRenderer>
113 #include <QtConcurrentRun>
121 // sync with GuiAlert.cpp
122 #define EXPORT_in_THREAD 1
125 #include "support/bind.h"
129 #ifdef HAVE_SYS_TIME_H
130 # include <sys/time.h>
138 using namespace lyx::support;
142 using support::addExtension;
143 using support::changeExtension;
144 using support::removeExtension;
150 class BackgroundWidget : public QWidget
153 BackgroundWidget(int width, int height)
154 : width_(width), height_(height)
156 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
157 if (!lyxrc.show_banner)
159 /// The text to be written on top of the pixmap
160 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
161 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
162 /// The text to be written on top of the pixmap
163 QString const text = lyx_version ?
164 qt_("version ") + lyx_version : qt_("unknown version");
165 #if QT_VERSION >= 0x050000
166 QString imagedir = "images/";
167 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
168 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
169 if (svgRenderer.isValid()) {
170 splash_ = QPixmap(splashSize());
171 QPainter painter(&splash_);
172 svgRenderer.render(&painter);
173 splash_.setDevicePixelRatio(pixelRatio());
175 splash_ = getPixmap("images/", "banner", "png");
178 splash_ = getPixmap("images/", "banner", "svgz,png");
181 QPainter pain(&splash_);
182 pain.setPen(QColor(0, 0, 0));
183 qreal const fsize = fontSize();
186 qreal locscale = htextsize.toFloat(&ok);
189 QPointF const position = textPosition(false);
190 QPointF const hposition = textPosition(true);
191 QRectF const hrect(hposition, splashSize());
193 "widget pixel ratio: " << pixelRatio() <<
194 " splash pixel ratio: " << splashPixelRatio() <<
195 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
197 // The font used to display the version info
198 font.setStyleHint(QFont::SansSerif);
199 font.setWeight(QFont::Bold);
200 font.setPointSizeF(fsize);
202 pain.drawText(position, text);
203 // The font used to display the version info
204 font.setStyleHint(QFont::SansSerif);
205 font.setWeight(QFont::Normal);
206 font.setPointSizeF(hfsize);
207 // Check how long the logo gets with the current font
208 // and adapt if the font is running wider than what
210 QFontMetrics fm(font);
211 // Split the title into lines to measure the longest line
212 // in the current l7n.
213 QStringList titlesegs = htext.split('\n');
215 int hline = fm.height();
216 QStringList::const_iterator sit;
217 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
218 if (fm.width(*sit) > wline)
219 wline = fm.width(*sit);
221 // The longest line in the reference font (for English)
222 // is 180. Calculate scale factor from that.
223 double const wscale = wline > 0 ? (180.0 / wline) : 1;
224 // Now do the same for the height (necessary for condensed fonts)
225 double const hscale = (34.0 / hline);
226 // take the lower of the two scale factors.
227 double const scale = min(wscale, hscale);
228 // Now rescale. Also consider l7n's offset factor.
229 font.setPointSizeF(hfsize * scale * locscale);
232 pain.drawText(hrect, Qt::AlignLeft, htext);
233 setFocusPolicy(Qt::StrongFocus);
236 void paintEvent(QPaintEvent *)
238 int const w = width_;
239 int const h = height_;
240 int const x = (width() - w) / 2;
241 int const y = (height() - h) / 2;
243 "widget pixel ratio: " << pixelRatio() <<
244 " splash pixel ratio: " << splashPixelRatio() <<
245 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
247 pain.drawPixmap(x, y, w, h, splash_);
250 void keyPressEvent(QKeyEvent * ev)
253 setKeySymbol(&sym, ev);
255 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
267 /// Current ratio between physical pixels and device-independent pixels
268 double pixelRatio() const {
269 #if QT_VERSION >= 0x050000
270 return qt_scale_factor * devicePixelRatio();
276 qreal fontSize() const {
277 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
280 QPointF textPosition(bool const heading) const {
281 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
282 : QPointF(width_/2 - 18, height_/2 + 45);
285 QSize splashSize() const {
287 static_cast<unsigned int>(width_ * pixelRatio()),
288 static_cast<unsigned int>(height_ * pixelRatio()));
291 /// Ratio between physical pixels and device-independent pixels of splash image
292 double splashPixelRatio() const {
293 #if QT_VERSION >= 0x050000
294 return splash_.devicePixelRatio();
302 /// Toolbar store providing access to individual toolbars by name.
303 typedef map<string, GuiToolbar *> ToolbarMap;
305 typedef shared_ptr<Dialog> DialogPtr;
310 class GuiView::GuiViewPrivate
313 GuiViewPrivate(GuiViewPrivate const &);
314 void operator=(GuiViewPrivate const &);
316 GuiViewPrivate(GuiView * gv)
317 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
318 layout_(0), autosave_timeout_(5000),
321 // hardcode here the platform specific icon size
322 smallIconSize = 16; // scaling problems
323 normalIconSize = 20; // ok, default if iconsize.png is missing
324 bigIconSize = 26; // better for some math icons
325 hugeIconSize = 32; // better for hires displays
328 // if it exists, use width of iconsize.png as normal size
329 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
330 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
332 QImage image(toqstr(fn.absFileName()));
333 if (image.width() < int(smallIconSize))
334 normalIconSize = smallIconSize;
335 else if (image.width() > int(giantIconSize))
336 normalIconSize = giantIconSize;
338 normalIconSize = image.width();
341 splitter_ = new QSplitter;
342 bg_widget_ = new BackgroundWidget(400, 250);
343 stack_widget_ = new QStackedWidget;
344 stack_widget_->addWidget(bg_widget_);
345 stack_widget_->addWidget(splitter_);
348 // TODO cleanup, remove the singleton, handle multiple Windows?
349 progress_ = ProgressInterface::instance();
350 if (!dynamic_cast<GuiProgress*>(progress_)) {
351 progress_ = new GuiProgress; // TODO who deletes it
352 ProgressInterface::setInstance(progress_);
355 dynamic_cast<GuiProgress*>(progress_),
356 SIGNAL(updateStatusBarMessage(QString const&)),
357 gv, SLOT(updateStatusBarMessage(QString const&)));
359 dynamic_cast<GuiProgress*>(progress_),
360 SIGNAL(clearMessageText()),
361 gv, SLOT(clearMessageText()));
368 delete stack_widget_;
373 stack_widget_->setCurrentWidget(bg_widget_);
374 bg_widget_->setUpdatesEnabled(true);
375 bg_widget_->setFocus();
378 int tabWorkAreaCount()
380 return splitter_->count();
383 TabWorkArea * tabWorkArea(int i)
385 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
388 TabWorkArea * currentTabWorkArea()
390 int areas = tabWorkAreaCount();
392 // The first TabWorkArea is always the first one, if any.
393 return tabWorkArea(0);
395 for (int i = 0; i != areas; ++i) {
396 TabWorkArea * twa = tabWorkArea(i);
397 if (current_main_work_area_ == twa->currentWorkArea())
401 // None has the focus so we just take the first one.
402 return tabWorkArea(0);
405 int countWorkAreasOf(Buffer & buf)
407 int areas = tabWorkAreaCount();
409 for (int i = 0; i != areas; ++i) {
410 TabWorkArea * twa = tabWorkArea(i);
411 if (twa->workArea(buf))
417 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
419 if (processing_thread_watcher_.isRunning()) {
420 // we prefer to cancel this preview in order to keep a snappy
424 processing_thread_watcher_.setFuture(f);
427 QSize iconSize(docstring const & icon_size)
430 if (icon_size == "small")
431 size = smallIconSize;
432 else if (icon_size == "normal")
433 size = normalIconSize;
434 else if (icon_size == "big")
436 else if (icon_size == "huge")
438 else if (icon_size == "giant")
439 size = giantIconSize;
441 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
443 if (size < smallIconSize)
444 size = smallIconSize;
446 return QSize(size, size);
449 QSize iconSize(QString const & icon_size)
451 return iconSize(qstring_to_ucs4(icon_size));
454 string & iconSize(QSize const & qsize)
456 LATTEST(qsize.width() == qsize.height());
458 static string icon_size;
460 unsigned int size = qsize.width();
462 if (size < smallIconSize)
463 size = smallIconSize;
465 if (size == smallIconSize)
467 else if (size == normalIconSize)
468 icon_size = "normal";
469 else if (size == bigIconSize)
471 else if (size == hugeIconSize)
473 else if (size == giantIconSize)
476 icon_size = convert<string>(size);
483 GuiWorkArea * current_work_area_;
484 GuiWorkArea * current_main_work_area_;
485 QSplitter * splitter_;
486 QStackedWidget * stack_widget_;
487 BackgroundWidget * bg_widget_;
489 ToolbarMap toolbars_;
490 ProgressInterface* progress_;
491 /// The main layout box.
493 * \warning Don't Delete! The layout box is actually owned by
494 * whichever toolbar contains it. All the GuiView class needs is a
495 * means of accessing it.
497 * FIXME: replace that with a proper model so that we are not limited
498 * to only one dialog.
503 map<string, DialogPtr> dialogs_;
505 unsigned int smallIconSize;
506 unsigned int normalIconSize;
507 unsigned int bigIconSize;
508 unsigned int hugeIconSize;
509 unsigned int giantIconSize;
511 QTimer statusbar_timer_;
512 /// auto-saving of buffers
513 Timeout autosave_timeout_;
514 /// flag against a race condition due to multiclicks, see bug #1119
518 TocModels toc_models_;
521 QFutureWatcher<docstring> autosave_watcher_;
522 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
524 string last_export_format;
525 string processing_format;
527 static QSet<Buffer const *> busyBuffers;
528 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
529 Buffer * buffer, string const & format);
530 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
531 Buffer * buffer, string const & format);
532 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
533 Buffer * buffer, string const & format);
534 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
537 static Buffer::ExportStatus runAndDestroy(const T& func,
538 Buffer const * orig, Buffer * buffer, string const & format);
540 // TODO syncFunc/previewFunc: use bind
541 bool asyncBufferProcessing(string const & argument,
542 Buffer const * used_buffer,
543 docstring const & msg,
544 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
545 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
546 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
549 QVector<GuiWorkArea*> guiWorkAreas();
552 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
555 GuiView::GuiView(int id)
556 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
557 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
560 connect(this, SIGNAL(bufferViewChanged()),
561 this, SLOT(onBufferViewChanged()));
563 // GuiToolbars *must* be initialised before the menu bar.
564 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
567 // set ourself as the current view. This is needed for the menu bar
568 // filling, at least for the static special menu item on Mac. Otherwise
569 // they are greyed out.
570 guiApp->setCurrentView(this);
572 // Fill up the menu bar.
573 guiApp->menus().fillMenuBar(menuBar(), this, true);
575 setCentralWidget(d.stack_widget_);
577 // Start autosave timer
578 if (lyxrc.autosave) {
579 // The connection is closed when this is destroyed.
580 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
581 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
582 d.autosave_timeout_.start();
584 connect(&d.statusbar_timer_, SIGNAL(timeout()),
585 this, SLOT(clearMessage()));
587 // We don't want to keep the window in memory if it is closed.
588 setAttribute(Qt::WA_DeleteOnClose, true);
590 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
591 // QIcon::fromTheme was introduced in Qt 4.6
592 #if (QT_VERSION >= 0x040600)
593 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
594 // since the icon is provided in the application bundle. We use a themed
595 // version when available and use the bundled one as fallback.
596 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
598 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
604 // use tabbed dock area for multiple docks
605 // (such as "source" and "messages")
606 setDockOptions(QMainWindow::ForceTabbedDocks);
609 setAcceptDrops(true);
611 // add busy indicator to statusbar
612 QLabel * busylabel = new QLabel(statusBar());
613 statusBar()->addPermanentWidget(busylabel);
614 search_mode mode = theGuiApp()->imageSearchMode();
615 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
616 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
617 busylabel->setMovie(busyanim);
621 connect(&d.processing_thread_watcher_, SIGNAL(started()),
622 busylabel, SLOT(show()));
623 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
624 busylabel, SLOT(hide()));
626 QFontMetrics const fm(statusBar()->fontMetrics());
627 int const iconheight = max(int(d.normalIconSize), fm.height());
628 QSize const iconsize(iconheight, iconheight);
630 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
631 shell_escape_ = new QLabel(statusBar());
632 shell_escape_->setPixmap(shellescape);
633 shell_escape_->setScaledContents(true);
634 shell_escape_->setAlignment(Qt::AlignCenter);
635 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
636 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
637 "external commands for this document. "
638 "Right click to change."));
639 SEMenu * menu = new SEMenu(this);
640 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
641 menu, SLOT(showMenu(QPoint)));
642 shell_escape_->hide();
643 statusBar()->addPermanentWidget(shell_escape_);
645 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
646 read_only_ = new QLabel(statusBar());
647 read_only_->setPixmap(readonly);
648 read_only_->setScaledContents(true);
649 read_only_->setAlignment(Qt::AlignCenter);
651 statusBar()->addPermanentWidget(read_only_);
653 version_control_ = new QLabel(statusBar());
654 version_control_->setAlignment(Qt::AlignCenter);
655 version_control_->setFrameStyle(QFrame::StyledPanel);
656 version_control_->hide();
657 statusBar()->addPermanentWidget(version_control_);
659 statusBar()->setSizeGripEnabled(true);
662 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
663 SLOT(autoSaveThreadFinished()));
665 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
666 SLOT(processingThreadStarted()));
667 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
668 SLOT(processingThreadFinished()));
670 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
671 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
673 // set custom application bars context menu, e.g. tool bar and menu bar
674 setContextMenuPolicy(Qt::CustomContextMenu);
675 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
676 SLOT(toolBarPopup(const QPoint &)));
678 // Forbid too small unresizable window because it can happen
679 // with some window manager under X11.
680 setMinimumSize(300, 200);
682 if (lyxrc.allow_geometry_session) {
683 // Now take care of session management.
688 // no session handling, default to a sane size.
689 setGeometry(50, 50, 690, 510);
692 // clear session data if any.
694 settings.remove("views");
704 void GuiView::disableShellEscape()
706 BufferView * bv = documentBufferView();
709 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
710 bv->buffer().params().shell_escape = false;
711 bv->processUpdateFlags(Update::Force);
715 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
717 QVector<GuiWorkArea*> areas;
718 for (int i = 0; i < tabWorkAreaCount(); i++) {
719 TabWorkArea* ta = tabWorkArea(i);
720 for (int u = 0; u < ta->count(); u++) {
721 areas << ta->workArea(u);
727 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
728 string const & format)
730 docstring const fmt = theFormats().prettyName(format);
733 case Buffer::ExportSuccess:
734 msg = bformat(_("Successful export to format: %1$s"), fmt);
736 case Buffer::ExportCancel:
737 msg = _("Document export cancelled.");
739 case Buffer::ExportError:
740 case Buffer::ExportNoPathToFormat:
741 case Buffer::ExportTexPathHasSpaces:
742 case Buffer::ExportConverterError:
743 msg = bformat(_("Error while exporting format: %1$s"), fmt);
745 case Buffer::PreviewSuccess:
746 msg = bformat(_("Successful preview of format: %1$s"), fmt);
748 case Buffer::PreviewError:
749 msg = bformat(_("Error while previewing format: %1$s"), fmt);
751 case Buffer::ExportKilled:
752 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
759 void GuiView::processingThreadStarted()
764 void GuiView::processingThreadFinished()
766 QFutureWatcher<Buffer::ExportStatus> const * watcher =
767 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
769 Buffer::ExportStatus const status = watcher->result();
770 handleExportStatus(this, status, d.processing_format);
773 BufferView const * const bv = currentBufferView();
774 if (bv && !bv->buffer().errorList("Export").empty()) {
779 bool const error = (status != Buffer::ExportSuccess &&
780 status != Buffer::PreviewSuccess &&
781 status != Buffer::ExportCancel);
783 ErrorList & el = bv->buffer().errorList(d.last_export_format);
784 // at this point, we do not know if buffer-view or
785 // master-buffer-view was called. If there was an export error,
786 // and the current buffer's error log is empty, we guess that
787 // it must be master-buffer-view that was called so we set
789 errors(d.last_export_format, el.empty());
794 void GuiView::autoSaveThreadFinished()
796 QFutureWatcher<docstring> const * watcher =
797 static_cast<QFutureWatcher<docstring> const *>(sender());
798 message(watcher->result());
803 void GuiView::saveLayout() const
806 settings.setValue("zoom_ratio", zoom_ratio_);
807 settings.setValue("devel_mode", devel_mode_);
808 settings.beginGroup("views");
809 settings.beginGroup(QString::number(id_));
810 #if defined(Q_WS_X11) || defined(QPA_XCB)
811 settings.setValue("pos", pos());
812 settings.setValue("size", size());
814 settings.setValue("geometry", saveGeometry());
816 settings.setValue("layout", saveState(0));
817 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
821 void GuiView::saveUISettings() const
825 // Save the toolbar private states
826 ToolbarMap::iterator end = d.toolbars_.end();
827 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
828 it->second->saveSession(settings);
829 // Now take care of all other dialogs
830 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
831 for (; it!= d.dialogs_.end(); ++it)
832 it->second->saveSession(settings);
836 bool GuiView::restoreLayout()
839 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
840 // Actual zoom value: default zoom + fractional offset
841 int zoom = lyxrc.defaultZoom * zoom_ratio_;
842 if (zoom < static_cast<int>(zoom_min_))
844 lyxrc.currentZoom = zoom;
845 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
846 settings.beginGroup("views");
847 settings.beginGroup(QString::number(id_));
848 QString const icon_key = "icon_size";
849 if (!settings.contains(icon_key))
852 //code below is skipped when when ~/.config/LyX is (re)created
853 setIconSize(d.iconSize(settings.value(icon_key).toString()));
855 #if defined(Q_WS_X11) || defined(QPA_XCB)
856 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
857 QSize size = settings.value("size", QSize(690, 510)).toSize();
861 // Work-around for bug #6034: the window ends up in an undetermined
862 // state when trying to restore a maximized window when it is
863 // already maximized.
864 if (!(windowState() & Qt::WindowMaximized))
865 if (!restoreGeometry(settings.value("geometry").toByteArray()))
866 setGeometry(50, 50, 690, 510);
868 // Make sure layout is correctly oriented.
869 setLayoutDirection(qApp->layoutDirection());
871 // Allow the toc and view-source dock widget to be restored if needed.
873 if ((dialog = findOrBuild("toc", true)))
874 // see bug 5082. At least setup title and enabled state.
875 // Visibility will be adjusted by restoreState below.
876 dialog->prepareView();
877 if ((dialog = findOrBuild("view-source", true)))
878 dialog->prepareView();
879 if ((dialog = findOrBuild("progress", true)))
880 dialog->prepareView();
882 if (!restoreState(settings.value("layout").toByteArray(), 0))
885 // init the toolbars that have not been restored
886 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
887 Toolbars::Infos::iterator end = guiApp->toolbars().end();
888 for (; cit != end; ++cit) {
889 GuiToolbar * tb = toolbar(cit->name);
890 if (tb && !tb->isRestored())
891 initToolbar(cit->name);
894 // update lock (all) toolbars positions
895 updateLockToolbars();
902 GuiToolbar * GuiView::toolbar(string const & name)
904 ToolbarMap::iterator it = d.toolbars_.find(name);
905 if (it != d.toolbars_.end())
908 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
913 void GuiView::updateLockToolbars()
915 toolbarsMovable_ = false;
916 for (ToolbarInfo const & info : guiApp->toolbars()) {
917 GuiToolbar * tb = toolbar(info.name);
918 if (tb && tb->isMovable())
919 toolbarsMovable_ = true;
924 void GuiView::constructToolbars()
926 ToolbarMap::iterator it = d.toolbars_.begin();
927 for (; it != d.toolbars_.end(); ++it)
931 // I don't like doing this here, but the standard toolbar
932 // destroys this object when it's destroyed itself (vfr)
933 d.layout_ = new LayoutBox(*this);
934 d.stack_widget_->addWidget(d.layout_);
935 d.layout_->move(0,0);
937 // extracts the toolbars from the backend
938 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
939 Toolbars::Infos::iterator end = guiApp->toolbars().end();
940 for (; cit != end; ++cit)
941 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
945 void GuiView::initToolbars()
947 // extracts the toolbars from the backend
948 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
949 Toolbars::Infos::iterator end = guiApp->toolbars().end();
950 for (; cit != end; ++cit)
951 initToolbar(cit->name);
955 void GuiView::initToolbar(string const & name)
957 GuiToolbar * tb = toolbar(name);
960 int const visibility = guiApp->toolbars().defaultVisibility(name);
961 bool newline = !(visibility & Toolbars::SAMEROW);
962 tb->setVisible(false);
963 tb->setVisibility(visibility);
965 if (visibility & Toolbars::TOP) {
967 addToolBarBreak(Qt::TopToolBarArea);
968 addToolBar(Qt::TopToolBarArea, tb);
971 if (visibility & Toolbars::BOTTOM) {
973 addToolBarBreak(Qt::BottomToolBarArea);
974 addToolBar(Qt::BottomToolBarArea, tb);
977 if (visibility & Toolbars::LEFT) {
979 addToolBarBreak(Qt::LeftToolBarArea);
980 addToolBar(Qt::LeftToolBarArea, tb);
983 if (visibility & Toolbars::RIGHT) {
985 addToolBarBreak(Qt::RightToolBarArea);
986 addToolBar(Qt::RightToolBarArea, tb);
989 if (visibility & Toolbars::ON)
990 tb->setVisible(true);
992 tb->setMovable(true);
996 TocModels & GuiView::tocModels()
998 return d.toc_models_;
1002 void GuiView::setFocus()
1004 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1005 QMainWindow::setFocus();
1009 bool GuiView::hasFocus() const
1011 if (currentWorkArea())
1012 return currentWorkArea()->hasFocus();
1013 if (currentMainWorkArea())
1014 return currentMainWorkArea()->hasFocus();
1015 return d.bg_widget_->hasFocus();
1019 void GuiView::focusInEvent(QFocusEvent * e)
1021 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1022 QMainWindow::focusInEvent(e);
1023 // Make sure guiApp points to the correct view.
1024 guiApp->setCurrentView(this);
1025 if (currentWorkArea())
1026 currentWorkArea()->setFocus();
1027 else if (currentMainWorkArea())
1028 currentMainWorkArea()->setFocus();
1030 d.bg_widget_->setFocus();
1034 void GuiView::showEvent(QShowEvent * e)
1036 LYXERR(Debug::GUI, "Passed Geometry "
1037 << size().height() << "x" << size().width()
1038 << "+" << pos().x() << "+" << pos().y());
1040 if (d.splitter_->count() == 0)
1041 // No work area, switch to the background widget.
1045 QMainWindow::showEvent(e);
1049 bool GuiView::closeScheduled()
1056 bool GuiView::prepareAllBuffersForLogout()
1058 Buffer * first = theBufferList().first();
1062 // First, iterate over all buffers and ask the users if unsaved
1063 // changes should be saved.
1064 // We cannot use a for loop as the buffer list cycles.
1067 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1069 b = theBufferList().next(b);
1070 } while (b != first);
1072 // Next, save session state
1073 // When a view/window was closed before without quitting LyX, there
1074 // are already entries in the lastOpened list.
1075 theSession().lastOpened().clear();
1082 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1083 ** is responsibility of the container (e.g., dialog)
1085 void GuiView::closeEvent(QCloseEvent * close_event)
1087 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1089 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1090 Alert::warning(_("Exit LyX"),
1091 _("LyX could not be closed because documents are being processed by LyX."));
1092 close_event->setAccepted(false);
1096 // If the user pressed the x (so we didn't call closeView
1097 // programmatically), we want to clear all existing entries.
1099 theSession().lastOpened().clear();
1104 // it can happen that this event arrives without selecting the view,
1105 // e.g. when clicking the close button on a background window.
1107 if (!closeWorkAreaAll()) {
1109 close_event->ignore();
1113 // Make sure that nothing will use this to be closed View.
1114 guiApp->unregisterView(this);
1116 if (isFullScreen()) {
1117 // Switch off fullscreen before closing.
1122 // Make sure the timer time out will not trigger a statusbar update.
1123 d.statusbar_timer_.stop();
1125 // Saving fullscreen requires additional tweaks in the toolbar code.
1126 // It wouldn't also work under linux natively.
1127 if (lyxrc.allow_geometry_session) {
1132 close_event->accept();
1136 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1138 if (event->mimeData()->hasUrls())
1140 /// \todo Ask lyx-devel is this is enough:
1141 /// if (event->mimeData()->hasFormat("text/plain"))
1142 /// event->acceptProposedAction();
1146 void GuiView::dropEvent(QDropEvent * event)
1148 QList<QUrl> files = event->mimeData()->urls();
1149 if (files.isEmpty())
1152 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1153 for (int i = 0; i != files.size(); ++i) {
1154 string const file = os::internal_path(fromqstr(
1155 files.at(i).toLocalFile()));
1159 string const ext = support::getExtension(file);
1160 vector<const Format *> found_formats;
1162 // Find all formats that have the correct extension.
1163 vector<const Format *> const & import_formats
1164 = theConverters().importableFormats();
1165 vector<const Format *>::const_iterator it = import_formats.begin();
1166 for (; it != import_formats.end(); ++it)
1167 if ((*it)->hasExtension(ext))
1168 found_formats.push_back(*it);
1171 if (found_formats.size() >= 1) {
1172 if (found_formats.size() > 1) {
1173 //FIXME: show a dialog to choose the correct importable format
1174 LYXERR(Debug::FILES,
1175 "Multiple importable formats found, selecting first");
1177 string const arg = found_formats[0]->name() + " " + file;
1178 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1181 //FIXME: do we have to explicitly check whether it's a lyx file?
1182 LYXERR(Debug::FILES,
1183 "No formats found, trying to open it as a lyx file");
1184 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1186 // add the functions to the queue
1187 guiApp->addToFuncRequestQueue(cmd);
1190 // now process the collected functions. We perform the events
1191 // asynchronously. This prevents potential problems in case the
1192 // BufferView is closed within an event.
1193 guiApp->processFuncRequestQueueAsync();
1197 void GuiView::message(docstring const & str)
1199 if (ForkedProcess::iAmAChild())
1202 // call is moved to GUI-thread by GuiProgress
1203 d.progress_->appendMessage(toqstr(str));
1207 void GuiView::clearMessageText()
1209 message(docstring());
1213 void GuiView::updateStatusBarMessage(QString const & str)
1215 statusBar()->showMessage(str);
1216 d.statusbar_timer_.stop();
1217 d.statusbar_timer_.start(3000);
1221 void GuiView::clearMessage()
1223 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1224 // the hasFocus function mostly returns false, even if the focus is on
1225 // a workarea in this view.
1229 d.statusbar_timer_.stop();
1233 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1235 if (wa != d.current_work_area_
1236 || wa->bufferView().buffer().isInternal())
1238 Buffer const & buf = wa->bufferView().buffer();
1239 // Set the windows title
1240 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1241 if (buf.notifiesExternalModification()) {
1242 title = bformat(_("%1$s (modified externally)"), title);
1243 // If the external modification status has changed, then maybe the status of
1244 // buffer-save has changed too.
1248 title += from_ascii(" - LyX");
1250 setWindowTitle(toqstr(title));
1251 // Sets the path for the window: this is used by OSX to
1252 // allow a context click on the title bar showing a menu
1253 // with the path up to the file
1254 setWindowFilePath(toqstr(buf.absFileName()));
1255 // Tell Qt whether the current document is changed
1256 setWindowModified(!buf.isClean());
1258 if (buf.params().shell_escape)
1259 shell_escape_->show();
1261 shell_escape_->hide();
1263 if (buf.hasReadonlyFlag())
1268 if (buf.lyxvc().inUse()) {
1269 version_control_->show();
1270 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1272 version_control_->hide();
1276 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1278 if (d.current_work_area_)
1279 // disconnect the current work area from all slots
1280 QObject::disconnect(d.current_work_area_, 0, this, 0);
1282 disconnectBufferView();
1283 connectBufferView(wa->bufferView());
1284 connectBuffer(wa->bufferView().buffer());
1285 d.current_work_area_ = wa;
1286 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1287 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1288 QObject::connect(wa, SIGNAL(busy(bool)),
1289 this, SLOT(setBusy(bool)));
1290 // connection of a signal to a signal
1291 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1292 this, SIGNAL(bufferViewChanged()));
1293 Q_EMIT updateWindowTitle(wa);
1294 Q_EMIT bufferViewChanged();
1298 void GuiView::onBufferViewChanged()
1301 // Buffer-dependent dialogs must be updated. This is done here because
1302 // some dialogs require buffer()->text.
1307 void GuiView::on_lastWorkAreaRemoved()
1310 // We already are in a close event. Nothing more to do.
1313 if (d.splitter_->count() > 1)
1314 // We have a splitter so don't close anything.
1317 // Reset and updates the dialogs.
1318 Q_EMIT bufferViewChanged();
1323 if (lyxrc.open_buffers_in_tabs)
1324 // Nothing more to do, the window should stay open.
1327 if (guiApp->viewIds().size() > 1) {
1333 // On Mac we also close the last window because the application stay
1334 // resident in memory. On other platforms we don't close the last
1335 // window because this would quit the application.
1341 void GuiView::updateStatusBar()
1343 // let the user see the explicit message
1344 if (d.statusbar_timer_.isActive())
1351 void GuiView::showMessage()
1355 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1356 if (msg.isEmpty()) {
1357 BufferView const * bv = currentBufferView();
1359 msg = toqstr(bv->cursor().currentState(devel_mode_));
1361 msg = qt_("Welcome to LyX!");
1363 statusBar()->showMessage(msg);
1367 bool GuiView::event(QEvent * e)
1371 // Useful debug code:
1372 //case QEvent::ActivationChange:
1373 //case QEvent::WindowDeactivate:
1374 //case QEvent::Paint:
1375 //case QEvent::Enter:
1376 //case QEvent::Leave:
1377 //case QEvent::HoverEnter:
1378 //case QEvent::HoverLeave:
1379 //case QEvent::HoverMove:
1380 //case QEvent::StatusTip:
1381 //case QEvent::DragEnter:
1382 //case QEvent::DragLeave:
1383 //case QEvent::Drop:
1386 case QEvent::WindowActivate: {
1387 GuiView * old_view = guiApp->currentView();
1388 if (this == old_view) {
1390 return QMainWindow::event(e);
1392 if (old_view && old_view->currentBufferView()) {
1393 // save current selection to the selection buffer to allow
1394 // middle-button paste in this window.
1395 cap::saveSelection(old_view->currentBufferView()->cursor());
1397 guiApp->setCurrentView(this);
1398 if (d.current_work_area_)
1399 on_currentWorkAreaChanged(d.current_work_area_);
1403 return QMainWindow::event(e);
1406 case QEvent::ShortcutOverride: {
1408 if (isFullScreen() && menuBar()->isHidden()) {
1409 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1410 // FIXME: we should also try to detect special LyX shortcut such as
1411 // Alt-P and Alt-M. Right now there is a hack in
1412 // GuiWorkArea::processKeySym() that hides again the menubar for
1414 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1416 return QMainWindow::event(e);
1419 return QMainWindow::event(e);
1423 return QMainWindow::event(e);
1427 void GuiView::resetWindowTitle()
1429 setWindowTitle(qt_("LyX"));
1432 bool GuiView::focusNextPrevChild(bool /*next*/)
1439 bool GuiView::busy() const
1445 void GuiView::setBusy(bool busy)
1447 bool const busy_before = busy_ > 0;
1448 busy ? ++busy_ : --busy_;
1449 if ((busy_ > 0) == busy_before)
1450 // busy state didn't change
1454 QApplication::setOverrideCursor(Qt::WaitCursor);
1457 QApplication::restoreOverrideCursor();
1462 void GuiView::resetCommandExecute()
1464 command_execute_ = false;
1469 double GuiView::pixelRatio() const
1471 #if QT_VERSION >= 0x050000
1472 return qt_scale_factor * devicePixelRatio();
1479 GuiWorkArea * GuiView::workArea(int index)
1481 if (TabWorkArea * twa = d.currentTabWorkArea())
1482 if (index < twa->count())
1483 return twa->workArea(index);
1488 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1490 if (currentWorkArea()
1491 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1492 return currentWorkArea();
1493 if (TabWorkArea * twa = d.currentTabWorkArea())
1494 return twa->workArea(buffer);
1499 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1501 // Automatically create a TabWorkArea if there are none yet.
1502 TabWorkArea * tab_widget = d.splitter_->count()
1503 ? d.currentTabWorkArea() : addTabWorkArea();
1504 return tab_widget->addWorkArea(buffer, *this);
1508 TabWorkArea * GuiView::addTabWorkArea()
1510 TabWorkArea * twa = new TabWorkArea;
1511 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1512 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1513 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1514 this, SLOT(on_lastWorkAreaRemoved()));
1516 d.splitter_->addWidget(twa);
1517 d.stack_widget_->setCurrentWidget(d.splitter_);
1522 GuiWorkArea const * GuiView::currentWorkArea() const
1524 return d.current_work_area_;
1528 GuiWorkArea * GuiView::currentWorkArea()
1530 return d.current_work_area_;
1534 GuiWorkArea const * GuiView::currentMainWorkArea() const
1536 if (!d.currentTabWorkArea())
1538 return d.currentTabWorkArea()->currentWorkArea();
1542 GuiWorkArea * GuiView::currentMainWorkArea()
1544 if (!d.currentTabWorkArea())
1546 return d.currentTabWorkArea()->currentWorkArea();
1550 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1552 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1554 d.current_work_area_ = 0;
1556 Q_EMIT bufferViewChanged();
1560 // FIXME: I've no clue why this is here and why it accesses
1561 // theGuiApp()->currentView, which might be 0 (bug 6464).
1562 // See also 27525 (vfr).
1563 if (theGuiApp()->currentView() == this
1564 && theGuiApp()->currentView()->currentWorkArea() == wa)
1567 if (currentBufferView())
1568 cap::saveSelection(currentBufferView()->cursor());
1570 theGuiApp()->setCurrentView(this);
1571 d.current_work_area_ = wa;
1573 // We need to reset this now, because it will need to be
1574 // right if the tabWorkArea gets reset in the for loop. We
1575 // will change it back if we aren't in that case.
1576 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1577 d.current_main_work_area_ = wa;
1579 for (int i = 0; i != d.splitter_->count(); ++i) {
1580 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1581 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1582 << ", Current main wa: " << currentMainWorkArea());
1587 d.current_main_work_area_ = old_cmwa;
1589 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1590 on_currentWorkAreaChanged(wa);
1591 BufferView & bv = wa->bufferView();
1592 bv.cursor().fixIfBroken();
1594 wa->setUpdatesEnabled(true);
1595 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1599 void GuiView::removeWorkArea(GuiWorkArea * wa)
1601 LASSERT(wa, return);
1602 if (wa == d.current_work_area_) {
1604 disconnectBufferView();
1605 d.current_work_area_ = 0;
1606 d.current_main_work_area_ = 0;
1609 bool found_twa = false;
1610 for (int i = 0; i != d.splitter_->count(); ++i) {
1611 TabWorkArea * twa = d.tabWorkArea(i);
1612 if (twa->removeWorkArea(wa)) {
1613 // Found in this tab group, and deleted the GuiWorkArea.
1615 if (twa->count() != 0) {
1616 if (d.current_work_area_ == 0)
1617 // This means that we are closing the current GuiWorkArea, so
1618 // switch to the next GuiWorkArea in the found TabWorkArea.
1619 setCurrentWorkArea(twa->currentWorkArea());
1621 // No more WorkAreas in this tab group, so delete it.
1628 // It is not a tabbed work area (i.e., the search work area), so it
1629 // should be deleted by other means.
1630 LASSERT(found_twa, return);
1632 if (d.current_work_area_ == 0) {
1633 if (d.splitter_->count() != 0) {
1634 TabWorkArea * twa = d.currentTabWorkArea();
1635 setCurrentWorkArea(twa->currentWorkArea());
1637 // No more work areas, switch to the background widget.
1638 setCurrentWorkArea(0);
1644 LayoutBox * GuiView::getLayoutDialog() const
1650 void GuiView::updateLayoutList()
1653 d.layout_->updateContents(false);
1657 void GuiView::updateToolbars()
1659 ToolbarMap::iterator end = d.toolbars_.end();
1660 if (d.current_work_area_) {
1662 if (d.current_work_area_->bufferView().cursor().inMathed()
1663 && !d.current_work_area_->bufferView().cursor().inRegexped())
1664 context |= Toolbars::MATH;
1665 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1666 context |= Toolbars::TABLE;
1667 if (currentBufferView()->buffer().areChangesPresent()
1668 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1669 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1670 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1671 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1672 context |= Toolbars::REVIEW;
1673 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1674 context |= Toolbars::MATHMACROTEMPLATE;
1675 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1676 context |= Toolbars::IPA;
1677 if (command_execute_)
1678 context |= Toolbars::MINIBUFFER;
1679 if (minibuffer_focus_) {
1680 context |= Toolbars::MINIBUFFER_FOCUS;
1681 minibuffer_focus_ = false;
1684 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1685 it->second->update(context);
1687 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1688 it->second->update();
1692 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1694 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1695 LASSERT(newBuffer, return);
1697 GuiWorkArea * wa = workArea(*newBuffer);
1700 newBuffer->masterBuffer()->updateBuffer();
1702 wa = addWorkArea(*newBuffer);
1703 // scroll to the position when the BufferView was last closed
1704 if (lyxrc.use_lastfilepos) {
1705 LastFilePosSection::FilePos filepos =
1706 theSession().lastFilePos().load(newBuffer->fileName());
1707 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1710 //Disconnect the old buffer...there's no new one.
1713 connectBuffer(*newBuffer);
1714 connectBufferView(wa->bufferView());
1716 setCurrentWorkArea(wa);
1720 void GuiView::connectBuffer(Buffer & buf)
1722 buf.setGuiDelegate(this);
1726 void GuiView::disconnectBuffer()
1728 if (d.current_work_area_)
1729 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1733 void GuiView::connectBufferView(BufferView & bv)
1735 bv.setGuiDelegate(this);
1739 void GuiView::disconnectBufferView()
1741 if (d.current_work_area_)
1742 d.current_work_area_->bufferView().setGuiDelegate(0);
1746 void GuiView::errors(string const & error_type, bool from_master)
1748 BufferView const * const bv = currentBufferView();
1752 ErrorList const & el = from_master ?
1753 bv->buffer().masterBuffer()->errorList(error_type) :
1754 bv->buffer().errorList(error_type);
1759 string err = error_type;
1761 err = "from_master|" + error_type;
1762 showDialog("errorlist", err);
1766 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1768 d.toc_models_.updateItem(toqstr(type), dit);
1772 void GuiView::structureChanged()
1774 // This is called from the Buffer, which has no way to ensure that cursors
1775 // in BufferView remain valid.
1776 if (documentBufferView())
1777 documentBufferView()->cursor().sanitize();
1778 // FIXME: This is slightly expensive, though less than the tocBackend update
1779 // (#9880). This also resets the view in the Toc Widget (#6675).
1780 d.toc_models_.reset(documentBufferView());
1781 // Navigator needs more than a simple update in this case. It needs to be
1783 updateDialog("toc", "");
1787 void GuiView::updateDialog(string const & name, string const & sdata)
1789 if (!isDialogVisible(name))
1792 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1793 if (it == d.dialogs_.end())
1796 Dialog * const dialog = it->second.get();
1797 if (dialog->isVisibleView())
1798 dialog->initialiseParams(sdata);
1802 BufferView * GuiView::documentBufferView()
1804 return currentMainWorkArea()
1805 ? ¤tMainWorkArea()->bufferView()
1810 BufferView const * GuiView::documentBufferView() const
1812 return currentMainWorkArea()
1813 ? ¤tMainWorkArea()->bufferView()
1818 BufferView * GuiView::currentBufferView()
1820 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1824 BufferView const * GuiView::currentBufferView() const
1826 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1830 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1831 Buffer const * orig, Buffer * clone)
1833 bool const success = clone->autoSave();
1835 busyBuffers.remove(orig);
1837 ? _("Automatic save done.")
1838 : _("Automatic save failed!");
1842 void GuiView::autoSave()
1844 LYXERR(Debug::INFO, "Running autoSave()");
1846 Buffer * buffer = documentBufferView()
1847 ? &documentBufferView()->buffer() : 0;
1849 resetAutosaveTimers();
1853 GuiViewPrivate::busyBuffers.insert(buffer);
1854 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1855 buffer, buffer->cloneBufferOnly());
1856 d.autosave_watcher_.setFuture(f);
1857 resetAutosaveTimers();
1861 void GuiView::resetAutosaveTimers()
1864 d.autosave_timeout_.restart();
1868 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1871 Buffer * buf = currentBufferView()
1872 ? ¤tBufferView()->buffer() : 0;
1873 Buffer * doc_buffer = documentBufferView()
1874 ? &(documentBufferView()->buffer()) : 0;
1877 /* In LyX/Mac, when a dialog is open, the menus of the
1878 application can still be accessed without giving focus to
1879 the main window. In this case, we want to disable the menu
1880 entries that are buffer-related.
1881 This code must not be used on Linux and Windows, since it
1882 would disable buffer-related entries when hovering over the
1883 menu (see bug #9574).
1885 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1891 // Check whether we need a buffer
1892 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1893 // no, exit directly
1894 flag.message(from_utf8(N_("Command not allowed with"
1895 "out any document open")));
1896 flag.setEnabled(false);
1900 if (cmd.origin() == FuncRequest::TOC) {
1901 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1902 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1903 flag.setEnabled(false);
1907 switch(cmd.action()) {
1908 case LFUN_BUFFER_IMPORT:
1911 case LFUN_MASTER_BUFFER_EXPORT:
1913 && (doc_buffer->parent() != 0
1914 || doc_buffer->hasChildren())
1915 && !d.processing_thread_watcher_.isRunning()
1916 // this launches a dialog, which would be in the wrong Buffer
1917 && !(::lyx::operator==(cmd.argument(), "custom"));
1920 case LFUN_MASTER_BUFFER_UPDATE:
1921 case LFUN_MASTER_BUFFER_VIEW:
1923 && (doc_buffer->parent() != 0
1924 || doc_buffer->hasChildren())
1925 && !d.processing_thread_watcher_.isRunning();
1928 case LFUN_BUFFER_UPDATE:
1929 case LFUN_BUFFER_VIEW: {
1930 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1934 string format = to_utf8(cmd.argument());
1935 if (cmd.argument().empty())
1936 format = doc_buffer->params().getDefaultOutputFormat();
1937 enable = doc_buffer->params().isExportable(format, true);
1941 case LFUN_BUFFER_RELOAD:
1942 enable = doc_buffer && !doc_buffer->isUnnamed()
1943 && doc_buffer->fileName().exists()
1944 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1947 case LFUN_BUFFER_CHILD_OPEN:
1948 enable = doc_buffer != 0;
1951 case LFUN_BUFFER_WRITE:
1952 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1955 //FIXME: This LFUN should be moved to GuiApplication.
1956 case LFUN_BUFFER_WRITE_ALL: {
1957 // We enable the command only if there are some modified buffers
1958 Buffer * first = theBufferList().first();
1963 // We cannot use a for loop as the buffer list is a cycle.
1965 if (!b->isClean()) {
1969 b = theBufferList().next(b);
1970 } while (b != first);
1974 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1975 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1978 case LFUN_BUFFER_EXPORT: {
1979 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1983 return doc_buffer->getStatus(cmd, flag);
1987 case LFUN_BUFFER_EXPORT_AS:
1988 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1993 case LFUN_BUFFER_WRITE_AS:
1994 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
1995 enable = doc_buffer != 0;
1998 case LFUN_EXPORT_CANCEL:
1999 enable = d.processing_thread_watcher_.isRunning();
2002 case LFUN_BUFFER_CLOSE:
2003 case LFUN_VIEW_CLOSE:
2004 enable = doc_buffer != 0;
2007 case LFUN_BUFFER_CLOSE_ALL:
2008 enable = theBufferList().last() != theBufferList().first();
2011 case LFUN_BUFFER_CHKTEX: {
2012 // hide if we have no checktex command
2013 if (lyxrc.chktex_command.empty()) {
2014 flag.setUnknown(true);
2018 if (!doc_buffer || !doc_buffer->params().isLatex()
2019 || d.processing_thread_watcher_.isRunning()) {
2020 // grey out, don't hide
2028 case LFUN_VIEW_SPLIT:
2029 if (cmd.getArg(0) == "vertical")
2030 enable = doc_buffer && (d.splitter_->count() == 1 ||
2031 d.splitter_->orientation() == Qt::Vertical);
2033 enable = doc_buffer && (d.splitter_->count() == 1 ||
2034 d.splitter_->orientation() == Qt::Horizontal);
2037 case LFUN_TAB_GROUP_CLOSE:
2038 enable = d.tabWorkAreaCount() > 1;
2041 case LFUN_DEVEL_MODE_TOGGLE:
2042 flag.setOnOff(devel_mode_);
2045 case LFUN_TOOLBAR_TOGGLE: {
2046 string const name = cmd.getArg(0);
2047 if (GuiToolbar * t = toolbar(name))
2048 flag.setOnOff(t->isVisible());
2051 docstring const msg =
2052 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2058 case LFUN_TOOLBAR_MOVABLE: {
2059 string const name = cmd.getArg(0);
2060 // use negation since locked == !movable
2062 // toolbar name * locks all toolbars
2063 flag.setOnOff(!toolbarsMovable_);
2064 else if (GuiToolbar * t = toolbar(name))
2065 flag.setOnOff(!(t->isMovable()));
2068 docstring const msg =
2069 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2075 case LFUN_ICON_SIZE:
2076 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2079 case LFUN_DROP_LAYOUTS_CHOICE:
2083 case LFUN_UI_TOGGLE:
2084 flag.setOnOff(isFullScreen());
2087 case LFUN_DIALOG_DISCONNECT_INSET:
2090 case LFUN_DIALOG_HIDE:
2091 // FIXME: should we check if the dialog is shown?
2094 case LFUN_DIALOG_TOGGLE:
2095 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2098 case LFUN_DIALOG_SHOW: {
2099 string const name = cmd.getArg(0);
2101 enable = name == "aboutlyx"
2102 || name == "file" //FIXME: should be removed.
2103 || name == "lyxfiles"
2105 || name == "texinfo"
2106 || name == "progress"
2107 || name == "compare";
2108 else if (name == "character" || name == "symbols"
2109 || name == "mathdelimiter" || name == "mathmatrix") {
2110 if (!buf || buf->isReadonly())
2113 Cursor const & cur = currentBufferView()->cursor();
2114 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2117 else if (name == "latexlog")
2118 enable = FileName(doc_buffer->logName()).isReadableFile();
2119 else if (name == "spellchecker")
2120 enable = theSpellChecker()
2121 && !doc_buffer->isReadonly()
2122 && !doc_buffer->text().empty();
2123 else if (name == "vclog")
2124 enable = doc_buffer->lyxvc().inUse();
2128 case LFUN_DIALOG_UPDATE: {
2129 string const name = cmd.getArg(0);
2131 enable = name == "prefs";
2135 case LFUN_COMMAND_EXECUTE:
2137 case LFUN_MENU_OPEN:
2138 // Nothing to check.
2141 case LFUN_COMPLETION_INLINE:
2142 if (!d.current_work_area_
2143 || !d.current_work_area_->completer().inlinePossible(
2144 currentBufferView()->cursor()))
2148 case LFUN_COMPLETION_POPUP:
2149 if (!d.current_work_area_
2150 || !d.current_work_area_->completer().popupPossible(
2151 currentBufferView()->cursor()))
2156 if (!d.current_work_area_
2157 || !d.current_work_area_->completer().inlinePossible(
2158 currentBufferView()->cursor()))
2162 case LFUN_COMPLETION_ACCEPT:
2163 if (!d.current_work_area_
2164 || (!d.current_work_area_->completer().popupVisible()
2165 && !d.current_work_area_->completer().inlineVisible()
2166 && !d.current_work_area_->completer().completionAvailable()))
2170 case LFUN_COMPLETION_CANCEL:
2171 if (!d.current_work_area_
2172 || (!d.current_work_area_->completer().popupVisible()
2173 && !d.current_work_area_->completer().inlineVisible()))
2177 case LFUN_BUFFER_ZOOM_OUT:
2178 case LFUN_BUFFER_ZOOM_IN: {
2179 // only diff between these two is that the default for ZOOM_OUT
2181 bool const neg_zoom =
2182 convert<int>(cmd.argument()) < 0 ||
2183 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2184 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2185 docstring const msg =
2186 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2190 enable = doc_buffer;
2194 case LFUN_BUFFER_ZOOM: {
2195 bool const less_than_min_zoom =
2196 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2197 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2198 docstring const msg =
2199 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2204 enable = doc_buffer;
2208 case LFUN_BUFFER_MOVE_NEXT:
2209 case LFUN_BUFFER_MOVE_PREVIOUS:
2210 // we do not cycle when moving
2211 case LFUN_BUFFER_NEXT:
2212 case LFUN_BUFFER_PREVIOUS:
2213 // because we cycle, it doesn't matter whether on first or last
2214 enable = (d.currentTabWorkArea()->count() > 1);
2216 case LFUN_BUFFER_SWITCH:
2217 // toggle on the current buffer, but do not toggle off
2218 // the other ones (is that a good idea?)
2220 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2221 flag.setOnOff(true);
2224 case LFUN_VC_REGISTER:
2225 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2227 case LFUN_VC_RENAME:
2228 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2231 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2233 case LFUN_VC_CHECK_IN:
2234 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2236 case LFUN_VC_CHECK_OUT:
2237 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2239 case LFUN_VC_LOCKING_TOGGLE:
2240 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2241 && doc_buffer->lyxvc().lockingToggleEnabled();
2242 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2244 case LFUN_VC_REVERT:
2245 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2246 && !doc_buffer->hasReadonlyFlag();
2248 case LFUN_VC_UNDO_LAST:
2249 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2251 case LFUN_VC_REPO_UPDATE:
2252 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2254 case LFUN_VC_COMMAND: {
2255 if (cmd.argument().empty())
2257 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2261 case LFUN_VC_COMPARE:
2262 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2265 case LFUN_SERVER_GOTO_FILE_ROW:
2266 case LFUN_LYX_ACTIVATE:
2268 case LFUN_FORWARD_SEARCH:
2269 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2272 case LFUN_FILE_INSERT_PLAINTEXT:
2273 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2274 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2277 case LFUN_SPELLING_CONTINUOUSLY:
2278 flag.setOnOff(lyxrc.spellcheck_continuously);
2286 flag.setEnabled(false);
2292 static FileName selectTemplateFile()
2294 FileDialog dlg(qt_("Select template file"));
2295 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2296 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2298 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2299 QStringList(qt_("LyX Documents (*.lyx)")));
2301 if (result.first == FileDialog::Later)
2303 if (result.second.isEmpty())
2305 return FileName(fromqstr(result.second));
2309 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2313 Buffer * newBuffer = 0;
2315 newBuffer = checkAndLoadLyXFile(filename);
2316 } catch (ExceptionMessage const & e) {
2323 message(_("Document not loaded."));
2327 setBuffer(newBuffer);
2328 newBuffer->errors("Parse");
2331 theSession().lastFiles().add(filename);
2332 theSession().writeFile();
2339 void GuiView::openDocument(string const & fname)
2341 string initpath = lyxrc.document_path;
2343 if (documentBufferView()) {
2344 string const trypath = documentBufferView()->buffer().filePath();
2345 // If directory is writeable, use this as default.
2346 if (FileName(trypath).isDirWritable())
2352 if (fname.empty()) {
2353 FileDialog dlg(qt_("Select document to open"));
2354 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2355 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2357 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2358 FileDialog::Result result =
2359 dlg.open(toqstr(initpath), filter);
2361 if (result.first == FileDialog::Later)
2364 filename = fromqstr(result.second);
2366 // check selected filename
2367 if (filename.empty()) {
2368 message(_("Canceled."));
2374 // get absolute path of file and add ".lyx" to the filename if
2376 FileName const fullname =
2377 fileSearch(string(), filename, "lyx", support::may_not_exist);
2378 if (!fullname.empty())
2379 filename = fullname.absFileName();
2381 if (!fullname.onlyPath().isDirectory()) {
2382 Alert::warning(_("Invalid filename"),
2383 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2384 from_utf8(fullname.absFileName())));
2388 // if the file doesn't exist and isn't already open (bug 6645),
2389 // let the user create one
2390 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2391 !LyXVC::file_not_found_hook(fullname)) {
2392 // the user specifically chose this name. Believe him.
2393 Buffer * const b = newFile(filename, string(), true);
2399 docstring const disp_fn = makeDisplayPath(filename);
2400 message(bformat(_("Opening document %1$s..."), disp_fn));
2403 Buffer * buf = loadDocument(fullname);
2405 str2 = bformat(_("Document %1$s opened."), disp_fn);
2406 if (buf->lyxvc().inUse())
2407 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2408 " " + _("Version control detected.");
2410 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2415 // FIXME: clean that
2416 static bool import(GuiView * lv, FileName const & filename,
2417 string const & format, ErrorList & errorList)
2419 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2421 string loader_format;
2422 vector<string> loaders = theConverters().loaders();
2423 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2424 vector<string>::const_iterator it = loaders.begin();
2425 vector<string>::const_iterator en = loaders.end();
2426 for (; it != en; ++it) {
2427 if (!theConverters().isReachable(format, *it))
2430 string const tofile =
2431 support::changeExtension(filename.absFileName(),
2432 theFormats().extension(*it));
2433 if (theConverters().convert(0, filename, FileName(tofile),
2434 filename, format, *it, errorList) != Converters::SUCCESS)
2436 loader_format = *it;
2439 if (loader_format.empty()) {
2440 frontend::Alert::error(_("Couldn't import file"),
2441 bformat(_("No information for importing the format %1$s."),
2442 theFormats().prettyName(format)));
2446 loader_format = format;
2448 if (loader_format == "lyx") {
2449 Buffer * buf = lv->loadDocument(lyxfile);
2453 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2457 bool as_paragraphs = loader_format == "textparagraph";
2458 string filename2 = (loader_format == format) ? filename.absFileName()
2459 : support::changeExtension(filename.absFileName(),
2460 theFormats().extension(loader_format));
2461 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2463 guiApp->setCurrentView(lv);
2464 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2471 void GuiView::importDocument(string const & argument)
2474 string filename = split(argument, format, ' ');
2476 LYXERR(Debug::INFO, format << " file: " << filename);
2478 // need user interaction
2479 if (filename.empty()) {
2480 string initpath = lyxrc.document_path;
2481 if (documentBufferView()) {
2482 string const trypath = documentBufferView()->buffer().filePath();
2483 // If directory is writeable, use this as default.
2484 if (FileName(trypath).isDirWritable())
2488 docstring const text = bformat(_("Select %1$s file to import"),
2489 theFormats().prettyName(format));
2491 FileDialog dlg(toqstr(text));
2492 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2493 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2495 docstring filter = theFormats().prettyName(format);
2498 filter += from_utf8(theFormats().extensions(format));
2501 FileDialog::Result result =
2502 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2504 if (result.first == FileDialog::Later)
2507 filename = fromqstr(result.second);
2509 // check selected filename
2510 if (filename.empty())
2511 message(_("Canceled."));
2514 if (filename.empty())
2517 // get absolute path of file
2518 FileName const fullname(support::makeAbsPath(filename));
2520 // Can happen if the user entered a path into the dialog
2522 if (fullname.onlyFileName().empty()) {
2523 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2524 "Aborting import."),
2525 from_utf8(fullname.absFileName()));
2526 frontend::Alert::error(_("File name error"), msg);
2527 message(_("Canceled."));
2532 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2534 // Check if the document already is open
2535 Buffer * buf = theBufferList().getBuffer(lyxfile);
2538 if (!closeBuffer()) {
2539 message(_("Canceled."));
2544 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2546 // if the file exists already, and we didn't do
2547 // -i lyx thefile.lyx, warn
2548 if (lyxfile.exists() && fullname != lyxfile) {
2550 docstring text = bformat(_("The document %1$s already exists.\n\n"
2551 "Do you want to overwrite that document?"), displaypath);
2552 int const ret = Alert::prompt(_("Overwrite document?"),
2553 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2556 message(_("Canceled."));
2561 message(bformat(_("Importing %1$s..."), displaypath));
2562 ErrorList errorList;
2563 if (import(this, fullname, format, errorList))
2564 message(_("imported."));
2566 message(_("file not imported!"));
2568 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2572 void GuiView::newDocument(string const & filename, string templatefile,
2575 FileName initpath(lyxrc.document_path);
2576 if (documentBufferView()) {
2577 FileName const trypath(documentBufferView()->buffer().filePath());
2578 // If directory is writeable, use this as default.
2579 if (trypath.isDirWritable())
2583 if (from_template) {
2584 if (templatefile.empty())
2585 templatefile = selectTemplateFile().absFileName();
2586 if (templatefile.empty())
2591 if (filename.empty())
2592 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2594 b = newFile(filename, templatefile, true);
2599 // If no new document could be created, it is unsure
2600 // whether there is a valid BufferView.
2601 if (currentBufferView())
2602 // Ensure the cursor is correctly positioned on screen.
2603 currentBufferView()->showCursor();
2607 void GuiView::insertLyXFile(docstring const & fname)
2609 BufferView * bv = documentBufferView();
2614 FileName filename(to_utf8(fname));
2615 if (filename.empty()) {
2616 // Launch a file browser
2618 string initpath = lyxrc.document_path;
2619 string const trypath = bv->buffer().filePath();
2620 // If directory is writeable, use this as default.
2621 if (FileName(trypath).isDirWritable())
2625 FileDialog dlg(qt_("Select LyX document to insert"));
2626 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2627 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2629 FileDialog::Result result = dlg.open(toqstr(initpath),
2630 QStringList(qt_("LyX Documents (*.lyx)")));
2632 if (result.first == FileDialog::Later)
2636 filename.set(fromqstr(result.second));
2638 // check selected filename
2639 if (filename.empty()) {
2640 // emit message signal.
2641 message(_("Canceled."));
2646 bv->insertLyXFile(filename);
2647 bv->buffer().errors("Parse");
2651 string const GuiView::getTemplatesPath(Buffer & b)
2653 // We start off with the user's templates path
2654 string result = addPath(package().user_support().absFileName(), "templates");
2655 // Do we have a layout category?
2656 string const cat = b.params().baseClass() ?
2657 b.params().baseClass()->category()
2660 string subpath = addPath(result, cat);
2661 // If we have a subdirectory for the category already,
2663 FileName sp = FileName(subpath);
2664 if (sp.isDirectory())
2667 // Ask whether we should create such a subdirectory
2668 docstring const text =
2669 bformat(_("It is suggested to save the template in a subdirectory\n"
2670 "appropriate to the layout category (%1$s).\n"
2671 "This subdirectory does not exists yet.\n"
2672 "Do you want to create it?"),
2674 if (Alert::prompt(_("Create Category Directory?"),
2675 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2676 // If the user agreed, we try to create it and report if this failed.
2677 if (!sp.createDirectory(0777))
2678 Alert::error(_("Subdirectory creation failed!"),
2679 _("Could not create subdirectory.\n"
2680 "The template will be saved in the parent directory."));
2690 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2692 FileName fname = b.fileName();
2693 FileName const oldname = fname;
2694 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2696 if (!newname.empty()) {
2699 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2701 fname = support::makeAbsPath(to_utf8(newname),
2702 oldname.onlyPath().absFileName());
2704 // Switch to this Buffer.
2707 // No argument? Ask user through dialog.
2709 QString const title = as_template ? qt_("Choose a filename to save template as")
2710 : qt_("Choose a filename to save document as");
2711 FileDialog dlg(title);
2712 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2713 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2715 if (!isLyXFileName(fname.absFileName()))
2716 fname.changeExtension(".lyx");
2718 string const path = as_template ?
2720 : fname.onlyPath().absFileName();
2721 FileDialog::Result result =
2722 dlg.save(toqstr(path),
2723 QStringList(qt_("LyX Documents (*.lyx)")),
2724 toqstr(fname.onlyFileName()));
2726 if (result.first == FileDialog::Later)
2729 fname.set(fromqstr(result.second));
2734 if (!isLyXFileName(fname.absFileName()))
2735 fname.changeExtension(".lyx");
2738 // fname is now the new Buffer location.
2740 // if there is already a Buffer open with this name, we do not want
2741 // to have another one. (the second test makes sure we're not just
2742 // trying to overwrite ourselves, which is fine.)
2743 if (theBufferList().exists(fname) && fname != oldname
2744 && theBufferList().getBuffer(fname) != &b) {
2745 docstring const text =
2746 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2747 "Please close it before attempting to overwrite it.\n"
2748 "Do you want to choose a new filename?"),
2749 from_utf8(fname.absFileName()));
2750 int const ret = Alert::prompt(_("Chosen File Already Open"),
2751 text, 0, 1, _("&Rename"), _("&Cancel"));
2753 case 0: return renameBuffer(b, docstring(), kind);
2754 case 1: return false;
2759 bool const existsLocal = fname.exists();
2760 bool const existsInVC = LyXVC::fileInVC(fname);
2761 if (existsLocal || existsInVC) {
2762 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2763 if (kind != LV_WRITE_AS && existsInVC) {
2764 // renaming to a name that is already in VC
2766 docstring text = bformat(_("The document %1$s "
2767 "is already registered.\n\n"
2768 "Do you want to choose a new name?"),
2770 docstring const title = (kind == LV_VC_RENAME) ?
2771 _("Rename document?") : _("Copy document?");
2772 docstring const button = (kind == LV_VC_RENAME) ?
2773 _("&Rename") : _("&Copy");
2774 int const ret = Alert::prompt(title, text, 0, 1,
2775 button, _("&Cancel"));
2777 case 0: return renameBuffer(b, docstring(), kind);
2778 case 1: return false;
2783 docstring text = bformat(_("The document %1$s "
2784 "already exists.\n\n"
2785 "Do you want to overwrite that document?"),
2787 int const ret = Alert::prompt(_("Overwrite document?"),
2788 text, 0, 2, _("&Overwrite"),
2789 _("&Rename"), _("&Cancel"));
2792 case 1: return renameBuffer(b, docstring(), kind);
2793 case 2: return false;
2799 case LV_VC_RENAME: {
2800 string msg = b.lyxvc().rename(fname);
2803 message(from_utf8(msg));
2807 string msg = b.lyxvc().copy(fname);
2810 message(from_utf8(msg));
2814 case LV_WRITE_AS_TEMPLATE:
2817 // LyXVC created the file already in case of LV_VC_RENAME or
2818 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2819 // relative paths of included stuff right if we moved e.g. from
2820 // /a/b.lyx to /a/c/b.lyx.
2822 bool const saved = saveBuffer(b, fname);
2829 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2831 FileName fname = b.fileName();
2833 FileDialog dlg(qt_("Choose a filename to export the document as"));
2834 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2837 QString const anyformat = qt_("Guess from extension (*.*)");
2840 vector<Format const *> export_formats;
2841 for (Format const & f : theFormats())
2842 if (f.documentFormat())
2843 export_formats.push_back(&f);
2844 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2845 map<QString, string> fmap;
2848 for (Format const * f : export_formats) {
2849 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2850 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2852 from_ascii(f->extension())));
2853 types << loc_filter;
2854 fmap[loc_filter] = f->name();
2855 if (from_ascii(f->name()) == iformat) {
2856 filter = loc_filter;
2857 ext = f->extension();
2860 string ofname = fname.onlyFileName();
2862 ofname = support::changeExtension(ofname, ext);
2863 FileDialog::Result result =
2864 dlg.save(toqstr(fname.onlyPath().absFileName()),
2868 if (result.first != FileDialog::Chosen)
2872 fname.set(fromqstr(result.second));
2873 if (filter == anyformat)
2874 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2876 fmt_name = fmap[filter];
2877 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2878 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2880 if (fmt_name.empty() || fname.empty())
2883 // fname is now the new Buffer location.
2884 if (FileName(fname).exists()) {
2885 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2886 docstring text = bformat(_("The document %1$s already "
2887 "exists.\n\nDo you want to "
2888 "overwrite that document?"),
2890 int const ret = Alert::prompt(_("Overwrite document?"),
2891 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2894 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2895 case 2: return false;
2899 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2902 return dr.dispatched();
2906 bool GuiView::saveBuffer(Buffer & b)
2908 return saveBuffer(b, FileName());
2912 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2914 if (workArea(b) && workArea(b)->inDialogMode())
2917 if (fn.empty() && b.isUnnamed())
2918 return renameBuffer(b, docstring());
2920 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2922 theSession().lastFiles().add(b.fileName());
2923 theSession().writeFile();
2927 // Switch to this Buffer.
2930 // FIXME: we don't tell the user *WHY* the save failed !!
2931 docstring const file = makeDisplayPath(b.absFileName(), 30);
2932 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2933 "Do you want to rename the document and "
2934 "try again?"), file);
2935 int const ret = Alert::prompt(_("Rename and save?"),
2936 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2939 if (!renameBuffer(b, docstring()))
2948 return saveBuffer(b, fn);
2952 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2954 return closeWorkArea(wa, false);
2958 // We only want to close the buffer if it is not visible in other workareas
2959 // of the same view, nor in other views, and if this is not a child
2960 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2962 Buffer & buf = wa->bufferView().buffer();
2964 bool last_wa = d.countWorkAreasOf(buf) == 1
2965 && !inOtherView(buf) && !buf.parent();
2967 bool close_buffer = last_wa;
2970 if (lyxrc.close_buffer_with_last_view == "yes")
2972 else if (lyxrc.close_buffer_with_last_view == "no")
2973 close_buffer = false;
2976 if (buf.isUnnamed())
2977 file = from_utf8(buf.fileName().onlyFileName());
2979 file = buf.fileName().displayName(30);
2980 docstring const text = bformat(
2981 _("Last view on document %1$s is being closed.\n"
2982 "Would you like to close or hide the document?\n"
2984 "Hidden documents can be displayed back through\n"
2985 "the menu: View->Hidden->...\n"
2987 "To remove this question, set your preference in:\n"
2988 " Tools->Preferences->Look&Feel->UserInterface\n"
2990 int ret = Alert::prompt(_("Close or hide document?"),
2991 text, 0, 1, _("&Close"), _("&Hide"));
2992 close_buffer = (ret == 0);
2996 return closeWorkArea(wa, close_buffer);
3000 bool GuiView::closeBuffer()
3002 GuiWorkArea * wa = currentMainWorkArea();
3003 // coverity complained about this
3004 // it seems unnecessary, but perhaps is worth the check
3005 LASSERT(wa, return false);
3007 setCurrentWorkArea(wa);
3008 Buffer & buf = wa->bufferView().buffer();
3009 return closeWorkArea(wa, !buf.parent());
3013 void GuiView::writeSession() const {
3014 GuiWorkArea const * active_wa = currentMainWorkArea();
3015 for (int i = 0; i < d.splitter_->count(); ++i) {
3016 TabWorkArea * twa = d.tabWorkArea(i);
3017 for (int j = 0; j < twa->count(); ++j) {
3018 GuiWorkArea * wa = twa->workArea(j);
3019 Buffer & buf = wa->bufferView().buffer();
3020 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3026 bool GuiView::closeBufferAll()
3028 // Close the workareas in all other views
3029 QList<int> const ids = guiApp->viewIds();
3030 for (int i = 0; i != ids.size(); ++i) {
3031 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3035 // Close our own workareas
3036 if (!closeWorkAreaAll())
3039 // Now close the hidden buffers. We prevent hidden buffers from being
3040 // dirty, so we can just close them.
3041 theBufferList().closeAll();
3046 bool GuiView::closeWorkAreaAll()
3048 setCurrentWorkArea(currentMainWorkArea());
3050 // We might be in a situation that there is still a tabWorkArea, but
3051 // there are no tabs anymore. This can happen when we get here after a
3052 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3053 // many TabWorkArea's have no documents anymore.
3056 // We have to call count() each time, because it can happen that
3057 // more than one splitter will disappear in one iteration (bug 5998).
3058 while (d.splitter_->count() > empty_twa) {
3059 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3061 if (twa->count() == 0)
3064 setCurrentWorkArea(twa->currentWorkArea());
3065 if (!closeTabWorkArea(twa))
3073 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3078 Buffer & buf = wa->bufferView().buffer();
3080 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3081 Alert::warning(_("Close document"),
3082 _("Document could not be closed because it is being processed by LyX."));
3087 return closeBuffer(buf);
3089 if (!inMultiTabs(wa))
3090 if (!saveBufferIfNeeded(buf, true))
3098 bool GuiView::closeBuffer(Buffer & buf)
3100 bool success = true;
3101 ListOfBuffers clist = buf.getChildren();
3102 ListOfBuffers::const_iterator it = clist.begin();
3103 ListOfBuffers::const_iterator const bend = clist.end();
3104 for (; it != bend; ++it) {
3105 Buffer * child_buf = *it;
3106 if (theBufferList().isOthersChild(&buf, child_buf)) {
3107 child_buf->setParent(0);
3111 // FIXME: should we look in other tabworkareas?
3112 // ANSWER: I don't think so. I've tested, and if the child is
3113 // open in some other window, it closes without a problem.
3114 GuiWorkArea * child_wa = workArea(*child_buf);
3117 // If we are in a close_event all children will be closed in some time,
3118 // so no need to do it here. This will ensure that the children end up
3119 // in the session file in the correct order. If we close the master
3120 // buffer, we can close or release the child buffers here too.
3122 success = closeWorkArea(child_wa, true);
3126 // In this case the child buffer is open but hidden.
3127 // Even in this case, children can be dirty (e.g.,
3128 // after a label change in the master, see #11405).
3129 // Therefore, check this
3130 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3131 // If we are in a close_event all children will be closed in some time,
3132 // so no need to do it here. This will ensure that the children end up
3133 // in the session file in the correct order. If we close the master
3134 // buffer, we can close or release the child buffers here too.
3136 // Save dirty buffers also if closing_!
3137 if (saveBufferIfNeeded(*child_buf, false)) {
3138 child_buf->removeAutosaveFile();
3139 theBufferList().release(child_buf);
3141 // Saving of dirty children has been cancelled.
3142 // Cancel the whole process.
3149 // goto bookmark to update bookmark pit.
3150 // FIXME: we should update only the bookmarks related to this buffer!
3151 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3152 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3153 guiApp->gotoBookmark(i+1, false, false);
3155 if (saveBufferIfNeeded(buf, false)) {
3156 buf.removeAutosaveFile();
3157 theBufferList().release(&buf);
3161 // open all children again to avoid a crash because of dangling
3162 // pointers (bug 6603)
3168 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3170 while (twa == d.currentTabWorkArea()) {
3171 twa->setCurrentIndex(twa->count() - 1);
3173 GuiWorkArea * wa = twa->currentWorkArea();
3174 Buffer & b = wa->bufferView().buffer();
3176 // We only want to close the buffer if the same buffer is not visible
3177 // in another view, and if this is not a child and if we are closing
3178 // a view (not a tabgroup).
3179 bool const close_buffer =
3180 !inOtherView(b) && !b.parent() && closing_;
3182 if (!closeWorkArea(wa, close_buffer))
3189 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3191 if (buf.isClean() || buf.paragraphs().empty())
3194 // Switch to this Buffer.
3200 if (buf.isUnnamed()) {
3201 file = from_utf8(buf.fileName().onlyFileName());
3204 FileName filename = buf.fileName();
3206 file = filename.displayName(30);
3207 exists = filename.exists();
3210 // Bring this window to top before asking questions.
3215 if (hiding && buf.isUnnamed()) {
3216 docstring const text = bformat(_("The document %1$s has not been "
3217 "saved yet.\n\nDo you want to save "
3218 "the document?"), file);
3219 ret = Alert::prompt(_("Save new document?"),
3220 text, 0, 1, _("&Save"), _("&Cancel"));
3224 docstring const text = exists ?
3225 bformat(_("The document %1$s has unsaved changes."
3226 "\n\nDo you want to save the document or "
3227 "discard the changes?"), file) :
3228 bformat(_("The document %1$s has not been saved yet."
3229 "\n\nDo you want to save the document or "
3230 "discard it entirely?"), file);
3231 docstring const title = exists ?
3232 _("Save changed document?") : _("Save document?");
3233 ret = Alert::prompt(title, text, 0, 2,
3234 _("&Save"), _("&Discard"), _("&Cancel"));
3239 if (!saveBuffer(buf))
3243 // If we crash after this we could have no autosave file
3244 // but I guess this is really improbable (Jug).
3245 // Sometimes improbable things happen:
3246 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3247 // buf.removeAutosaveFile();
3249 // revert all changes
3260 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3262 Buffer & buf = wa->bufferView().buffer();
3264 for (int i = 0; i != d.splitter_->count(); ++i) {
3265 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3266 if (wa_ && wa_ != wa)
3269 return inOtherView(buf);
3273 bool GuiView::inOtherView(Buffer & buf)
3275 QList<int> const ids = guiApp->viewIds();
3277 for (int i = 0; i != ids.size(); ++i) {
3281 if (guiApp->view(ids[i]).workArea(buf))
3288 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3290 if (!documentBufferView())
3293 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3294 Buffer * const curbuf = &documentBufferView()->buffer();
3295 int nwa = twa->count();
3296 for (int i = 0; i < nwa; ++i) {
3297 if (&workArea(i)->bufferView().buffer() == curbuf) {
3299 if (np == NEXTBUFFER)
3300 next_index = (i == nwa - 1 ? 0 : i + 1);
3302 next_index = (i == 0 ? nwa - 1 : i - 1);
3304 twa->moveTab(i, next_index);
3306 setBuffer(&workArea(next_index)->bufferView().buffer());
3314 /// make sure the document is saved
3315 static bool ensureBufferClean(Buffer * buffer)
3317 LASSERT(buffer, return false);
3318 if (buffer->isClean() && !buffer->isUnnamed())
3321 docstring const file = buffer->fileName().displayName(30);
3324 if (!buffer->isUnnamed()) {
3325 text = bformat(_("The document %1$s has unsaved "
3326 "changes.\n\nDo you want to save "
3327 "the document?"), file);
3328 title = _("Save changed document?");
3331 text = bformat(_("The document %1$s has not been "
3332 "saved yet.\n\nDo you want to save "
3333 "the document?"), file);
3334 title = _("Save new document?");
3336 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3339 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3341 return buffer->isClean() && !buffer->isUnnamed();
3345 bool GuiView::reloadBuffer(Buffer & buf)
3347 currentBufferView()->cursor().reset();
3348 Buffer::ReadStatus status = buf.reload();
3349 return status == Buffer::ReadSuccess;
3353 void GuiView::checkExternallyModifiedBuffers()
3355 BufferList::iterator bit = theBufferList().begin();
3356 BufferList::iterator const bend = theBufferList().end();
3357 for (; bit != bend; ++bit) {
3358 Buffer * buf = *bit;
3359 if (buf->fileName().exists() && buf->isChecksumModified()) {
3360 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3361 " Reload now? Any local changes will be lost."),
3362 from_utf8(buf->absFileName()));
3363 int const ret = Alert::prompt(_("Reload externally changed document?"),
3364 text, 0, 1, _("&Reload"), _("&Cancel"));
3372 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3374 Buffer * buffer = documentBufferView()
3375 ? &(documentBufferView()->buffer()) : 0;
3377 switch (cmd.action()) {
3378 case LFUN_VC_REGISTER:
3379 if (!buffer || !ensureBufferClean(buffer))
3381 if (!buffer->lyxvc().inUse()) {
3382 if (buffer->lyxvc().registrer()) {
3383 reloadBuffer(*buffer);
3384 dr.clearMessageUpdate();
3389 case LFUN_VC_RENAME:
3390 case LFUN_VC_COPY: {
3391 if (!buffer || !ensureBufferClean(buffer))
3393 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3394 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3395 // Some changes are not yet committed.
3396 // We test here and not in getStatus(), since
3397 // this test is expensive.
3399 LyXVC::CommandResult ret =
3400 buffer->lyxvc().checkIn(log);
3402 if (ret == LyXVC::ErrorCommand ||
3403 ret == LyXVC::VCSuccess)
3404 reloadBuffer(*buffer);
3405 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3406 frontend::Alert::error(
3407 _("Revision control error."),
3408 _("Document could not be checked in."));
3412 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3413 LV_VC_RENAME : LV_VC_COPY;
3414 renameBuffer(*buffer, cmd.argument(), kind);
3419 case LFUN_VC_CHECK_IN:
3420 if (!buffer || !ensureBufferClean(buffer))
3422 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3424 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3426 // Only skip reloading if the checkin was cancelled or
3427 // an error occurred before the real checkin VCS command
3428 // was executed, since the VCS might have changed the
3429 // file even if it could not checkin successfully.
3430 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3431 reloadBuffer(*buffer);
3435 case LFUN_VC_CHECK_OUT:
3436 if (!buffer || !ensureBufferClean(buffer))
3438 if (buffer->lyxvc().inUse()) {
3439 dr.setMessage(buffer->lyxvc().checkOut());
3440 reloadBuffer(*buffer);
3444 case LFUN_VC_LOCKING_TOGGLE:
3445 LASSERT(buffer, return);
3446 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3448 if (buffer->lyxvc().inUse()) {
3449 string res = buffer->lyxvc().lockingToggle();
3451 frontend::Alert::error(_("Revision control error."),
3452 _("Error when setting the locking property."));
3455 reloadBuffer(*buffer);
3460 case LFUN_VC_REVERT:
3461 LASSERT(buffer, return);
3462 if (buffer->lyxvc().revert()) {
3463 reloadBuffer(*buffer);
3464 dr.clearMessageUpdate();
3468 case LFUN_VC_UNDO_LAST:
3469 LASSERT(buffer, return);
3470 buffer->lyxvc().undoLast();
3471 reloadBuffer(*buffer);
3472 dr.clearMessageUpdate();
3475 case LFUN_VC_REPO_UPDATE:
3476 LASSERT(buffer, return);
3477 if (ensureBufferClean(buffer)) {
3478 dr.setMessage(buffer->lyxvc().repoUpdate());
3479 checkExternallyModifiedBuffers();
3483 case LFUN_VC_COMMAND: {
3484 string flag = cmd.getArg(0);
3485 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3488 if (contains(flag, 'M')) {
3489 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3492 string path = cmd.getArg(1);
3493 if (contains(path, "$$p") && buffer)
3494 path = subst(path, "$$p", buffer->filePath());
3495 LYXERR(Debug::LYXVC, "Directory: " << path);
3497 if (!pp.isReadableDirectory()) {
3498 lyxerr << _("Directory is not accessible.") << endl;
3501 support::PathChanger p(pp);
3503 string command = cmd.getArg(2);
3504 if (command.empty())
3507 command = subst(command, "$$i", buffer->absFileName());
3508 command = subst(command, "$$p", buffer->filePath());
3510 command = subst(command, "$$m", to_utf8(message));
3511 LYXERR(Debug::LYXVC, "Command: " << command);
3513 one.startscript(Systemcall::Wait, command);
3517 if (contains(flag, 'I'))
3518 buffer->markDirty();
3519 if (contains(flag, 'R'))
3520 reloadBuffer(*buffer);
3525 case LFUN_VC_COMPARE: {
3526 if (cmd.argument().empty()) {
3527 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3531 string rev1 = cmd.getArg(0);
3536 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3539 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3540 f2 = buffer->absFileName();
3542 string rev2 = cmd.getArg(1);
3546 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3550 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3551 f1 << "\n" << f2 << "\n" );
3552 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3553 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3563 void GuiView::openChildDocument(string const & fname)
3565 LASSERT(documentBufferView(), return);
3566 Buffer & buffer = documentBufferView()->buffer();
3567 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3568 documentBufferView()->saveBookmark(false);
3570 if (theBufferList().exists(filename)) {
3571 child = theBufferList().getBuffer(filename);
3574 message(bformat(_("Opening child document %1$s..."),
3575 makeDisplayPath(filename.absFileName())));
3576 child = loadDocument(filename, false);
3578 // Set the parent name of the child document.
3579 // This makes insertion of citations and references in the child work,
3580 // when the target is in the parent or another child document.
3582 child->setParent(&buffer);
3586 bool GuiView::goToFileRow(string const & argument)
3590 size_t i = argument.find_last_of(' ');
3591 if (i != string::npos) {
3592 file_name = os::internal_path(trim(argument.substr(0, i)));
3593 istringstream is(argument.substr(i + 1));
3598 if (i == string::npos) {
3599 LYXERR0("Wrong argument: " << argument);
3603 string const abstmp = package().temp_dir().absFileName();
3604 string const realtmp = package().temp_dir().realPath();
3605 // We have to use os::path_prefix_is() here, instead of
3606 // simply prefixIs(), because the file name comes from
3607 // an external application and may need case adjustment.
3608 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3609 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3610 // Needed by inverse dvi search. If it is a file
3611 // in tmpdir, call the apropriated function.
3612 // If tmpdir is a symlink, we may have the real
3613 // path passed back, so we correct for that.
3614 if (!prefixIs(file_name, abstmp))
3615 file_name = subst(file_name, realtmp, abstmp);
3616 buf = theBufferList().getBufferFromTmp(file_name);
3618 // Must replace extension of the file to be .lyx
3619 // and get full path
3620 FileName const s = fileSearch(string(),
3621 support::changeExtension(file_name, ".lyx"), "lyx");
3622 // Either change buffer or load the file
3623 if (theBufferList().exists(s))
3624 buf = theBufferList().getBuffer(s);
3625 else if (s.exists()) {
3626 buf = loadDocument(s);
3631 _("File does not exist: %1$s"),
3632 makeDisplayPath(file_name)));
3638 _("No buffer for file: %1$s."),
3639 makeDisplayPath(file_name))
3644 bool success = documentBufferView()->setCursorFromRow(row);
3646 LYXERR(Debug::LATEX,
3647 "setCursorFromRow: invalid position for row " << row);
3648 frontend::Alert::error(_("Inverse Search Failed"),
3649 _("Invalid position requested by inverse search.\n"
3650 "You may need to update the viewed document."));
3656 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3658 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3659 menu->exec(QCursor::pos());
3664 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3665 Buffer const * orig, Buffer * clone, string const & format)
3667 Buffer::ExportStatus const status = func(format);
3669 // the cloning operation will have produced a clone of the entire set of
3670 // documents, starting from the master. so we must delete those.
3671 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3673 busyBuffers.remove(orig);
3678 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3679 Buffer const * orig, Buffer * clone, string const & format)
3681 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3683 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3687 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3688 Buffer const * orig, Buffer * clone, string const & format)
3690 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3692 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3696 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3697 Buffer const * orig, Buffer * clone, string const & format)
3699 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3701 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3705 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3706 string const & argument,
3707 Buffer const * used_buffer,
3708 docstring const & msg,
3709 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3710 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3711 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3717 string format = argument;
3719 format = used_buffer->params().getDefaultOutputFormat();
3720 processing_format = format;
3722 progress_->clearMessages();
3725 #if EXPORT_in_THREAD
3727 GuiViewPrivate::busyBuffers.insert(used_buffer);
3728 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3729 if (!cloned_buffer) {
3730 Alert::error(_("Export Error"),
3731 _("Error cloning the Buffer."));
3734 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3739 setPreviewFuture(f);
3740 last_export_format = used_buffer->params().bufferFormat();
3743 // We are asynchronous, so we don't know here anything about the success
3746 Buffer::ExportStatus status;
3748 status = (used_buffer->*syncFunc)(format, false);
3749 } else if (previewFunc) {
3750 status = (used_buffer->*previewFunc)(format);
3753 handleExportStatus(gv_, status, format);
3755 return (status == Buffer::ExportSuccess
3756 || status == Buffer::PreviewSuccess);
3760 Buffer::ExportStatus status;
3762 status = (used_buffer->*syncFunc)(format, true);
3763 } else if (previewFunc) {
3764 status = (used_buffer->*previewFunc)(format);
3767 handleExportStatus(gv_, status, format);
3769 return (status == Buffer::ExportSuccess
3770 || status == Buffer::PreviewSuccess);
3774 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3776 BufferView * bv = currentBufferView();
3777 LASSERT(bv, return);
3779 // Let the current BufferView dispatch its own actions.
3780 bv->dispatch(cmd, dr);
3781 if (dr.dispatched()) {
3782 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3783 updateDialog("document", "");
3787 // Try with the document BufferView dispatch if any.
3788 BufferView * doc_bv = documentBufferView();
3789 if (doc_bv && doc_bv != bv) {
3790 doc_bv->dispatch(cmd, dr);
3791 if (dr.dispatched()) {
3792 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3793 updateDialog("document", "");
3798 // Then let the current Cursor dispatch its own actions.
3799 bv->cursor().dispatch(cmd);
3801 // update completion. We do it here and not in
3802 // processKeySym to avoid another redraw just for a
3803 // changed inline completion
3804 if (cmd.origin() == FuncRequest::KEYBOARD) {
3805 if (cmd.action() == LFUN_SELF_INSERT
3806 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3807 updateCompletion(bv->cursor(), true, true);
3808 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3809 updateCompletion(bv->cursor(), false, true);
3811 updateCompletion(bv->cursor(), false, false);
3814 dr = bv->cursor().result();
3818 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3820 BufferView * bv = currentBufferView();
3821 // By default we won't need any update.
3822 dr.screenUpdate(Update::None);
3823 // assume cmd will be dispatched
3824 dr.dispatched(true);
3826 Buffer * doc_buffer = documentBufferView()
3827 ? &(documentBufferView()->buffer()) : 0;
3829 if (cmd.origin() == FuncRequest::TOC) {
3830 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3831 // FIXME: do we need to pass a DispatchResult object here?
3832 toc->doDispatch(bv->cursor(), cmd);
3836 string const argument = to_utf8(cmd.argument());
3838 switch(cmd.action()) {
3839 case LFUN_BUFFER_CHILD_OPEN:
3840 openChildDocument(to_utf8(cmd.argument()));
3843 case LFUN_BUFFER_IMPORT:
3844 importDocument(to_utf8(cmd.argument()));
3847 case LFUN_MASTER_BUFFER_EXPORT:
3849 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3851 case LFUN_BUFFER_EXPORT: {
3854 // GCC only sees strfwd.h when building merged
3855 if (::lyx::operator==(cmd.argument(), "custom")) {
3856 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3857 // so the following test should not be needed.
3858 // In principle, we could try to switch to such a view...
3859 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3860 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3864 string const dest = cmd.getArg(1);
3865 FileName target_dir;
3866 if (!dest.empty() && FileName::isAbsolute(dest))
3867 target_dir = FileName(support::onlyPath(dest));
3869 target_dir = doc_buffer->fileName().onlyPath();
3871 string const format = (argument.empty() || argument == "default") ?
3872 doc_buffer->params().getDefaultOutputFormat() : argument;
3874 if ((dest.empty() && doc_buffer->isUnnamed())
3875 || !target_dir.isDirWritable()) {
3876 exportBufferAs(*doc_buffer, from_utf8(format));
3879 /* TODO/Review: Is it a problem to also export the children?
3880 See the update_unincluded flag */
3881 d.asyncBufferProcessing(format,
3884 &GuiViewPrivate::exportAndDestroy,
3886 0, cmd.allowAsync());
3887 // TODO Inform user about success
3891 case LFUN_BUFFER_EXPORT_AS: {
3892 LASSERT(doc_buffer, break);
3893 docstring f = cmd.argument();
3895 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3896 exportBufferAs(*doc_buffer, f);
3900 case LFUN_BUFFER_UPDATE: {
3901 d.asyncBufferProcessing(argument,
3904 &GuiViewPrivate::compileAndDestroy,
3906 0, cmd.allowAsync());
3909 case LFUN_BUFFER_VIEW: {
3910 d.asyncBufferProcessing(argument,
3912 _("Previewing ..."),
3913 &GuiViewPrivate::previewAndDestroy,
3915 &Buffer::preview, cmd.allowAsync());
3918 case LFUN_MASTER_BUFFER_UPDATE: {
3919 d.asyncBufferProcessing(argument,
3920 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3922 &GuiViewPrivate::compileAndDestroy,
3924 0, cmd.allowAsync());
3927 case LFUN_MASTER_BUFFER_VIEW: {
3928 d.asyncBufferProcessing(argument,
3929 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3931 &GuiViewPrivate::previewAndDestroy,
3932 0, &Buffer::preview, cmd.allowAsync());
3935 case LFUN_EXPORT_CANCEL: {
3936 Systemcall::killscript();
3939 case LFUN_BUFFER_SWITCH: {
3940 string const file_name = to_utf8(cmd.argument());
3941 if (!FileName::isAbsolute(file_name)) {
3943 dr.setMessage(_("Absolute filename expected."));
3947 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3950 dr.setMessage(_("Document not loaded"));
3954 // Do we open or switch to the buffer in this view ?
3955 if (workArea(*buffer)
3956 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3961 // Look for the buffer in other views
3962 QList<int> const ids = guiApp->viewIds();
3964 for (; i != ids.size(); ++i) {
3965 GuiView & gv = guiApp->view(ids[i]);
3966 if (gv.workArea(*buffer)) {
3968 gv.activateWindow();
3970 gv.setBuffer(buffer);
3975 // If necessary, open a new window as a last resort
3976 if (i == ids.size()) {
3977 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3983 case LFUN_BUFFER_NEXT:
3984 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3987 case LFUN_BUFFER_MOVE_NEXT:
3988 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3991 case LFUN_BUFFER_PREVIOUS:
3992 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3995 case LFUN_BUFFER_MOVE_PREVIOUS:
3996 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3999 case LFUN_BUFFER_CHKTEX:
4000 LASSERT(doc_buffer, break);
4001 doc_buffer->runChktex();
4004 case LFUN_COMMAND_EXECUTE: {
4005 command_execute_ = true;
4006 minibuffer_focus_ = true;
4009 case LFUN_DROP_LAYOUTS_CHOICE:
4010 d.layout_->showPopup();
4013 case LFUN_MENU_OPEN:
4014 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4015 menu->exec(QCursor::pos());
4018 case LFUN_FILE_INSERT:
4019 insertLyXFile(cmd.argument());
4022 case LFUN_FILE_INSERT_PLAINTEXT:
4023 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4024 string const fname = to_utf8(cmd.argument());
4025 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4026 dr.setMessage(_("Absolute filename expected."));
4030 FileName filename(fname);
4031 if (fname.empty()) {
4032 FileDialog dlg(qt_("Select file to insert"));
4034 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4035 QStringList(qt_("All Files (*)")));
4037 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4038 dr.setMessage(_("Canceled."));
4042 filename.set(fromqstr(result.second));
4046 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4047 bv->dispatch(new_cmd, dr);
4052 case LFUN_BUFFER_RELOAD: {
4053 LASSERT(doc_buffer, break);
4056 bool drop = (cmd.argument() == "dump");
4059 if (!drop && !doc_buffer->isClean()) {
4060 docstring const file =
4061 makeDisplayPath(doc_buffer->absFileName(), 20);
4062 if (doc_buffer->notifiesExternalModification()) {
4063 docstring text = _("The current version will be lost. "
4064 "Are you sure you want to load the version on disk "
4065 "of the document %1$s?");
4066 ret = Alert::prompt(_("Reload saved document?"),
4067 bformat(text, file), 1, 1,
4068 _("&Reload"), _("&Cancel"));
4070 docstring text = _("Any changes will be lost. "
4071 "Are you sure you want to revert to the saved version "
4072 "of the document %1$s?");
4073 ret = Alert::prompt(_("Revert to saved document?"),
4074 bformat(text, file), 1, 1,
4075 _("&Revert"), _("&Cancel"));
4080 doc_buffer->markClean();
4081 reloadBuffer(*doc_buffer);
4082 dr.forceBufferUpdate();
4087 case LFUN_BUFFER_WRITE:
4088 LASSERT(doc_buffer, break);
4089 saveBuffer(*doc_buffer);
4092 case LFUN_BUFFER_WRITE_AS:
4093 LASSERT(doc_buffer, break);
4094 renameBuffer(*doc_buffer, cmd.argument());
4097 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4098 LASSERT(doc_buffer, break);
4099 renameBuffer(*doc_buffer, cmd.argument(),
4100 LV_WRITE_AS_TEMPLATE);
4103 case LFUN_BUFFER_WRITE_ALL: {
4104 Buffer * first = theBufferList().first();
4107 message(_("Saving all documents..."));
4108 // We cannot use a for loop as the buffer list cycles.
4111 if (!b->isClean()) {
4113 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4115 b = theBufferList().next(b);
4116 } while (b != first);
4117 dr.setMessage(_("All documents saved."));
4121 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4122 LASSERT(doc_buffer, break);
4123 doc_buffer->clearExternalModification();
4126 case LFUN_BUFFER_CLOSE:
4130 case LFUN_BUFFER_CLOSE_ALL:
4134 case LFUN_DEVEL_MODE_TOGGLE:
4135 devel_mode_ = !devel_mode_;
4137 dr.setMessage(_("Developer mode is now enabled."));
4139 dr.setMessage(_("Developer mode is now disabled."));
4142 case LFUN_TOOLBAR_TOGGLE: {
4143 string const name = cmd.getArg(0);
4144 if (GuiToolbar * t = toolbar(name))
4149 case LFUN_TOOLBAR_MOVABLE: {
4150 string const name = cmd.getArg(0);
4152 // toggle (all) toolbars movablility
4153 toolbarsMovable_ = !toolbarsMovable_;
4154 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4155 GuiToolbar * tb = toolbar(ti.name);
4156 if (tb && tb->isMovable() != toolbarsMovable_)
4157 // toggle toolbar movablity if it does not fit lock
4158 // (all) toolbars positions state silent = true, since
4159 // status bar notifications are slow
4162 if (toolbarsMovable_)
4163 dr.setMessage(_("Toolbars unlocked."));
4165 dr.setMessage(_("Toolbars locked."));
4166 } else if (GuiToolbar * t = toolbar(name)) {
4167 // toggle current toolbar movablity
4169 // update lock (all) toolbars positions
4170 updateLockToolbars();
4175 case LFUN_ICON_SIZE: {
4176 QSize size = d.iconSize(cmd.argument());
4178 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4179 size.width(), size.height()));
4183 case LFUN_DIALOG_UPDATE: {
4184 string const name = to_utf8(cmd.argument());
4185 if (name == "prefs" || name == "document")
4186 updateDialog(name, string());
4187 else if (name == "paragraph")
4188 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4189 else if (currentBufferView()) {
4190 Inset * inset = currentBufferView()->editedInset(name);
4191 // Can only update a dialog connected to an existing inset
4193 // FIXME: get rid of this indirection; GuiView ask the inset
4194 // if he is kind enough to update itself...
4195 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4196 //FIXME: pass DispatchResult here?
4197 inset->dispatch(currentBufferView()->cursor(), fr);
4203 case LFUN_DIALOG_TOGGLE: {
4204 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4205 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4206 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4210 case LFUN_DIALOG_DISCONNECT_INSET:
4211 disconnectDialog(to_utf8(cmd.argument()));
4214 case LFUN_DIALOG_HIDE: {
4215 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4219 case LFUN_DIALOG_SHOW: {
4220 string const name = cmd.getArg(0);
4221 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4223 if (name == "latexlog") {
4224 // gettatus checks that
4225 LATTEST(doc_buffer);
4226 Buffer::LogType type;
4227 string const logfile = doc_buffer->logName(&type);
4229 case Buffer::latexlog:
4232 case Buffer::buildlog:
4233 sdata = "literate ";
4236 sdata += Lexer::quoteString(logfile);
4237 showDialog("log", sdata);
4238 } else if (name == "vclog") {
4239 // getStatus checks that
4240 LATTEST(doc_buffer);
4241 string const sdata2 = "vc " +
4242 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4243 showDialog("log", sdata2);
4244 } else if (name == "symbols") {
4245 sdata = bv->cursor().getEncoding()->name();
4247 showDialog("symbols", sdata);
4249 } else if (name == "prefs" && isFullScreen()) {
4250 lfunUiToggle("fullscreen");
4251 showDialog("prefs", sdata);
4253 showDialog(name, sdata);
4258 dr.setMessage(cmd.argument());
4261 case LFUN_UI_TOGGLE: {
4262 string arg = cmd.getArg(0);
4263 if (!lfunUiToggle(arg)) {
4264 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4265 dr.setMessage(bformat(msg, from_utf8(arg)));
4267 // Make sure the keyboard focus stays in the work area.
4272 case LFUN_VIEW_SPLIT: {
4273 LASSERT(doc_buffer, break);
4274 string const orientation = cmd.getArg(0);
4275 d.splitter_->setOrientation(orientation == "vertical"
4276 ? Qt::Vertical : Qt::Horizontal);
4277 TabWorkArea * twa = addTabWorkArea();
4278 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4279 setCurrentWorkArea(wa);
4282 case LFUN_TAB_GROUP_CLOSE:
4283 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4284 closeTabWorkArea(twa);
4285 d.current_work_area_ = 0;
4286 twa = d.currentTabWorkArea();
4287 // Switch to the next GuiWorkArea in the found TabWorkArea.
4289 // Make sure the work area is up to date.
4290 setCurrentWorkArea(twa->currentWorkArea());
4292 setCurrentWorkArea(0);
4297 case LFUN_VIEW_CLOSE:
4298 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4299 closeWorkArea(twa->currentWorkArea());
4300 d.current_work_area_ = 0;
4301 twa = d.currentTabWorkArea();
4302 // Switch to the next GuiWorkArea in the found TabWorkArea.
4304 // Make sure the work area is up to date.
4305 setCurrentWorkArea(twa->currentWorkArea());
4307 setCurrentWorkArea(0);
4312 case LFUN_COMPLETION_INLINE:
4313 if (d.current_work_area_)
4314 d.current_work_area_->completer().showInline();
4317 case LFUN_COMPLETION_POPUP:
4318 if (d.current_work_area_)
4319 d.current_work_area_->completer().showPopup();
4324 if (d.current_work_area_)
4325 d.current_work_area_->completer().tab();
4328 case LFUN_COMPLETION_CANCEL:
4329 if (d.current_work_area_) {
4330 if (d.current_work_area_->completer().popupVisible())
4331 d.current_work_area_->completer().hidePopup();
4333 d.current_work_area_->completer().hideInline();
4337 case LFUN_COMPLETION_ACCEPT:
4338 if (d.current_work_area_)
4339 d.current_work_area_->completer().activate();
4342 case LFUN_BUFFER_ZOOM_IN:
4343 case LFUN_BUFFER_ZOOM_OUT:
4344 case LFUN_BUFFER_ZOOM: {
4345 if (cmd.argument().empty()) {
4346 if (cmd.action() == LFUN_BUFFER_ZOOM)
4348 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4353 if (cmd.action() == LFUN_BUFFER_ZOOM)
4354 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4355 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4356 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4358 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4361 // Actual zoom value: default zoom + fractional extra value
4362 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4363 if (zoom < static_cast<int>(zoom_min_))
4366 lyxrc.currentZoom = zoom;
4368 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4369 lyxrc.currentZoom, lyxrc.defaultZoom));
4371 guiApp->fontLoader().update();
4372 dr.screenUpdate(Update::Force | Update::FitCursor);
4376 case LFUN_VC_REGISTER:
4377 case LFUN_VC_RENAME:
4379 case LFUN_VC_CHECK_IN:
4380 case LFUN_VC_CHECK_OUT:
4381 case LFUN_VC_REPO_UPDATE:
4382 case LFUN_VC_LOCKING_TOGGLE:
4383 case LFUN_VC_REVERT:
4384 case LFUN_VC_UNDO_LAST:
4385 case LFUN_VC_COMMAND:
4386 case LFUN_VC_COMPARE:
4387 dispatchVC(cmd, dr);
4390 case LFUN_SERVER_GOTO_FILE_ROW:
4391 if(goToFileRow(to_utf8(cmd.argument())))
4392 dr.screenUpdate(Update::Force | Update::FitCursor);
4395 case LFUN_LYX_ACTIVATE:
4399 case LFUN_FORWARD_SEARCH: {
4400 // it seems safe to assume we have a document buffer, since
4401 // getStatus wants one.
4402 LATTEST(doc_buffer);
4403 Buffer const * doc_master = doc_buffer->masterBuffer();
4404 FileName const path(doc_master->temppath());
4405 string const texname = doc_master->isChild(doc_buffer)
4406 ? DocFileName(changeExtension(
4407 doc_buffer->absFileName(),
4408 "tex")).mangledFileName()
4409 : doc_buffer->latexName();
4410 string const fulltexname =
4411 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4412 string const mastername =
4413 removeExtension(doc_master->latexName());
4414 FileName const dviname(addName(path.absFileName(),
4415 addExtension(mastername, "dvi")));
4416 FileName const pdfname(addName(path.absFileName(),
4417 addExtension(mastername, "pdf")));
4418 bool const have_dvi = dviname.exists();
4419 bool const have_pdf = pdfname.exists();
4420 if (!have_dvi && !have_pdf) {
4421 dr.setMessage(_("Please, preview the document first."));
4424 string outname = dviname.onlyFileName();
4425 string command = lyxrc.forward_search_dvi;
4426 if (!have_dvi || (have_pdf &&
4427 pdfname.lastModified() > dviname.lastModified())) {
4428 outname = pdfname.onlyFileName();
4429 command = lyxrc.forward_search_pdf;
4432 DocIterator cur = bv->cursor();
4433 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4434 LYXERR(Debug::ACTION, "Forward search: row:" << row
4436 if (row == -1 || command.empty()) {
4437 dr.setMessage(_("Couldn't proceed."));
4440 string texrow = convert<string>(row);
4442 command = subst(command, "$$n", texrow);
4443 command = subst(command, "$$f", fulltexname);
4444 command = subst(command, "$$t", texname);
4445 command = subst(command, "$$o", outname);
4447 PathChanger p(path);
4449 one.startscript(Systemcall::DontWait, command);
4453 case LFUN_SPELLING_CONTINUOUSLY:
4454 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4455 dr.screenUpdate(Update::Force);
4459 // The LFUN must be for one of BufferView, Buffer or Cursor;
4461 dispatchToBufferView(cmd, dr);
4465 // Part of automatic menu appearance feature.
4466 if (isFullScreen()) {
4467 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4471 // Need to update bv because many LFUNs here might have destroyed it
4472 bv = currentBufferView();
4474 // Clear non-empty selections
4475 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4477 Cursor & cur = bv->cursor();
4478 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4479 cur.clearSelection();
4485 bool GuiView::lfunUiToggle(string const & ui_component)
4487 if (ui_component == "scrollbar") {
4488 // hide() is of no help
4489 if (d.current_work_area_->verticalScrollBarPolicy() ==
4490 Qt::ScrollBarAlwaysOff)
4492 d.current_work_area_->setVerticalScrollBarPolicy(
4493 Qt::ScrollBarAsNeeded);
4495 d.current_work_area_->setVerticalScrollBarPolicy(
4496 Qt::ScrollBarAlwaysOff);
4497 } else if (ui_component == "statusbar") {
4498 statusBar()->setVisible(!statusBar()->isVisible());
4499 } else if (ui_component == "menubar") {
4500 menuBar()->setVisible(!menuBar()->isVisible());
4502 if (ui_component == "frame") {
4504 getContentsMargins(&l, &t, &r, &b);
4505 //are the frames in default state?
4506 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4508 setContentsMargins(-2, -2, -2, -2);
4510 setContentsMargins(0, 0, 0, 0);
4513 if (ui_component == "fullscreen") {
4521 void GuiView::toggleFullScreen()
4523 if (isFullScreen()) {
4524 for (int i = 0; i != d.splitter_->count(); ++i)
4525 d.tabWorkArea(i)->setFullScreen(false);
4526 setContentsMargins(0, 0, 0, 0);
4527 setWindowState(windowState() ^ Qt::WindowFullScreen);
4530 statusBar()->show();
4533 hideDialogs("prefs", 0);
4534 for (int i = 0; i != d.splitter_->count(); ++i)
4535 d.tabWorkArea(i)->setFullScreen(true);
4536 setContentsMargins(-2, -2, -2, -2);
4538 setWindowState(windowState() ^ Qt::WindowFullScreen);
4539 if (lyxrc.full_screen_statusbar)
4540 statusBar()->hide();
4541 if (lyxrc.full_screen_menubar)
4543 if (lyxrc.full_screen_toolbars) {
4544 ToolbarMap::iterator end = d.toolbars_.end();
4545 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4550 // give dialogs like the TOC a chance to adapt
4555 Buffer const * GuiView::updateInset(Inset const * inset)
4560 Buffer const * inset_buffer = &(inset->buffer());
4562 for (int i = 0; i != d.splitter_->count(); ++i) {
4563 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4566 Buffer const * buffer = &(wa->bufferView().buffer());
4567 if (inset_buffer == buffer)
4568 wa->scheduleRedraw(true);
4570 return inset_buffer;
4574 void GuiView::restartCaret()
4576 /* When we move around, or type, it's nice to be able to see
4577 * the caret immediately after the keypress.
4579 if (d.current_work_area_)
4580 d.current_work_area_->startBlinkingCaret();
4582 // Take this occasion to update the other GUI elements.
4588 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4590 if (d.current_work_area_)
4591 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4596 // This list should be kept in sync with the list of insets in
4597 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4598 // dialog should have the same name as the inset.
4599 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4600 // docs in LyXAction.cpp.
4602 char const * const dialognames[] = {
4604 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4605 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4606 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4607 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4608 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4609 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4610 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4611 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4613 char const * const * const end_dialognames =
4614 dialognames + (sizeof(dialognames) / sizeof(char *));
4618 cmpCStr(char const * name) : name_(name) {}
4619 bool operator()(char const * other) {
4620 return strcmp(other, name_) == 0;
4627 bool isValidName(string const & name)
4629 return find_if(dialognames, end_dialognames,
4630 cmpCStr(name.c_str())) != end_dialognames;
4636 void GuiView::resetDialogs()
4638 // Make sure that no LFUN uses any GuiView.
4639 guiApp->setCurrentView(0);
4643 constructToolbars();
4644 guiApp->menus().fillMenuBar(menuBar(), this, false);
4645 d.layout_->updateContents(true);
4646 // Now update controls with current buffer.
4647 guiApp->setCurrentView(this);
4653 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4655 if (!isValidName(name))
4658 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4660 if (it != d.dialogs_.end()) {
4662 it->second->hideView();
4663 return it->second.get();
4666 Dialog * dialog = build(name);
4667 d.dialogs_[name].reset(dialog);
4668 if (lyxrc.allow_geometry_session)
4669 dialog->restoreSession();
4676 void GuiView::showDialog(string const & name, string const & sdata,
4679 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4683 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4689 const string name = fromqstr(qname);
4690 const string sdata = fromqstr(qdata);
4694 Dialog * dialog = findOrBuild(name, false);
4696 bool const visible = dialog->isVisibleView();
4697 dialog->showData(sdata);
4698 if (currentBufferView())
4699 currentBufferView()->editInset(name, inset);
4700 // We only set the focus to the new dialog if it was not yet
4701 // visible in order not to change the existing previous behaviour
4703 // activateWindow is needed for floating dockviews
4704 dialog->asQWidget()->raise();
4705 dialog->asQWidget()->activateWindow();
4706 dialog->asQWidget()->setFocus();
4710 catch (ExceptionMessage const & ex) {
4718 bool GuiView::isDialogVisible(string const & name) const
4720 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4721 if (it == d.dialogs_.end())
4723 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4727 void GuiView::hideDialog(string const & name, Inset * inset)
4729 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4730 if (it == d.dialogs_.end())
4734 if (!currentBufferView())
4736 if (inset != currentBufferView()->editedInset(name))
4740 Dialog * const dialog = it->second.get();
4741 if (dialog->isVisibleView())
4743 if (currentBufferView())
4744 currentBufferView()->editInset(name, 0);
4748 void GuiView::disconnectDialog(string const & name)
4750 if (!isValidName(name))
4752 if (currentBufferView())
4753 currentBufferView()->editInset(name, 0);
4757 void GuiView::hideAll() const
4759 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4760 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4762 for(; it != end; ++it)
4763 it->second->hideView();
4767 void GuiView::updateDialogs()
4769 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4770 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4772 for(; it != end; ++it) {
4773 Dialog * dialog = it->second.get();
4775 if (dialog->needBufferOpen() && !documentBufferView())
4776 hideDialog(fromqstr(dialog->name()), 0);
4777 else if (dialog->isVisibleView())
4778 dialog->checkStatus();
4785 Dialog * createDialog(GuiView & lv, string const & name);
4787 // will be replaced by a proper factory...
4788 Dialog * createGuiAbout(GuiView & lv);
4789 Dialog * createGuiBibtex(GuiView & lv);
4790 Dialog * createGuiChanges(GuiView & lv);
4791 Dialog * createGuiCharacter(GuiView & lv);
4792 Dialog * createGuiCitation(GuiView & lv);
4793 Dialog * createGuiCompare(GuiView & lv);
4794 Dialog * createGuiCompareHistory(GuiView & lv);
4795 Dialog * createGuiDelimiter(GuiView & lv);
4796 Dialog * createGuiDocument(GuiView & lv);
4797 Dialog * createGuiErrorList(GuiView & lv);
4798 Dialog * createGuiExternal(GuiView & lv);
4799 Dialog * createGuiGraphics(GuiView & lv);
4800 Dialog * createGuiInclude(GuiView & lv);
4801 Dialog * createGuiIndex(GuiView & lv);
4802 Dialog * createGuiListings(GuiView & lv);
4803 Dialog * createGuiLog(GuiView & lv);
4804 Dialog * createGuiLyXFiles(GuiView & lv);
4805 Dialog * createGuiMathMatrix(GuiView & lv);
4806 Dialog * createGuiNote(GuiView & lv);
4807 Dialog * createGuiParagraph(GuiView & lv);
4808 Dialog * createGuiPhantom(GuiView & lv);
4809 Dialog * createGuiPreferences(GuiView & lv);
4810 Dialog * createGuiPrint(GuiView & lv);
4811 Dialog * createGuiPrintindex(GuiView & lv);
4812 Dialog * createGuiRef(GuiView & lv);
4813 Dialog * createGuiSearch(GuiView & lv);
4814 Dialog * createGuiSearchAdv(GuiView & lv);
4815 Dialog * createGuiSendTo(GuiView & lv);
4816 Dialog * createGuiShowFile(GuiView & lv);
4817 Dialog * createGuiSpellchecker(GuiView & lv);
4818 Dialog * createGuiSymbols(GuiView & lv);
4819 Dialog * createGuiTabularCreate(GuiView & lv);
4820 Dialog * createGuiTexInfo(GuiView & lv);
4821 Dialog * createGuiToc(GuiView & lv);
4822 Dialog * createGuiThesaurus(GuiView & lv);
4823 Dialog * createGuiViewSource(GuiView & lv);
4824 Dialog * createGuiWrap(GuiView & lv);
4825 Dialog * createGuiProgressView(GuiView & lv);
4829 Dialog * GuiView::build(string const & name)
4831 LASSERT(isValidName(name), return 0);
4833 Dialog * dialog = createDialog(*this, name);
4837 if (name == "aboutlyx")
4838 return createGuiAbout(*this);
4839 if (name == "bibtex")
4840 return createGuiBibtex(*this);
4841 if (name == "changes")
4842 return createGuiChanges(*this);
4843 if (name == "character")
4844 return createGuiCharacter(*this);
4845 if (name == "citation")
4846 return createGuiCitation(*this);
4847 if (name == "compare")
4848 return createGuiCompare(*this);
4849 if (name == "comparehistory")
4850 return createGuiCompareHistory(*this);
4851 if (name == "document")
4852 return createGuiDocument(*this);
4853 if (name == "errorlist")
4854 return createGuiErrorList(*this);
4855 if (name == "external")
4856 return createGuiExternal(*this);
4858 return createGuiShowFile(*this);
4859 if (name == "findreplace")
4860 return createGuiSearch(*this);
4861 if (name == "findreplaceadv")
4862 return createGuiSearchAdv(*this);
4863 if (name == "graphics")
4864 return createGuiGraphics(*this);
4865 if (name == "include")
4866 return createGuiInclude(*this);
4867 if (name == "index")
4868 return createGuiIndex(*this);
4869 if (name == "index_print")
4870 return createGuiPrintindex(*this);
4871 if (name == "listings")
4872 return createGuiListings(*this);
4874 return createGuiLog(*this);
4875 if (name == "lyxfiles")
4876 return createGuiLyXFiles(*this);
4877 if (name == "mathdelimiter")
4878 return createGuiDelimiter(*this);
4879 if (name == "mathmatrix")
4880 return createGuiMathMatrix(*this);
4882 return createGuiNote(*this);
4883 if (name == "paragraph")
4884 return createGuiParagraph(*this);
4885 if (name == "phantom")
4886 return createGuiPhantom(*this);
4887 if (name == "prefs")
4888 return createGuiPreferences(*this);
4890 return createGuiRef(*this);
4891 if (name == "sendto")
4892 return createGuiSendTo(*this);
4893 if (name == "spellchecker")
4894 return createGuiSpellchecker(*this);
4895 if (name == "symbols")
4896 return createGuiSymbols(*this);
4897 if (name == "tabularcreate")
4898 return createGuiTabularCreate(*this);
4899 if (name == "texinfo")
4900 return createGuiTexInfo(*this);
4901 if (name == "thesaurus")
4902 return createGuiThesaurus(*this);
4904 return createGuiToc(*this);
4905 if (name == "view-source")
4906 return createGuiViewSource(*this);
4908 return createGuiWrap(*this);
4909 if (name == "progress")
4910 return createGuiProgressView(*this);
4916 SEMenu::SEMenu(QWidget * parent)
4918 QAction * action = addAction(qt_("Disable Shell Escape"));
4919 connect(action, SIGNAL(triggered()),
4920 parent, SLOT(disableShellEscape()));
4923 } // namespace frontend
4926 #include "moc_GuiView.cpp"