3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
8 * \author Abdelrazak Younes
11 * Full author contact details are available in file CREDITS.
18 #include "DispatchResult.h"
19 #include "FileDialog.h"
20 #include "FontLoader.h"
21 #include "GuiApplication.h"
22 #include "GuiCommandBuffer.h"
23 #include "GuiCompleter.h"
24 #include "GuiKeySymbol.h"
26 #include "GuiToolbar.h"
27 #include "GuiWorkArea.h"
28 #include "GuiProgress.h"
29 #include "LayoutBox.h"
33 #include "qt_helpers.h"
34 #include "support/filetools.h"
36 #include "frontends/alert.h"
37 #include "frontends/KeySymbol.h"
39 #include "buffer_funcs.h"
41 #include "BufferList.h"
42 #include "BufferParams.h"
43 #include "BufferView.h"
45 #include "Converter.h"
47 #include "CutAndPaste.h"
49 #include "ErrorList.h"
51 #include "FuncStatus.h"
52 #include "FuncRequest.h"
56 #include "LyXAction.h"
60 #include "Paragraph.h"
61 #include "SpellChecker.h"
64 #include "TextClass.h"
69 #include "support/convert.h"
70 #include "support/debug.h"
71 #include "support/ExceptionMessage.h"
72 #include "support/FileName.h"
73 #include "support/filetools.h"
74 #include "support/gettext.h"
75 #include "support/filetools.h"
76 #include "support/ForkedCalls.h"
77 #include "support/lassert.h"
78 #include "support/lstrings.h"
79 #include "support/os.h"
80 #include "support/Package.h"
81 #include "support/PathChanger.h"
82 #include "support/Systemcall.h"
83 #include "support/Timeout.h"
84 #include "support/ProgressInterface.h"
87 #include <QApplication>
88 #include <QCloseEvent>
90 #include <QDesktopWidget>
91 #include <QDragEnterEvent>
94 #include <QFutureWatcher>
104 #include <QPushButton>
105 #include <QScrollBar>
107 #include <QShowEvent>
109 #include <QStackedWidget>
110 #include <QStatusBar>
111 #include <QSvgRenderer>
112 #include <QtConcurrentRun>
120 // sync with GuiAlert.cpp
121 #define EXPORT_in_THREAD 1
124 #include "support/bind.h"
128 #ifdef HAVE_SYS_TIME_H
129 # include <sys/time.h>
137 using namespace lyx::support;
141 using support::addExtension;
142 using support::changeExtension;
143 using support::removeExtension;
149 class BackgroundWidget : public QWidget
152 BackgroundWidget(int width, int height)
153 : width_(width), height_(height)
155 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
156 if (!lyxrc.show_banner)
158 /// The text to be written on top of the pixmap
159 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
160 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
161 /// The text to be written on top of the pixmap
162 QString const text = lyx_version ?
163 qt_("version ") + lyx_version : qt_("unknown version");
164 #if QT_VERSION >= 0x050000
165 QString imagedir = "images/";
166 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
167 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
168 if (svgRenderer.isValid()) {
169 splash_ = QPixmap(splashSize());
170 QPainter painter(&splash_);
171 svgRenderer.render(&painter);
172 splash_.setDevicePixelRatio(pixelRatio());
174 splash_ = getPixmap("images/", "banner", "png");
177 splash_ = getPixmap("images/", "banner", "svgz,png");
180 QPainter pain(&splash_);
181 pain.setPen(QColor(0, 0, 0));
182 qreal const fsize = fontSize();
185 qreal locscale = htextsize.toFloat(&ok);
188 QPointF const position = textPosition(false);
189 QPointF const hposition = textPosition(true);
190 QRectF const hrect(hposition, splashSize());
192 "widget pixel ratio: " << pixelRatio() <<
193 " splash pixel ratio: " << splashPixelRatio() <<
194 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
196 // The font used to display the version info
197 font.setStyleHint(QFont::SansSerif);
198 font.setWeight(QFont::Bold);
199 font.setPointSizeF(fsize);
201 pain.drawText(position, text);
202 // The font used to display the version info
203 font.setStyleHint(QFont::SansSerif);
204 font.setWeight(QFont::Normal);
205 font.setPointSizeF(hfsize);
206 // Check how long the logo gets with the current font
207 // and adapt if the font is running wider than what
209 QFontMetrics fm(font);
210 // Split the title into lines to measure the longest line
211 // in the current l7n.
212 QStringList titlesegs = htext.split('\n');
214 int hline = fm.height();
215 QStringList::const_iterator sit;
216 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
217 if (fm.width(*sit) > wline)
218 wline = fm.width(*sit);
220 // The longest line in the reference font (for English)
221 // is 180. Calculate scale factor from that.
222 double const wscale = wline > 0 ? (180.0 / wline) : 1;
223 // Now do the same for the height (necessary for condensed fonts)
224 double const hscale = (34.0 / hline);
225 // take the lower of the two scale factors.
226 double const scale = min(wscale, hscale);
227 // Now rescale. Also consider l7n's offset factor.
228 font.setPointSizeF(hfsize * scale * locscale);
231 pain.drawText(hrect, Qt::AlignLeft, htext);
232 setFocusPolicy(Qt::StrongFocus);
235 void paintEvent(QPaintEvent *)
237 int const w = width_;
238 int const h = height_;
239 int const x = (width() - w) / 2;
240 int const y = (height() - h) / 2;
242 "widget pixel ratio: " << pixelRatio() <<
243 " splash pixel ratio: " << splashPixelRatio() <<
244 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
246 pain.drawPixmap(x, y, w, h, splash_);
249 void keyPressEvent(QKeyEvent * ev)
252 setKeySymbol(&sym, ev);
254 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
266 /// Current ratio between physical pixels and device-independent pixels
267 double pixelRatio() const {
268 #if QT_VERSION >= 0x050000
269 return qt_scale_factor * devicePixelRatio();
275 qreal fontSize() const {
276 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
279 QPointF textPosition(bool const heading) const {
280 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
281 : QPointF(width_/2 - 18, height_/2 + 45);
284 QSize splashSize() const {
286 static_cast<unsigned int>(width_ * pixelRatio()),
287 static_cast<unsigned int>(height_ * pixelRatio()));
290 /// Ratio between physical pixels and device-independent pixels of splash image
291 double splashPixelRatio() const {
292 #if QT_VERSION >= 0x050000
293 return splash_.devicePixelRatio();
301 /// Toolbar store providing access to individual toolbars by name.
302 typedef map<string, GuiToolbar *> ToolbarMap;
304 typedef shared_ptr<Dialog> DialogPtr;
309 class GuiView::GuiViewPrivate
312 GuiViewPrivate(GuiViewPrivate const &);
313 void operator=(GuiViewPrivate const &);
315 GuiViewPrivate(GuiView * gv)
316 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
317 layout_(0), autosave_timeout_(5000),
320 // hardcode here the platform specific icon size
321 smallIconSize = 16; // scaling problems
322 normalIconSize = 20; // ok, default if iconsize.png is missing
323 bigIconSize = 26; // better for some math icons
324 hugeIconSize = 32; // better for hires displays
327 // if it exists, use width of iconsize.png as normal size
328 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
329 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
331 QImage image(toqstr(fn.absFileName()));
332 if (image.width() < int(smallIconSize))
333 normalIconSize = smallIconSize;
334 else if (image.width() > int(giantIconSize))
335 normalIconSize = giantIconSize;
337 normalIconSize = image.width();
340 splitter_ = new QSplitter;
341 bg_widget_ = new BackgroundWidget(400, 250);
342 stack_widget_ = new QStackedWidget;
343 stack_widget_->addWidget(bg_widget_);
344 stack_widget_->addWidget(splitter_);
347 // TODO cleanup, remove the singleton, handle multiple Windows?
348 progress_ = ProgressInterface::instance();
349 if (!dynamic_cast<GuiProgress*>(progress_)) {
350 progress_ = new GuiProgress; // TODO who deletes it
351 ProgressInterface::setInstance(progress_);
354 dynamic_cast<GuiProgress*>(progress_),
355 SIGNAL(updateStatusBarMessage(QString const&)),
356 gv, SLOT(updateStatusBarMessage(QString const&)));
358 dynamic_cast<GuiProgress*>(progress_),
359 SIGNAL(clearMessageText()),
360 gv, SLOT(clearMessageText()));
367 delete stack_widget_;
372 stack_widget_->setCurrentWidget(bg_widget_);
373 bg_widget_->setUpdatesEnabled(true);
374 bg_widget_->setFocus();
377 int tabWorkAreaCount()
379 return splitter_->count();
382 TabWorkArea * tabWorkArea(int i)
384 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
387 TabWorkArea * currentTabWorkArea()
389 int areas = tabWorkAreaCount();
391 // The first TabWorkArea is always the first one, if any.
392 return tabWorkArea(0);
394 for (int i = 0; i != areas; ++i) {
395 TabWorkArea * twa = tabWorkArea(i);
396 if (current_main_work_area_ == twa->currentWorkArea())
400 // None has the focus so we just take the first one.
401 return tabWorkArea(0);
404 int countWorkAreasOf(Buffer & buf)
406 int areas = tabWorkAreaCount();
408 for (int i = 0; i != areas; ++i) {
409 TabWorkArea * twa = tabWorkArea(i);
410 if (twa->workArea(buf))
416 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
418 if (processing_thread_watcher_.isRunning()) {
419 // we prefer to cancel this preview in order to keep a snappy
423 processing_thread_watcher_.setFuture(f);
426 QSize iconSize(docstring const & icon_size)
429 if (icon_size == "small")
430 size = smallIconSize;
431 else if (icon_size == "normal")
432 size = normalIconSize;
433 else if (icon_size == "big")
435 else if (icon_size == "huge")
437 else if (icon_size == "giant")
438 size = giantIconSize;
440 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
442 if (size < smallIconSize)
443 size = smallIconSize;
445 return QSize(size, size);
448 QSize iconSize(QString const & icon_size)
450 return iconSize(qstring_to_ucs4(icon_size));
453 string & iconSize(QSize const & qsize)
455 LATTEST(qsize.width() == qsize.height());
457 static string icon_size;
459 unsigned int size = qsize.width();
461 if (size < smallIconSize)
462 size = smallIconSize;
464 if (size == smallIconSize)
466 else if (size == normalIconSize)
467 icon_size = "normal";
468 else if (size == bigIconSize)
470 else if (size == hugeIconSize)
472 else if (size == giantIconSize)
475 icon_size = convert<string>(size);
482 GuiWorkArea * current_work_area_;
483 GuiWorkArea * current_main_work_area_;
484 QSplitter * splitter_;
485 QStackedWidget * stack_widget_;
486 BackgroundWidget * bg_widget_;
488 ToolbarMap toolbars_;
489 ProgressInterface* progress_;
490 /// The main layout box.
492 * \warning Don't Delete! The layout box is actually owned by
493 * whichever toolbar contains it. All the GuiView class needs is a
494 * means of accessing it.
496 * FIXME: replace that with a proper model so that we are not limited
497 * to only one dialog.
502 map<string, DialogPtr> dialogs_;
504 unsigned int smallIconSize;
505 unsigned int normalIconSize;
506 unsigned int bigIconSize;
507 unsigned int hugeIconSize;
508 unsigned int giantIconSize;
510 QTimer statusbar_timer_;
511 /// auto-saving of buffers
512 Timeout autosave_timeout_;
513 /// flag against a race condition due to multiclicks, see bug #1119
517 TocModels toc_models_;
520 QFutureWatcher<docstring> autosave_watcher_;
521 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
523 string last_export_format;
524 string processing_format;
526 static QSet<Buffer const *> busyBuffers;
527 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
528 Buffer * buffer, string const & format);
529 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
530 Buffer * buffer, string const & format);
531 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
532 Buffer * buffer, string const & format);
533 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
536 static Buffer::ExportStatus runAndDestroy(const T& func,
537 Buffer const * orig, Buffer * buffer, string const & format);
539 // TODO syncFunc/previewFunc: use bind
540 bool asyncBufferProcessing(string const & argument,
541 Buffer const * used_buffer,
542 docstring const & msg,
543 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
544 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
545 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
548 QVector<GuiWorkArea*> guiWorkAreas();
551 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
554 GuiView::GuiView(int id)
555 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
556 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
559 connect(this, SIGNAL(bufferViewChanged()),
560 this, SLOT(onBufferViewChanged()));
562 // GuiToolbars *must* be initialised before the menu bar.
563 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
566 // set ourself as the current view. This is needed for the menu bar
567 // filling, at least for the static special menu item on Mac. Otherwise
568 // they are greyed out.
569 guiApp->setCurrentView(this);
571 // Fill up the menu bar.
572 guiApp->menus().fillMenuBar(menuBar(), this, true);
574 setCentralWidget(d.stack_widget_);
576 // Start autosave timer
577 if (lyxrc.autosave) {
578 // The connection is closed when this is destroyed.
579 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
580 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
581 d.autosave_timeout_.start();
583 connect(&d.statusbar_timer_, SIGNAL(timeout()),
584 this, SLOT(clearMessage()));
586 // We don't want to keep the window in memory if it is closed.
587 setAttribute(Qt::WA_DeleteOnClose, true);
589 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
590 // QIcon::fromTheme was introduced in Qt 4.6
591 #if (QT_VERSION >= 0x040600)
592 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
593 // since the icon is provided in the application bundle. We use a themed
594 // version when available and use the bundled one as fallback.
595 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
597 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
603 // use tabbed dock area for multiple docks
604 // (such as "source" and "messages")
605 setDockOptions(QMainWindow::ForceTabbedDocks);
608 setAcceptDrops(true);
610 // add busy indicator to statusbar
611 QLabel * busylabel = new QLabel(statusBar());
612 statusBar()->addPermanentWidget(busylabel);
613 search_mode mode = theGuiApp()->imageSearchMode();
614 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
615 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
616 busylabel->setMovie(busyanim);
620 connect(&d.processing_thread_watcher_, SIGNAL(started()),
621 busylabel, SLOT(show()));
622 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
623 busylabel, SLOT(hide()));
625 QFontMetrics const fm(statusBar()->fontMetrics());
626 int const iconheight = max(int(d.normalIconSize), fm.height());
627 QSize const iconsize(iconheight, iconheight);
629 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
630 shell_escape_ = new QLabel(statusBar());
631 shell_escape_->setPixmap(shellescape);
632 shell_escape_->setScaledContents(true);
633 shell_escape_->setAlignment(Qt::AlignCenter);
634 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
635 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
636 "external commands for this document. "
637 "Right click to change."));
638 SEMenu * menu = new SEMenu(this);
639 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
640 menu, SLOT(showMenu(QPoint)));
641 shell_escape_->hide();
642 statusBar()->addPermanentWidget(shell_escape_);
644 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
645 read_only_ = new QLabel(statusBar());
646 read_only_->setPixmap(readonly);
647 read_only_->setScaledContents(true);
648 read_only_->setAlignment(Qt::AlignCenter);
650 statusBar()->addPermanentWidget(read_only_);
652 version_control_ = new QLabel(statusBar());
653 version_control_->setAlignment(Qt::AlignCenter);
654 version_control_->setFrameStyle(QFrame::StyledPanel);
655 version_control_->hide();
656 statusBar()->addPermanentWidget(version_control_);
658 statusBar()->setSizeGripEnabled(true);
661 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
662 SLOT(autoSaveThreadFinished()));
664 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
665 SLOT(processingThreadStarted()));
666 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
667 SLOT(processingThreadFinished()));
669 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
670 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
672 // set custom application bars context menu, e.g. tool bar and menu bar
673 setContextMenuPolicy(Qt::CustomContextMenu);
674 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
675 SLOT(toolBarPopup(const QPoint &)));
677 // Forbid too small unresizable window because it can happen
678 // with some window manager under X11.
679 setMinimumSize(300, 200);
681 if (lyxrc.allow_geometry_session) {
682 // Now take care of session management.
687 // no session handling, default to a sane size.
688 setGeometry(50, 50, 690, 510);
691 // clear session data if any.
693 settings.remove("views");
703 void GuiView::disableShellEscape()
705 BufferView * bv = documentBufferView();
708 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
709 bv->buffer().params().shell_escape = false;
710 bv->processUpdateFlags(Update::Force);
714 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
716 QVector<GuiWorkArea*> areas;
717 for (int i = 0; i < tabWorkAreaCount(); i++) {
718 TabWorkArea* ta = tabWorkArea(i);
719 for (int u = 0; u < ta->count(); u++) {
720 areas << ta->workArea(u);
726 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
727 string const & format)
729 docstring const fmt = theFormats().prettyName(format);
732 case Buffer::ExportSuccess:
733 msg = bformat(_("Successful export to format: %1$s"), fmt);
735 case Buffer::ExportCancel:
736 msg = _("Document export cancelled.");
738 case Buffer::ExportError:
739 case Buffer::ExportNoPathToFormat:
740 case Buffer::ExportTexPathHasSpaces:
741 case Buffer::ExportConverterError:
742 msg = bformat(_("Error while exporting format: %1$s"), fmt);
744 case Buffer::PreviewSuccess:
745 msg = bformat(_("Successful preview of format: %1$s"), fmt);
747 case Buffer::PreviewError:
748 msg = bformat(_("Error while previewing format: %1$s"), fmt);
750 case Buffer::ExportKilled:
751 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
758 void GuiView::processingThreadStarted()
763 void GuiView::processingThreadFinished()
765 QFutureWatcher<Buffer::ExportStatus> const * watcher =
766 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
768 Buffer::ExportStatus const status = watcher->result();
769 handleExportStatus(this, status, d.processing_format);
772 BufferView const * const bv = currentBufferView();
773 if (bv && !bv->buffer().errorList("Export").empty()) {
778 bool const error = (status != Buffer::ExportSuccess &&
779 status != Buffer::PreviewSuccess &&
780 status != Buffer::ExportCancel);
782 ErrorList & el = bv->buffer().errorList(d.last_export_format);
783 // at this point, we do not know if buffer-view or
784 // master-buffer-view was called. If there was an export error,
785 // and the current buffer's error log is empty, we guess that
786 // it must be master-buffer-view that was called so we set
788 errors(d.last_export_format, el.empty());
793 void GuiView::autoSaveThreadFinished()
795 QFutureWatcher<docstring> const * watcher =
796 static_cast<QFutureWatcher<docstring> const *>(sender());
797 message(watcher->result());
802 void GuiView::saveLayout() const
805 settings.setValue("zoom_ratio", zoom_ratio_);
806 settings.setValue("devel_mode", devel_mode_);
807 settings.beginGroup("views");
808 settings.beginGroup(QString::number(id_));
809 #if defined(Q_WS_X11) || defined(QPA_XCB)
810 settings.setValue("pos", pos());
811 settings.setValue("size", size());
813 settings.setValue("geometry", saveGeometry());
815 settings.setValue("layout", saveState(0));
816 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
820 void GuiView::saveUISettings() const
824 // Save the toolbar private states
825 ToolbarMap::iterator end = d.toolbars_.end();
826 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
827 it->second->saveSession(settings);
828 // Now take care of all other dialogs
829 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
830 for (; it!= d.dialogs_.end(); ++it)
831 it->second->saveSession(settings);
835 bool GuiView::restoreLayout()
838 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
839 // Actual zoom value: default zoom + fractional offset
840 int zoom = lyxrc.defaultZoom * zoom_ratio_;
841 if (zoom < static_cast<int>(zoom_min_))
843 lyxrc.currentZoom = zoom;
844 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
845 settings.beginGroup("views");
846 settings.beginGroup(QString::number(id_));
847 QString const icon_key = "icon_size";
848 if (!settings.contains(icon_key))
851 //code below is skipped when when ~/.config/LyX is (re)created
852 setIconSize(d.iconSize(settings.value(icon_key).toString()));
854 #if defined(Q_WS_X11) || defined(QPA_XCB)
855 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
856 QSize size = settings.value("size", QSize(690, 510)).toSize();
860 // Work-around for bug #6034: the window ends up in an undetermined
861 // state when trying to restore a maximized window when it is
862 // already maximized.
863 if (!(windowState() & Qt::WindowMaximized))
864 if (!restoreGeometry(settings.value("geometry").toByteArray()))
865 setGeometry(50, 50, 690, 510);
867 // Make sure layout is correctly oriented.
868 setLayoutDirection(qApp->layoutDirection());
870 // Allow the toc and view-source dock widget to be restored if needed.
872 if ((dialog = findOrBuild("toc", true)))
873 // see bug 5082. At least setup title and enabled state.
874 // Visibility will be adjusted by restoreState below.
875 dialog->prepareView();
876 if ((dialog = findOrBuild("view-source", true)))
877 dialog->prepareView();
878 if ((dialog = findOrBuild("progress", true)))
879 dialog->prepareView();
881 if (!restoreState(settings.value("layout").toByteArray(), 0))
884 // init the toolbars that have not been restored
885 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
886 Toolbars::Infos::iterator end = guiApp->toolbars().end();
887 for (; cit != end; ++cit) {
888 GuiToolbar * tb = toolbar(cit->name);
889 if (tb && !tb->isRestored())
890 initToolbar(cit->name);
893 // update lock (all) toolbars positions
894 updateLockToolbars();
901 GuiToolbar * GuiView::toolbar(string const & name)
903 ToolbarMap::iterator it = d.toolbars_.find(name);
904 if (it != d.toolbars_.end())
907 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
912 void GuiView::updateLockToolbars()
914 toolbarsMovable_ = false;
915 for (ToolbarInfo const & info : guiApp->toolbars()) {
916 GuiToolbar * tb = toolbar(info.name);
917 if (tb && tb->isMovable())
918 toolbarsMovable_ = true;
923 void GuiView::constructToolbars()
925 ToolbarMap::iterator it = d.toolbars_.begin();
926 for (; it != d.toolbars_.end(); ++it)
930 // I don't like doing this here, but the standard toolbar
931 // destroys this object when it's destroyed itself (vfr)
932 d.layout_ = new LayoutBox(*this);
933 d.stack_widget_->addWidget(d.layout_);
934 d.layout_->move(0,0);
936 // extracts the toolbars from the backend
937 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
938 Toolbars::Infos::iterator end = guiApp->toolbars().end();
939 for (; cit != end; ++cit)
940 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
944 void GuiView::initToolbars()
946 // extracts the toolbars from the backend
947 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
948 Toolbars::Infos::iterator end = guiApp->toolbars().end();
949 for (; cit != end; ++cit)
950 initToolbar(cit->name);
954 void GuiView::initToolbar(string const & name)
956 GuiToolbar * tb = toolbar(name);
959 int const visibility = guiApp->toolbars().defaultVisibility(name);
960 bool newline = !(visibility & Toolbars::SAMEROW);
961 tb->setVisible(false);
962 tb->setVisibility(visibility);
964 if (visibility & Toolbars::TOP) {
966 addToolBarBreak(Qt::TopToolBarArea);
967 addToolBar(Qt::TopToolBarArea, tb);
970 if (visibility & Toolbars::BOTTOM) {
972 addToolBarBreak(Qt::BottomToolBarArea);
973 addToolBar(Qt::BottomToolBarArea, tb);
976 if (visibility & Toolbars::LEFT) {
978 addToolBarBreak(Qt::LeftToolBarArea);
979 addToolBar(Qt::LeftToolBarArea, tb);
982 if (visibility & Toolbars::RIGHT) {
984 addToolBarBreak(Qt::RightToolBarArea);
985 addToolBar(Qt::RightToolBarArea, tb);
988 if (visibility & Toolbars::ON)
989 tb->setVisible(true);
991 tb->setMovable(true);
995 TocModels & GuiView::tocModels()
997 return d.toc_models_;
1001 void GuiView::setFocus()
1003 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1004 QMainWindow::setFocus();
1008 bool GuiView::hasFocus() const
1010 if (currentWorkArea())
1011 return currentWorkArea()->hasFocus();
1012 if (currentMainWorkArea())
1013 return currentMainWorkArea()->hasFocus();
1014 return d.bg_widget_->hasFocus();
1018 void GuiView::focusInEvent(QFocusEvent * e)
1020 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1021 QMainWindow::focusInEvent(e);
1022 // Make sure guiApp points to the correct view.
1023 guiApp->setCurrentView(this);
1024 if (currentWorkArea())
1025 currentWorkArea()->setFocus();
1026 else if (currentMainWorkArea())
1027 currentMainWorkArea()->setFocus();
1029 d.bg_widget_->setFocus();
1033 void GuiView::showEvent(QShowEvent * e)
1035 LYXERR(Debug::GUI, "Passed Geometry "
1036 << size().height() << "x" << size().width()
1037 << "+" << pos().x() << "+" << pos().y());
1039 if (d.splitter_->count() == 0)
1040 // No work area, switch to the background widget.
1044 QMainWindow::showEvent(e);
1048 bool GuiView::closeScheduled()
1055 bool GuiView::prepareAllBuffersForLogout()
1057 Buffer * first = theBufferList().first();
1061 // First, iterate over all buffers and ask the users if unsaved
1062 // changes should be saved.
1063 // We cannot use a for loop as the buffer list cycles.
1066 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1068 b = theBufferList().next(b);
1069 } while (b != first);
1071 // Next, save session state
1072 // When a view/window was closed before without quitting LyX, there
1073 // are already entries in the lastOpened list.
1074 theSession().lastOpened().clear();
1081 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1082 ** is responsibility of the container (e.g., dialog)
1084 void GuiView::closeEvent(QCloseEvent * close_event)
1086 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1088 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1089 Alert::warning(_("Exit LyX"),
1090 _("LyX could not be closed because documents are being processed by LyX."));
1091 close_event->setAccepted(false);
1095 // If the user pressed the x (so we didn't call closeView
1096 // programmatically), we want to clear all existing entries.
1098 theSession().lastOpened().clear();
1103 // it can happen that this event arrives without selecting the view,
1104 // e.g. when clicking the close button on a background window.
1106 if (!closeWorkAreaAll()) {
1108 close_event->ignore();
1112 // Make sure that nothing will use this to be closed View.
1113 guiApp->unregisterView(this);
1115 if (isFullScreen()) {
1116 // Switch off fullscreen before closing.
1121 // Make sure the timer time out will not trigger a statusbar update.
1122 d.statusbar_timer_.stop();
1124 // Saving fullscreen requires additional tweaks in the toolbar code.
1125 // It wouldn't also work under linux natively.
1126 if (lyxrc.allow_geometry_session) {
1131 close_event->accept();
1135 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1137 if (event->mimeData()->hasUrls())
1139 /// \todo Ask lyx-devel is this is enough:
1140 /// if (event->mimeData()->hasFormat("text/plain"))
1141 /// event->acceptProposedAction();
1145 void GuiView::dropEvent(QDropEvent * event)
1147 QList<QUrl> files = event->mimeData()->urls();
1148 if (files.isEmpty())
1151 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1152 for (int i = 0; i != files.size(); ++i) {
1153 string const file = os::internal_path(fromqstr(
1154 files.at(i).toLocalFile()));
1158 string const ext = support::getExtension(file);
1159 vector<const Format *> found_formats;
1161 // Find all formats that have the correct extension.
1162 vector<const Format *> const & import_formats
1163 = theConverters().importableFormats();
1164 vector<const Format *>::const_iterator it = import_formats.begin();
1165 for (; it != import_formats.end(); ++it)
1166 if ((*it)->hasExtension(ext))
1167 found_formats.push_back(*it);
1170 if (found_formats.size() >= 1) {
1171 if (found_formats.size() > 1) {
1172 //FIXME: show a dialog to choose the correct importable format
1173 LYXERR(Debug::FILES,
1174 "Multiple importable formats found, selecting first");
1176 string const arg = found_formats[0]->name() + " " + file;
1177 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1180 //FIXME: do we have to explicitly check whether it's a lyx file?
1181 LYXERR(Debug::FILES,
1182 "No formats found, trying to open it as a lyx file");
1183 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1185 // add the functions to the queue
1186 guiApp->addToFuncRequestQueue(cmd);
1189 // now process the collected functions. We perform the events
1190 // asynchronously. This prevents potential problems in case the
1191 // BufferView is closed within an event.
1192 guiApp->processFuncRequestQueueAsync();
1196 void GuiView::message(docstring const & str)
1198 if (ForkedProcess::iAmAChild())
1201 // call is moved to GUI-thread by GuiProgress
1202 d.progress_->appendMessage(toqstr(str));
1206 void GuiView::clearMessageText()
1208 message(docstring());
1212 void GuiView::updateStatusBarMessage(QString const & str)
1214 statusBar()->showMessage(str);
1215 d.statusbar_timer_.stop();
1216 d.statusbar_timer_.start(3000);
1220 void GuiView::clearMessage()
1222 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1223 // the hasFocus function mostly returns false, even if the focus is on
1224 // a workarea in this view.
1228 d.statusbar_timer_.stop();
1232 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1234 if (wa != d.current_work_area_
1235 || wa->bufferView().buffer().isInternal())
1237 Buffer const & buf = wa->bufferView().buffer();
1238 // Set the windows title
1239 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1240 if (buf.notifiesExternalModification()) {
1241 title = bformat(_("%1$s (modified externally)"), title);
1242 // If the external modification status has changed, then maybe the status of
1243 // buffer-save has changed too.
1247 title += from_ascii(" - LyX");
1249 setWindowTitle(toqstr(title));
1250 // Sets the path for the window: this is used by OSX to
1251 // allow a context click on the title bar showing a menu
1252 // with the path up to the file
1253 setWindowFilePath(toqstr(buf.absFileName()));
1254 // Tell Qt whether the current document is changed
1255 setWindowModified(!buf.isClean());
1257 if (buf.params().shell_escape)
1258 shell_escape_->show();
1260 shell_escape_->hide();
1262 if (buf.hasReadonlyFlag())
1267 if (buf.lyxvc().inUse()) {
1268 version_control_->show();
1269 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1271 version_control_->hide();
1275 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1277 if (d.current_work_area_)
1278 // disconnect the current work area from all slots
1279 QObject::disconnect(d.current_work_area_, 0, this, 0);
1281 disconnectBufferView();
1282 connectBufferView(wa->bufferView());
1283 connectBuffer(wa->bufferView().buffer());
1284 d.current_work_area_ = wa;
1285 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1286 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1287 QObject::connect(wa, SIGNAL(busy(bool)),
1288 this, SLOT(setBusy(bool)));
1289 // connection of a signal to a signal
1290 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1291 this, SIGNAL(bufferViewChanged()));
1292 Q_EMIT updateWindowTitle(wa);
1293 Q_EMIT bufferViewChanged();
1297 void GuiView::onBufferViewChanged()
1300 // Buffer-dependent dialogs must be updated. This is done here because
1301 // some dialogs require buffer()->text.
1306 void GuiView::on_lastWorkAreaRemoved()
1309 // We already are in a close event. Nothing more to do.
1312 if (d.splitter_->count() > 1)
1313 // We have a splitter so don't close anything.
1316 // Reset and updates the dialogs.
1317 Q_EMIT bufferViewChanged();
1322 if (lyxrc.open_buffers_in_tabs)
1323 // Nothing more to do, the window should stay open.
1326 if (guiApp->viewIds().size() > 1) {
1332 // On Mac we also close the last window because the application stay
1333 // resident in memory. On other platforms we don't close the last
1334 // window because this would quit the application.
1340 void GuiView::updateStatusBar()
1342 // let the user see the explicit message
1343 if (d.statusbar_timer_.isActive())
1350 void GuiView::showMessage()
1354 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1355 if (msg.isEmpty()) {
1356 BufferView const * bv = currentBufferView();
1358 msg = toqstr(bv->cursor().currentState(devel_mode_));
1360 msg = qt_("Welcome to LyX!");
1362 statusBar()->showMessage(msg);
1366 bool GuiView::event(QEvent * e)
1370 // Useful debug code:
1371 //case QEvent::ActivationChange:
1372 //case QEvent::WindowDeactivate:
1373 //case QEvent::Paint:
1374 //case QEvent::Enter:
1375 //case QEvent::Leave:
1376 //case QEvent::HoverEnter:
1377 //case QEvent::HoverLeave:
1378 //case QEvent::HoverMove:
1379 //case QEvent::StatusTip:
1380 //case QEvent::DragEnter:
1381 //case QEvent::DragLeave:
1382 //case QEvent::Drop:
1385 case QEvent::WindowActivate: {
1386 GuiView * old_view = guiApp->currentView();
1387 if (this == old_view) {
1389 return QMainWindow::event(e);
1391 if (old_view && old_view->currentBufferView()) {
1392 // save current selection to the selection buffer to allow
1393 // middle-button paste in this window.
1394 cap::saveSelection(old_view->currentBufferView()->cursor());
1396 guiApp->setCurrentView(this);
1397 if (d.current_work_area_)
1398 on_currentWorkAreaChanged(d.current_work_area_);
1402 return QMainWindow::event(e);
1405 case QEvent::ShortcutOverride: {
1407 if (isFullScreen() && menuBar()->isHidden()) {
1408 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1409 // FIXME: we should also try to detect special LyX shortcut such as
1410 // Alt-P and Alt-M. Right now there is a hack in
1411 // GuiWorkArea::processKeySym() that hides again the menubar for
1413 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1415 return QMainWindow::event(e);
1418 return QMainWindow::event(e);
1422 return QMainWindow::event(e);
1426 void GuiView::resetWindowTitle()
1428 setWindowTitle(qt_("LyX"));
1431 bool GuiView::focusNextPrevChild(bool /*next*/)
1438 bool GuiView::busy() const
1444 void GuiView::setBusy(bool busy)
1446 bool const busy_before = busy_ > 0;
1447 busy ? ++busy_ : --busy_;
1448 if ((busy_ > 0) == busy_before)
1449 // busy state didn't change
1453 QApplication::setOverrideCursor(Qt::WaitCursor);
1456 QApplication::restoreOverrideCursor();
1461 void GuiView::resetCommandExecute()
1463 command_execute_ = false;
1468 double GuiView::pixelRatio() const
1470 #if QT_VERSION >= 0x050000
1471 return qt_scale_factor * devicePixelRatio();
1478 GuiWorkArea * GuiView::workArea(int index)
1480 if (TabWorkArea * twa = d.currentTabWorkArea())
1481 if (index < twa->count())
1482 return twa->workArea(index);
1487 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1489 if (currentWorkArea()
1490 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1491 return currentWorkArea();
1492 if (TabWorkArea * twa = d.currentTabWorkArea())
1493 return twa->workArea(buffer);
1498 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1500 // Automatically create a TabWorkArea if there are none yet.
1501 TabWorkArea * tab_widget = d.splitter_->count()
1502 ? d.currentTabWorkArea() : addTabWorkArea();
1503 return tab_widget->addWorkArea(buffer, *this);
1507 TabWorkArea * GuiView::addTabWorkArea()
1509 TabWorkArea * twa = new TabWorkArea;
1510 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1511 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1512 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1513 this, SLOT(on_lastWorkAreaRemoved()));
1515 d.splitter_->addWidget(twa);
1516 d.stack_widget_->setCurrentWidget(d.splitter_);
1521 GuiWorkArea const * GuiView::currentWorkArea() const
1523 return d.current_work_area_;
1527 GuiWorkArea * GuiView::currentWorkArea()
1529 return d.current_work_area_;
1533 GuiWorkArea const * GuiView::currentMainWorkArea() const
1535 if (!d.currentTabWorkArea())
1537 return d.currentTabWorkArea()->currentWorkArea();
1541 GuiWorkArea * GuiView::currentMainWorkArea()
1543 if (!d.currentTabWorkArea())
1545 return d.currentTabWorkArea()->currentWorkArea();
1549 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1551 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1553 d.current_work_area_ = 0;
1555 Q_EMIT bufferViewChanged();
1559 // FIXME: I've no clue why this is here and why it accesses
1560 // theGuiApp()->currentView, which might be 0 (bug 6464).
1561 // See also 27525 (vfr).
1562 if (theGuiApp()->currentView() == this
1563 && theGuiApp()->currentView()->currentWorkArea() == wa)
1566 if (currentBufferView())
1567 cap::saveSelection(currentBufferView()->cursor());
1569 theGuiApp()->setCurrentView(this);
1570 d.current_work_area_ = wa;
1572 // We need to reset this now, because it will need to be
1573 // right if the tabWorkArea gets reset in the for loop. We
1574 // will change it back if we aren't in that case.
1575 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1576 d.current_main_work_area_ = wa;
1578 for (int i = 0; i != d.splitter_->count(); ++i) {
1579 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1580 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1581 << ", Current main wa: " << currentMainWorkArea());
1586 d.current_main_work_area_ = old_cmwa;
1588 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1589 on_currentWorkAreaChanged(wa);
1590 BufferView & bv = wa->bufferView();
1591 bv.cursor().fixIfBroken();
1593 wa->setUpdatesEnabled(true);
1594 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1598 void GuiView::removeWorkArea(GuiWorkArea * wa)
1600 LASSERT(wa, return);
1601 if (wa == d.current_work_area_) {
1603 disconnectBufferView();
1604 d.current_work_area_ = 0;
1605 d.current_main_work_area_ = 0;
1608 bool found_twa = false;
1609 for (int i = 0; i != d.splitter_->count(); ++i) {
1610 TabWorkArea * twa = d.tabWorkArea(i);
1611 if (twa->removeWorkArea(wa)) {
1612 // Found in this tab group, and deleted the GuiWorkArea.
1614 if (twa->count() != 0) {
1615 if (d.current_work_area_ == 0)
1616 // This means that we are closing the current GuiWorkArea, so
1617 // switch to the next GuiWorkArea in the found TabWorkArea.
1618 setCurrentWorkArea(twa->currentWorkArea());
1620 // No more WorkAreas in this tab group, so delete it.
1627 // It is not a tabbed work area (i.e., the search work area), so it
1628 // should be deleted by other means.
1629 LASSERT(found_twa, return);
1631 if (d.current_work_area_ == 0) {
1632 if (d.splitter_->count() != 0) {
1633 TabWorkArea * twa = d.currentTabWorkArea();
1634 setCurrentWorkArea(twa->currentWorkArea());
1636 // No more work areas, switch to the background widget.
1637 setCurrentWorkArea(0);
1643 LayoutBox * GuiView::getLayoutDialog() const
1649 void GuiView::updateLayoutList()
1652 d.layout_->updateContents(false);
1656 void GuiView::updateToolbars()
1658 ToolbarMap::iterator end = d.toolbars_.end();
1659 if (d.current_work_area_) {
1661 if (d.current_work_area_->bufferView().cursor().inMathed()
1662 && !d.current_work_area_->bufferView().cursor().inRegexped())
1663 context |= Toolbars::MATH;
1664 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1665 context |= Toolbars::TABLE;
1666 if (currentBufferView()->buffer().areChangesPresent()
1667 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1668 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1669 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1670 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1671 context |= Toolbars::REVIEW;
1672 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1673 context |= Toolbars::MATHMACROTEMPLATE;
1674 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1675 context |= Toolbars::IPA;
1676 if (command_execute_)
1677 context |= Toolbars::MINIBUFFER;
1678 if (minibuffer_focus_) {
1679 context |= Toolbars::MINIBUFFER_FOCUS;
1680 minibuffer_focus_ = false;
1683 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1684 it->second->update(context);
1686 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1687 it->second->update();
1691 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1693 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1694 LASSERT(newBuffer, return);
1696 GuiWorkArea * wa = workArea(*newBuffer);
1699 newBuffer->masterBuffer()->updateBuffer();
1701 wa = addWorkArea(*newBuffer);
1702 // scroll to the position when the BufferView was last closed
1703 if (lyxrc.use_lastfilepos) {
1704 LastFilePosSection::FilePos filepos =
1705 theSession().lastFilePos().load(newBuffer->fileName());
1706 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1709 //Disconnect the old buffer...there's no new one.
1712 connectBuffer(*newBuffer);
1713 connectBufferView(wa->bufferView());
1715 setCurrentWorkArea(wa);
1719 void GuiView::connectBuffer(Buffer & buf)
1721 buf.setGuiDelegate(this);
1725 void GuiView::disconnectBuffer()
1727 if (d.current_work_area_)
1728 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1732 void GuiView::connectBufferView(BufferView & bv)
1734 bv.setGuiDelegate(this);
1738 void GuiView::disconnectBufferView()
1740 if (d.current_work_area_)
1741 d.current_work_area_->bufferView().setGuiDelegate(0);
1745 void GuiView::errors(string const & error_type, bool from_master)
1747 BufferView const * const bv = currentBufferView();
1751 ErrorList const & el = from_master ?
1752 bv->buffer().masterBuffer()->errorList(error_type) :
1753 bv->buffer().errorList(error_type);
1758 string err = error_type;
1760 err = "from_master|" + error_type;
1761 showDialog("errorlist", err);
1765 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1767 d.toc_models_.updateItem(toqstr(type), dit);
1771 void GuiView::structureChanged()
1773 // This is called from the Buffer, which has no way to ensure that cursors
1774 // in BufferView remain valid.
1775 if (documentBufferView())
1776 documentBufferView()->cursor().sanitize();
1777 // FIXME: This is slightly expensive, though less than the tocBackend update
1778 // (#9880). This also resets the view in the Toc Widget (#6675).
1779 d.toc_models_.reset(documentBufferView());
1780 // Navigator needs more than a simple update in this case. It needs to be
1782 updateDialog("toc", "");
1786 void GuiView::updateDialog(string const & name, string const & sdata)
1788 if (!isDialogVisible(name))
1791 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1792 if (it == d.dialogs_.end())
1795 Dialog * const dialog = it->second.get();
1796 if (dialog->isVisibleView())
1797 dialog->initialiseParams(sdata);
1801 BufferView * GuiView::documentBufferView()
1803 return currentMainWorkArea()
1804 ? ¤tMainWorkArea()->bufferView()
1809 BufferView const * GuiView::documentBufferView() const
1811 return currentMainWorkArea()
1812 ? ¤tMainWorkArea()->bufferView()
1817 BufferView * GuiView::currentBufferView()
1819 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1823 BufferView const * GuiView::currentBufferView() const
1825 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1829 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1830 Buffer const * orig, Buffer * clone)
1832 bool const success = clone->autoSave();
1834 busyBuffers.remove(orig);
1836 ? _("Automatic save done.")
1837 : _("Automatic save failed!");
1841 void GuiView::autoSave()
1843 LYXERR(Debug::INFO, "Running autoSave()");
1845 Buffer * buffer = documentBufferView()
1846 ? &documentBufferView()->buffer() : 0;
1848 resetAutosaveTimers();
1852 GuiViewPrivate::busyBuffers.insert(buffer);
1853 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1854 buffer, buffer->cloneBufferOnly());
1855 d.autosave_watcher_.setFuture(f);
1856 resetAutosaveTimers();
1860 void GuiView::resetAutosaveTimers()
1863 d.autosave_timeout_.restart();
1867 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1870 Buffer * buf = currentBufferView()
1871 ? ¤tBufferView()->buffer() : 0;
1872 Buffer * doc_buffer = documentBufferView()
1873 ? &(documentBufferView()->buffer()) : 0;
1876 /* In LyX/Mac, when a dialog is open, the menus of the
1877 application can still be accessed without giving focus to
1878 the main window. In this case, we want to disable the menu
1879 entries that are buffer-related.
1880 This code must not be used on Linux and Windows, since it
1881 would disable buffer-related entries when hovering over the
1882 menu (see bug #9574).
1884 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1890 // Check whether we need a buffer
1891 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1892 // no, exit directly
1893 flag.message(from_utf8(N_("Command not allowed with"
1894 "out any document open")));
1895 flag.setEnabled(false);
1899 if (cmd.origin() == FuncRequest::TOC) {
1900 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1901 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1902 flag.setEnabled(false);
1906 switch(cmd.action()) {
1907 case LFUN_BUFFER_IMPORT:
1910 case LFUN_MASTER_BUFFER_EXPORT:
1912 && (doc_buffer->parent() != 0
1913 || doc_buffer->hasChildren())
1914 && !d.processing_thread_watcher_.isRunning()
1915 // this launches a dialog, which would be in the wrong Buffer
1916 && !(::lyx::operator==(cmd.argument(), "custom"));
1919 case LFUN_MASTER_BUFFER_UPDATE:
1920 case LFUN_MASTER_BUFFER_VIEW:
1922 && (doc_buffer->parent() != 0
1923 || doc_buffer->hasChildren())
1924 && !d.processing_thread_watcher_.isRunning();
1927 case LFUN_BUFFER_UPDATE:
1928 case LFUN_BUFFER_VIEW: {
1929 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1933 string format = to_utf8(cmd.argument());
1934 if (cmd.argument().empty())
1935 format = doc_buffer->params().getDefaultOutputFormat();
1936 enable = doc_buffer->params().isExportable(format, true);
1940 case LFUN_BUFFER_RELOAD:
1941 enable = doc_buffer && !doc_buffer->isUnnamed()
1942 && doc_buffer->fileName().exists()
1943 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1946 case LFUN_BUFFER_CHILD_OPEN:
1947 enable = doc_buffer != 0;
1950 case LFUN_BUFFER_WRITE:
1951 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1954 //FIXME: This LFUN should be moved to GuiApplication.
1955 case LFUN_BUFFER_WRITE_ALL: {
1956 // We enable the command only if there are some modified buffers
1957 Buffer * first = theBufferList().first();
1962 // We cannot use a for loop as the buffer list is a cycle.
1964 if (!b->isClean()) {
1968 b = theBufferList().next(b);
1969 } while (b != first);
1973 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1974 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1977 case LFUN_BUFFER_EXPORT: {
1978 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1982 return doc_buffer->getStatus(cmd, flag);
1986 case LFUN_BUFFER_EXPORT_AS:
1987 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1992 case LFUN_BUFFER_WRITE_AS:
1993 enable = doc_buffer != 0;
1996 case LFUN_EXPORT_CANCEL:
1997 enable = d.processing_thread_watcher_.isRunning();
2000 case LFUN_BUFFER_CLOSE:
2001 case LFUN_VIEW_CLOSE:
2002 enable = doc_buffer != 0;
2005 case LFUN_BUFFER_CLOSE_ALL:
2006 enable = theBufferList().last() != theBufferList().first();
2009 case LFUN_BUFFER_CHKTEX: {
2010 // hide if we have no checktex command
2011 if (lyxrc.chktex_command.empty()) {
2012 flag.setUnknown(true);
2016 if (!doc_buffer || !doc_buffer->params().isLatex()
2017 || d.processing_thread_watcher_.isRunning()) {
2018 // grey out, don't hide
2026 case LFUN_VIEW_SPLIT:
2027 if (cmd.getArg(0) == "vertical")
2028 enable = doc_buffer && (d.splitter_->count() == 1 ||
2029 d.splitter_->orientation() == Qt::Vertical);
2031 enable = doc_buffer && (d.splitter_->count() == 1 ||
2032 d.splitter_->orientation() == Qt::Horizontal);
2035 case LFUN_TAB_GROUP_CLOSE:
2036 enable = d.tabWorkAreaCount() > 1;
2039 case LFUN_DEVEL_MODE_TOGGLE:
2040 flag.setOnOff(devel_mode_);
2043 case LFUN_TOOLBAR_TOGGLE: {
2044 string const name = cmd.getArg(0);
2045 if (GuiToolbar * t = toolbar(name))
2046 flag.setOnOff(t->isVisible());
2049 docstring const msg =
2050 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2056 case LFUN_TOOLBAR_MOVABLE: {
2057 string const name = cmd.getArg(0);
2058 // use negation since locked == !movable
2060 // toolbar name * locks all toolbars
2061 flag.setOnOff(!toolbarsMovable_);
2062 else if (GuiToolbar * t = toolbar(name))
2063 flag.setOnOff(!(t->isMovable()));
2066 docstring const msg =
2067 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2073 case LFUN_ICON_SIZE:
2074 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2077 case LFUN_DROP_LAYOUTS_CHOICE:
2081 case LFUN_UI_TOGGLE:
2082 flag.setOnOff(isFullScreen());
2085 case LFUN_DIALOG_DISCONNECT_INSET:
2088 case LFUN_DIALOG_HIDE:
2089 // FIXME: should we check if the dialog is shown?
2092 case LFUN_DIALOG_TOGGLE:
2093 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2096 case LFUN_DIALOG_SHOW: {
2097 string const name = cmd.getArg(0);
2099 enable = name == "aboutlyx"
2100 || name == "file" //FIXME: should be removed.
2101 || name == "lyxfiles"
2103 || name == "texinfo"
2104 || name == "progress"
2105 || name == "compare";
2106 else if (name == "character" || name == "symbols"
2107 || name == "mathdelimiter" || name == "mathmatrix") {
2108 if (!buf || buf->isReadonly())
2111 Cursor const & cur = currentBufferView()->cursor();
2112 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2115 else if (name == "latexlog")
2116 enable = FileName(doc_buffer->logName()).isReadableFile();
2117 else if (name == "spellchecker")
2118 enable = theSpellChecker()
2119 && !doc_buffer->isReadonly()
2120 && !doc_buffer->text().empty();
2121 else if (name == "vclog")
2122 enable = doc_buffer->lyxvc().inUse();
2126 case LFUN_DIALOG_UPDATE: {
2127 string const name = cmd.getArg(0);
2129 enable = name == "prefs";
2133 case LFUN_COMMAND_EXECUTE:
2135 case LFUN_MENU_OPEN:
2136 // Nothing to check.
2139 case LFUN_COMPLETION_INLINE:
2140 if (!d.current_work_area_
2141 || !d.current_work_area_->completer().inlinePossible(
2142 currentBufferView()->cursor()))
2146 case LFUN_COMPLETION_POPUP:
2147 if (!d.current_work_area_
2148 || !d.current_work_area_->completer().popupPossible(
2149 currentBufferView()->cursor()))
2154 if (!d.current_work_area_
2155 || !d.current_work_area_->completer().inlinePossible(
2156 currentBufferView()->cursor()))
2160 case LFUN_COMPLETION_ACCEPT:
2161 if (!d.current_work_area_
2162 || (!d.current_work_area_->completer().popupVisible()
2163 && !d.current_work_area_->completer().inlineVisible()
2164 && !d.current_work_area_->completer().completionAvailable()))
2168 case LFUN_COMPLETION_CANCEL:
2169 if (!d.current_work_area_
2170 || (!d.current_work_area_->completer().popupVisible()
2171 && !d.current_work_area_->completer().inlineVisible()))
2175 case LFUN_BUFFER_ZOOM_OUT:
2176 case LFUN_BUFFER_ZOOM_IN: {
2177 // only diff between these two is that the default for ZOOM_OUT
2179 bool const neg_zoom =
2180 convert<int>(cmd.argument()) < 0 ||
2181 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2182 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2183 docstring const msg =
2184 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2188 enable = doc_buffer;
2192 case LFUN_BUFFER_ZOOM: {
2193 bool const less_than_min_zoom =
2194 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2195 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2196 docstring const msg =
2197 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2202 enable = doc_buffer;
2206 case LFUN_BUFFER_MOVE_NEXT:
2207 case LFUN_BUFFER_MOVE_PREVIOUS:
2208 // we do not cycle when moving
2209 case LFUN_BUFFER_NEXT:
2210 case LFUN_BUFFER_PREVIOUS:
2211 // because we cycle, it doesn't matter whether on first or last
2212 enable = (d.currentTabWorkArea()->count() > 1);
2214 case LFUN_BUFFER_SWITCH:
2215 // toggle on the current buffer, but do not toggle off
2216 // the other ones (is that a good idea?)
2218 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2219 flag.setOnOff(true);
2222 case LFUN_VC_REGISTER:
2223 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2225 case LFUN_VC_RENAME:
2226 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2229 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2231 case LFUN_VC_CHECK_IN:
2232 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2234 case LFUN_VC_CHECK_OUT:
2235 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2237 case LFUN_VC_LOCKING_TOGGLE:
2238 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2239 && doc_buffer->lyxvc().lockingToggleEnabled();
2240 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2242 case LFUN_VC_REVERT:
2243 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2244 && !doc_buffer->hasReadonlyFlag();
2246 case LFUN_VC_UNDO_LAST:
2247 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2249 case LFUN_VC_REPO_UPDATE:
2250 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2252 case LFUN_VC_COMMAND: {
2253 if (cmd.argument().empty())
2255 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2259 case LFUN_VC_COMPARE:
2260 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2263 case LFUN_SERVER_GOTO_FILE_ROW:
2264 case LFUN_LYX_ACTIVATE:
2266 case LFUN_FORWARD_SEARCH:
2267 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2270 case LFUN_FILE_INSERT_PLAINTEXT:
2271 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2272 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2275 case LFUN_SPELLING_CONTINUOUSLY:
2276 flag.setOnOff(lyxrc.spellcheck_continuously);
2284 flag.setEnabled(false);
2290 static FileName selectTemplateFile()
2292 FileDialog dlg(qt_("Select template file"));
2293 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2294 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2296 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2297 QStringList(qt_("LyX Documents (*.lyx)")));
2299 if (result.first == FileDialog::Later)
2301 if (result.second.isEmpty())
2303 return FileName(fromqstr(result.second));
2307 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2311 Buffer * newBuffer = 0;
2313 newBuffer = checkAndLoadLyXFile(filename);
2314 } catch (ExceptionMessage const & e) {
2321 message(_("Document not loaded."));
2325 setBuffer(newBuffer);
2326 newBuffer->errors("Parse");
2329 theSession().lastFiles().add(filename);
2330 theSession().writeFile();
2337 void GuiView::openDocument(string const & fname)
2339 string initpath = lyxrc.document_path;
2341 if (documentBufferView()) {
2342 string const trypath = documentBufferView()->buffer().filePath();
2343 // If directory is writeable, use this as default.
2344 if (FileName(trypath).isDirWritable())
2350 if (fname.empty()) {
2351 FileDialog dlg(qt_("Select document to open"));
2352 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2353 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2355 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2356 FileDialog::Result result =
2357 dlg.open(toqstr(initpath), filter);
2359 if (result.first == FileDialog::Later)
2362 filename = fromqstr(result.second);
2364 // check selected filename
2365 if (filename.empty()) {
2366 message(_("Canceled."));
2372 // get absolute path of file and add ".lyx" to the filename if
2374 FileName const fullname =
2375 fileSearch(string(), filename, "lyx", support::may_not_exist);
2376 if (!fullname.empty())
2377 filename = fullname.absFileName();
2379 if (!fullname.onlyPath().isDirectory()) {
2380 Alert::warning(_("Invalid filename"),
2381 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2382 from_utf8(fullname.absFileName())));
2386 // if the file doesn't exist and isn't already open (bug 6645),
2387 // let the user create one
2388 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2389 !LyXVC::file_not_found_hook(fullname)) {
2390 // the user specifically chose this name. Believe him.
2391 Buffer * const b = newFile(filename, string(), true);
2397 docstring const disp_fn = makeDisplayPath(filename);
2398 message(bformat(_("Opening document %1$s..."), disp_fn));
2401 Buffer * buf = loadDocument(fullname);
2403 str2 = bformat(_("Document %1$s opened."), disp_fn);
2404 if (buf->lyxvc().inUse())
2405 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2406 " " + _("Version control detected.");
2408 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2413 // FIXME: clean that
2414 static bool import(GuiView * lv, FileName const & filename,
2415 string const & format, ErrorList & errorList)
2417 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2419 string loader_format;
2420 vector<string> loaders = theConverters().loaders();
2421 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2422 vector<string>::const_iterator it = loaders.begin();
2423 vector<string>::const_iterator en = loaders.end();
2424 for (; it != en; ++it) {
2425 if (!theConverters().isReachable(format, *it))
2428 string const tofile =
2429 support::changeExtension(filename.absFileName(),
2430 theFormats().extension(*it));
2431 if (theConverters().convert(0, filename, FileName(tofile),
2432 filename, format, *it, errorList) != Converters::SUCCESS)
2434 loader_format = *it;
2437 if (loader_format.empty()) {
2438 frontend::Alert::error(_("Couldn't import file"),
2439 bformat(_("No information for importing the format %1$s."),
2440 theFormats().prettyName(format)));
2444 loader_format = format;
2446 if (loader_format == "lyx") {
2447 Buffer * buf = lv->loadDocument(lyxfile);
2451 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2455 bool as_paragraphs = loader_format == "textparagraph";
2456 string filename2 = (loader_format == format) ? filename.absFileName()
2457 : support::changeExtension(filename.absFileName(),
2458 theFormats().extension(loader_format));
2459 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2461 guiApp->setCurrentView(lv);
2462 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2469 void GuiView::importDocument(string const & argument)
2472 string filename = split(argument, format, ' ');
2474 LYXERR(Debug::INFO, format << " file: " << filename);
2476 // need user interaction
2477 if (filename.empty()) {
2478 string initpath = lyxrc.document_path;
2479 if (documentBufferView()) {
2480 string const trypath = documentBufferView()->buffer().filePath();
2481 // If directory is writeable, use this as default.
2482 if (FileName(trypath).isDirWritable())
2486 docstring const text = bformat(_("Select %1$s file to import"),
2487 theFormats().prettyName(format));
2489 FileDialog dlg(toqstr(text));
2490 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2491 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2493 docstring filter = theFormats().prettyName(format);
2496 filter += from_utf8(theFormats().extensions(format));
2499 FileDialog::Result result =
2500 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2502 if (result.first == FileDialog::Later)
2505 filename = fromqstr(result.second);
2507 // check selected filename
2508 if (filename.empty())
2509 message(_("Canceled."));
2512 if (filename.empty())
2515 // get absolute path of file
2516 FileName const fullname(support::makeAbsPath(filename));
2518 // Can happen if the user entered a path into the dialog
2520 if (fullname.onlyFileName().empty()) {
2521 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2522 "Aborting import."),
2523 from_utf8(fullname.absFileName()));
2524 frontend::Alert::error(_("File name error"), msg);
2525 message(_("Canceled."));
2530 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2532 // Check if the document already is open
2533 Buffer * buf = theBufferList().getBuffer(lyxfile);
2536 if (!closeBuffer()) {
2537 message(_("Canceled."));
2542 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2544 // if the file exists already, and we didn't do
2545 // -i lyx thefile.lyx, warn
2546 if (lyxfile.exists() && fullname != lyxfile) {
2548 docstring text = bformat(_("The document %1$s already exists.\n\n"
2549 "Do you want to overwrite that document?"), displaypath);
2550 int const ret = Alert::prompt(_("Overwrite document?"),
2551 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2554 message(_("Canceled."));
2559 message(bformat(_("Importing %1$s..."), displaypath));
2560 ErrorList errorList;
2561 if (import(this, fullname, format, errorList))
2562 message(_("imported."));
2564 message(_("file not imported!"));
2566 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2570 void GuiView::newDocument(string const & filename, string templatefile,
2573 FileName initpath(lyxrc.document_path);
2574 if (documentBufferView()) {
2575 FileName const trypath(documentBufferView()->buffer().filePath());
2576 // If directory is writeable, use this as default.
2577 if (trypath.isDirWritable())
2581 if (from_template) {
2582 if (templatefile.empty())
2583 templatefile = selectTemplateFile().absFileName();
2584 if (templatefile.empty())
2589 if (filename.empty())
2590 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2592 b = newFile(filename, templatefile, true);
2597 // If no new document could be created, it is unsure
2598 // whether there is a valid BufferView.
2599 if (currentBufferView())
2600 // Ensure the cursor is correctly positioned on screen.
2601 currentBufferView()->showCursor();
2605 void GuiView::insertLyXFile(docstring const & fname)
2607 BufferView * bv = documentBufferView();
2612 FileName filename(to_utf8(fname));
2613 if (filename.empty()) {
2614 // Launch a file browser
2616 string initpath = lyxrc.document_path;
2617 string const trypath = bv->buffer().filePath();
2618 // If directory is writeable, use this as default.
2619 if (FileName(trypath).isDirWritable())
2623 FileDialog dlg(qt_("Select LyX document to insert"));
2624 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2625 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2627 FileDialog::Result result = dlg.open(toqstr(initpath),
2628 QStringList(qt_("LyX Documents (*.lyx)")));
2630 if (result.first == FileDialog::Later)
2634 filename.set(fromqstr(result.second));
2636 // check selected filename
2637 if (filename.empty()) {
2638 // emit message signal.
2639 message(_("Canceled."));
2644 bv->insertLyXFile(filename);
2645 bv->buffer().errors("Parse");
2649 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2651 FileName fname = b.fileName();
2652 FileName const oldname = fname;
2654 if (!newname.empty()) {
2656 fname = support::makeAbsPath(to_utf8(newname), oldname.onlyPath().absFileName());
2658 // Switch to this Buffer.
2661 // No argument? Ask user through dialog.
2663 FileDialog dlg(qt_("Choose a filename to save document as"));
2664 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2665 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2667 if (!isLyXFileName(fname.absFileName()))
2668 fname.changeExtension(".lyx");
2670 FileDialog::Result result =
2671 dlg.save(toqstr(fname.onlyPath().absFileName()),
2672 QStringList(qt_("LyX Documents (*.lyx)")),
2673 toqstr(fname.onlyFileName()));
2675 if (result.first == FileDialog::Later)
2678 fname.set(fromqstr(result.second));
2683 if (!isLyXFileName(fname.absFileName()))
2684 fname.changeExtension(".lyx");
2687 // fname is now the new Buffer location.
2689 // if there is already a Buffer open with this name, we do not want
2690 // to have another one. (the second test makes sure we're not just
2691 // trying to overwrite ourselves, which is fine.)
2692 if (theBufferList().exists(fname) && fname != oldname
2693 && theBufferList().getBuffer(fname) != &b) {
2694 docstring const text =
2695 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2696 "Please close it before attempting to overwrite it.\n"
2697 "Do you want to choose a new filename?"),
2698 from_utf8(fname.absFileName()));
2699 int const ret = Alert::prompt(_("Chosen File Already Open"),
2700 text, 0, 1, _("&Rename"), _("&Cancel"));
2702 case 0: return renameBuffer(b, docstring(), kind);
2703 case 1: return false;
2708 bool const existsLocal = fname.exists();
2709 bool const existsInVC = LyXVC::fileInVC(fname);
2710 if (existsLocal || existsInVC) {
2711 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2712 if (kind != LV_WRITE_AS && existsInVC) {
2713 // renaming to a name that is already in VC
2715 docstring text = bformat(_("The document %1$s "
2716 "is already registered.\n\n"
2717 "Do you want to choose a new name?"),
2719 docstring const title = (kind == LV_VC_RENAME) ?
2720 _("Rename document?") : _("Copy document?");
2721 docstring const button = (kind == LV_VC_RENAME) ?
2722 _("&Rename") : _("&Copy");
2723 int const ret = Alert::prompt(title, text, 0, 1,
2724 button, _("&Cancel"));
2726 case 0: return renameBuffer(b, docstring(), kind);
2727 case 1: return false;
2732 docstring text = bformat(_("The document %1$s "
2733 "already exists.\n\n"
2734 "Do you want to overwrite that document?"),
2736 int const ret = Alert::prompt(_("Overwrite document?"),
2737 text, 0, 2, _("&Overwrite"),
2738 _("&Rename"), _("&Cancel"));
2741 case 1: return renameBuffer(b, docstring(), kind);
2742 case 2: return false;
2748 case LV_VC_RENAME: {
2749 string msg = b.lyxvc().rename(fname);
2752 message(from_utf8(msg));
2756 string msg = b.lyxvc().copy(fname);
2759 message(from_utf8(msg));
2765 // LyXVC created the file already in case of LV_VC_RENAME or
2766 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2767 // relative paths of included stuff right if we moved e.g. from
2768 // /a/b.lyx to /a/c/b.lyx.
2770 bool const saved = saveBuffer(b, fname);
2777 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2779 FileName fname = b.fileName();
2781 FileDialog dlg(qt_("Choose a filename to export the document as"));
2782 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2785 QString const anyformat = qt_("Guess from extension (*.*)");
2788 vector<Format const *> export_formats;
2789 for (Format const & f : theFormats())
2790 if (f.documentFormat())
2791 export_formats.push_back(&f);
2792 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2793 map<QString, string> fmap;
2796 for (Format const * f : export_formats) {
2797 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2798 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2800 from_ascii(f->extension())));
2801 types << loc_filter;
2802 fmap[loc_filter] = f->name();
2803 if (from_ascii(f->name()) == iformat) {
2804 filter = loc_filter;
2805 ext = f->extension();
2808 string ofname = fname.onlyFileName();
2810 ofname = support::changeExtension(ofname, ext);
2811 FileDialog::Result result =
2812 dlg.save(toqstr(fname.onlyPath().absFileName()),
2816 if (result.first != FileDialog::Chosen)
2820 fname.set(fromqstr(result.second));
2821 if (filter == anyformat)
2822 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2824 fmt_name = fmap[filter];
2825 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2826 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2828 if (fmt_name.empty() || fname.empty())
2831 // fname is now the new Buffer location.
2832 if (FileName(fname).exists()) {
2833 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2834 docstring text = bformat(_("The document %1$s already "
2835 "exists.\n\nDo you want to "
2836 "overwrite that document?"),
2838 int const ret = Alert::prompt(_("Overwrite document?"),
2839 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2842 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2843 case 2: return false;
2847 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2850 return dr.dispatched();
2854 bool GuiView::saveBuffer(Buffer & b)
2856 return saveBuffer(b, FileName());
2860 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2862 if (workArea(b) && workArea(b)->inDialogMode())
2865 if (fn.empty() && b.isUnnamed())
2866 return renameBuffer(b, docstring());
2868 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2870 theSession().lastFiles().add(b.fileName());
2871 theSession().writeFile();
2875 // Switch to this Buffer.
2878 // FIXME: we don't tell the user *WHY* the save failed !!
2879 docstring const file = makeDisplayPath(b.absFileName(), 30);
2880 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2881 "Do you want to rename the document and "
2882 "try again?"), file);
2883 int const ret = Alert::prompt(_("Rename and save?"),
2884 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2887 if (!renameBuffer(b, docstring()))
2896 return saveBuffer(b, fn);
2900 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2902 return closeWorkArea(wa, false);
2906 // We only want to close the buffer if it is not visible in other workareas
2907 // of the same view, nor in other views, and if this is not a child
2908 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2910 Buffer & buf = wa->bufferView().buffer();
2912 bool last_wa = d.countWorkAreasOf(buf) == 1
2913 && !inOtherView(buf) && !buf.parent();
2915 bool close_buffer = last_wa;
2918 if (lyxrc.close_buffer_with_last_view == "yes")
2920 else if (lyxrc.close_buffer_with_last_view == "no")
2921 close_buffer = false;
2924 if (buf.isUnnamed())
2925 file = from_utf8(buf.fileName().onlyFileName());
2927 file = buf.fileName().displayName(30);
2928 docstring const text = bformat(
2929 _("Last view on document %1$s is being closed.\n"
2930 "Would you like to close or hide the document?\n"
2932 "Hidden documents can be displayed back through\n"
2933 "the menu: View->Hidden->...\n"
2935 "To remove this question, set your preference in:\n"
2936 " Tools->Preferences->Look&Feel->UserInterface\n"
2938 int ret = Alert::prompt(_("Close or hide document?"),
2939 text, 0, 1, _("&Close"), _("&Hide"));
2940 close_buffer = (ret == 0);
2944 return closeWorkArea(wa, close_buffer);
2948 bool GuiView::closeBuffer()
2950 GuiWorkArea * wa = currentMainWorkArea();
2951 // coverity complained about this
2952 // it seems unnecessary, but perhaps is worth the check
2953 LASSERT(wa, return false);
2955 setCurrentWorkArea(wa);
2956 Buffer & buf = wa->bufferView().buffer();
2957 return closeWorkArea(wa, !buf.parent());
2961 void GuiView::writeSession() const {
2962 GuiWorkArea const * active_wa = currentMainWorkArea();
2963 for (int i = 0; i < d.splitter_->count(); ++i) {
2964 TabWorkArea * twa = d.tabWorkArea(i);
2965 for (int j = 0; j < twa->count(); ++j) {
2966 GuiWorkArea * wa = twa->workArea(j);
2967 Buffer & buf = wa->bufferView().buffer();
2968 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
2974 bool GuiView::closeBufferAll()
2976 // Close the workareas in all other views
2977 QList<int> const ids = guiApp->viewIds();
2978 for (int i = 0; i != ids.size(); ++i) {
2979 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
2983 // Close our own workareas
2984 if (!closeWorkAreaAll())
2987 // Now close the hidden buffers. We prevent hidden buffers from being
2988 // dirty, so we can just close them.
2989 theBufferList().closeAll();
2994 bool GuiView::closeWorkAreaAll()
2996 setCurrentWorkArea(currentMainWorkArea());
2998 // We might be in a situation that there is still a tabWorkArea, but
2999 // there are no tabs anymore. This can happen when we get here after a
3000 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3001 // many TabWorkArea's have no documents anymore.
3004 // We have to call count() each time, because it can happen that
3005 // more than one splitter will disappear in one iteration (bug 5998).
3006 while (d.splitter_->count() > empty_twa) {
3007 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3009 if (twa->count() == 0)
3012 setCurrentWorkArea(twa->currentWorkArea());
3013 if (!closeTabWorkArea(twa))
3021 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3026 Buffer & buf = wa->bufferView().buffer();
3028 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3029 Alert::warning(_("Close document"),
3030 _("Document could not be closed because it is being processed by LyX."));
3035 return closeBuffer(buf);
3037 if (!inMultiTabs(wa))
3038 if (!saveBufferIfNeeded(buf, true))
3046 bool GuiView::closeBuffer(Buffer & buf)
3048 bool success = true;
3049 ListOfBuffers clist = buf.getChildren();
3050 ListOfBuffers::const_iterator it = clist.begin();
3051 ListOfBuffers::const_iterator const bend = clist.end();
3052 for (; it != bend; ++it) {
3053 Buffer * child_buf = *it;
3054 if (theBufferList().isOthersChild(&buf, child_buf)) {
3055 child_buf->setParent(0);
3059 // FIXME: should we look in other tabworkareas?
3060 // ANSWER: I don't think so. I've tested, and if the child is
3061 // open in some other window, it closes without a problem.
3062 GuiWorkArea * child_wa = workArea(*child_buf);
3065 // If we are in a close_event all children will be closed in some time,
3066 // so no need to do it here. This will ensure that the children end up
3067 // in the session file in the correct order. If we close the master
3068 // buffer, we can close or release the child buffers here too.
3070 success = closeWorkArea(child_wa, true);
3074 // In this case the child buffer is open but hidden.
3075 // Even in this case, children can be dirty (e.g.,
3076 // after a label change in the master, see #11405).
3077 // Therefore, check this
3078 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3079 // If we are in a close_event all children will be closed in some time,
3080 // so no need to do it here. This will ensure that the children end up
3081 // in the session file in the correct order. If we close the master
3082 // buffer, we can close or release the child buffers here too.
3084 // Save dirty buffers also if closing_!
3085 if (saveBufferIfNeeded(*child_buf, false)) {
3086 child_buf->removeAutosaveFile();
3087 theBufferList().release(child_buf);
3089 // Saving of dirty children has been cancelled.
3090 // Cancel the whole process.
3097 // goto bookmark to update bookmark pit.
3098 // FIXME: we should update only the bookmarks related to this buffer!
3099 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3100 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3101 guiApp->gotoBookmark(i+1, false, false);
3103 if (saveBufferIfNeeded(buf, false)) {
3104 buf.removeAutosaveFile();
3105 theBufferList().release(&buf);
3109 // open all children again to avoid a crash because of dangling
3110 // pointers (bug 6603)
3116 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3118 while (twa == d.currentTabWorkArea()) {
3119 twa->setCurrentIndex(twa->count() - 1);
3121 GuiWorkArea * wa = twa->currentWorkArea();
3122 Buffer & b = wa->bufferView().buffer();
3124 // We only want to close the buffer if the same buffer is not visible
3125 // in another view, and if this is not a child and if we are closing
3126 // a view (not a tabgroup).
3127 bool const close_buffer =
3128 !inOtherView(b) && !b.parent() && closing_;
3130 if (!closeWorkArea(wa, close_buffer))
3137 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3139 if (buf.isClean() || buf.paragraphs().empty())
3142 // Switch to this Buffer.
3148 if (buf.isUnnamed()) {
3149 file = from_utf8(buf.fileName().onlyFileName());
3152 FileName filename = buf.fileName();
3154 file = filename.displayName(30);
3155 exists = filename.exists();
3158 // Bring this window to top before asking questions.
3163 if (hiding && buf.isUnnamed()) {
3164 docstring const text = bformat(_("The document %1$s has not been "
3165 "saved yet.\n\nDo you want to save "
3166 "the document?"), file);
3167 ret = Alert::prompt(_("Save new document?"),
3168 text, 0, 1, _("&Save"), _("&Cancel"));
3172 docstring const text = exists ?
3173 bformat(_("The document %1$s has unsaved changes."
3174 "\n\nDo you want to save the document or "
3175 "discard the changes?"), file) :
3176 bformat(_("The document %1$s has not been saved yet."
3177 "\n\nDo you want to save the document or "
3178 "discard it entirely?"), file);
3179 docstring const title = exists ?
3180 _("Save changed document?") : _("Save document?");
3181 ret = Alert::prompt(title, text, 0, 2,
3182 _("&Save"), _("&Discard"), _("&Cancel"));
3187 if (!saveBuffer(buf))
3191 // If we crash after this we could have no autosave file
3192 // but I guess this is really improbable (Jug).
3193 // Sometimes improbable things happen:
3194 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3195 // buf.removeAutosaveFile();
3197 // revert all changes
3208 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3210 Buffer & buf = wa->bufferView().buffer();
3212 for (int i = 0; i != d.splitter_->count(); ++i) {
3213 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3214 if (wa_ && wa_ != wa)
3217 return inOtherView(buf);
3221 bool GuiView::inOtherView(Buffer & buf)
3223 QList<int> const ids = guiApp->viewIds();
3225 for (int i = 0; i != ids.size(); ++i) {
3229 if (guiApp->view(ids[i]).workArea(buf))
3236 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3238 if (!documentBufferView())
3241 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3242 Buffer * const curbuf = &documentBufferView()->buffer();
3243 int nwa = twa->count();
3244 for (int i = 0; i < nwa; ++i) {
3245 if (&workArea(i)->bufferView().buffer() == curbuf) {
3247 if (np == NEXTBUFFER)
3248 next_index = (i == nwa - 1 ? 0 : i + 1);
3250 next_index = (i == 0 ? nwa - 1 : i - 1);
3252 twa->moveTab(i, next_index);
3254 setBuffer(&workArea(next_index)->bufferView().buffer());
3262 /// make sure the document is saved
3263 static bool ensureBufferClean(Buffer * buffer)
3265 LASSERT(buffer, return false);
3266 if (buffer->isClean() && !buffer->isUnnamed())
3269 docstring const file = buffer->fileName().displayName(30);
3272 if (!buffer->isUnnamed()) {
3273 text = bformat(_("The document %1$s has unsaved "
3274 "changes.\n\nDo you want to save "
3275 "the document?"), file);
3276 title = _("Save changed document?");
3279 text = bformat(_("The document %1$s has not been "
3280 "saved yet.\n\nDo you want to save "
3281 "the document?"), file);
3282 title = _("Save new document?");
3284 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3287 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3289 return buffer->isClean() && !buffer->isUnnamed();
3293 bool GuiView::reloadBuffer(Buffer & buf)
3295 currentBufferView()->cursor().reset();
3296 Buffer::ReadStatus status = buf.reload();
3297 return status == Buffer::ReadSuccess;
3301 void GuiView::checkExternallyModifiedBuffers()
3303 BufferList::iterator bit = theBufferList().begin();
3304 BufferList::iterator const bend = theBufferList().end();
3305 for (; bit != bend; ++bit) {
3306 Buffer * buf = *bit;
3307 if (buf->fileName().exists() && buf->isChecksumModified()) {
3308 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3309 " Reload now? Any local changes will be lost."),
3310 from_utf8(buf->absFileName()));
3311 int const ret = Alert::prompt(_("Reload externally changed document?"),
3312 text, 0, 1, _("&Reload"), _("&Cancel"));
3320 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3322 Buffer * buffer = documentBufferView()
3323 ? &(documentBufferView()->buffer()) : 0;
3325 switch (cmd.action()) {
3326 case LFUN_VC_REGISTER:
3327 if (!buffer || !ensureBufferClean(buffer))
3329 if (!buffer->lyxvc().inUse()) {
3330 if (buffer->lyxvc().registrer()) {
3331 reloadBuffer(*buffer);
3332 dr.clearMessageUpdate();
3337 case LFUN_VC_RENAME:
3338 case LFUN_VC_COPY: {
3339 if (!buffer || !ensureBufferClean(buffer))
3341 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3342 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3343 // Some changes are not yet committed.
3344 // We test here and not in getStatus(), since
3345 // this test is expensive.
3347 LyXVC::CommandResult ret =
3348 buffer->lyxvc().checkIn(log);
3350 if (ret == LyXVC::ErrorCommand ||
3351 ret == LyXVC::VCSuccess)
3352 reloadBuffer(*buffer);
3353 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3354 frontend::Alert::error(
3355 _("Revision control error."),
3356 _("Document could not be checked in."));
3360 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3361 LV_VC_RENAME : LV_VC_COPY;
3362 renameBuffer(*buffer, cmd.argument(), kind);
3367 case LFUN_VC_CHECK_IN:
3368 if (!buffer || !ensureBufferClean(buffer))
3370 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3372 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3374 // Only skip reloading if the checkin was cancelled or
3375 // an error occurred before the real checkin VCS command
3376 // was executed, since the VCS might have changed the
3377 // file even if it could not checkin successfully.
3378 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3379 reloadBuffer(*buffer);
3383 case LFUN_VC_CHECK_OUT:
3384 if (!buffer || !ensureBufferClean(buffer))
3386 if (buffer->lyxvc().inUse()) {
3387 dr.setMessage(buffer->lyxvc().checkOut());
3388 reloadBuffer(*buffer);
3392 case LFUN_VC_LOCKING_TOGGLE:
3393 LASSERT(buffer, return);
3394 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3396 if (buffer->lyxvc().inUse()) {
3397 string res = buffer->lyxvc().lockingToggle();
3399 frontend::Alert::error(_("Revision control error."),
3400 _("Error when setting the locking property."));
3403 reloadBuffer(*buffer);
3408 case LFUN_VC_REVERT:
3409 LASSERT(buffer, return);
3410 if (buffer->lyxvc().revert()) {
3411 reloadBuffer(*buffer);
3412 dr.clearMessageUpdate();
3416 case LFUN_VC_UNDO_LAST:
3417 LASSERT(buffer, return);
3418 buffer->lyxvc().undoLast();
3419 reloadBuffer(*buffer);
3420 dr.clearMessageUpdate();
3423 case LFUN_VC_REPO_UPDATE:
3424 LASSERT(buffer, return);
3425 if (ensureBufferClean(buffer)) {
3426 dr.setMessage(buffer->lyxvc().repoUpdate());
3427 checkExternallyModifiedBuffers();
3431 case LFUN_VC_COMMAND: {
3432 string flag = cmd.getArg(0);
3433 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3436 if (contains(flag, 'M')) {
3437 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3440 string path = cmd.getArg(1);
3441 if (contains(path, "$$p") && buffer)
3442 path = subst(path, "$$p", buffer->filePath());
3443 LYXERR(Debug::LYXVC, "Directory: " << path);
3445 if (!pp.isReadableDirectory()) {
3446 lyxerr << _("Directory is not accessible.") << endl;
3449 support::PathChanger p(pp);
3451 string command = cmd.getArg(2);
3452 if (command.empty())
3455 command = subst(command, "$$i", buffer->absFileName());
3456 command = subst(command, "$$p", buffer->filePath());
3458 command = subst(command, "$$m", to_utf8(message));
3459 LYXERR(Debug::LYXVC, "Command: " << command);
3461 one.startscript(Systemcall::Wait, command);
3465 if (contains(flag, 'I'))
3466 buffer->markDirty();
3467 if (contains(flag, 'R'))
3468 reloadBuffer(*buffer);
3473 case LFUN_VC_COMPARE: {
3474 if (cmd.argument().empty()) {
3475 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3479 string rev1 = cmd.getArg(0);
3484 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3487 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3488 f2 = buffer->absFileName();
3490 string rev2 = cmd.getArg(1);
3494 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3498 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3499 f1 << "\n" << f2 << "\n" );
3500 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3501 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3511 void GuiView::openChildDocument(string const & fname)
3513 LASSERT(documentBufferView(), return);
3514 Buffer & buffer = documentBufferView()->buffer();
3515 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3516 documentBufferView()->saveBookmark(false);
3518 if (theBufferList().exists(filename)) {
3519 child = theBufferList().getBuffer(filename);
3522 message(bformat(_("Opening child document %1$s..."),
3523 makeDisplayPath(filename.absFileName())));
3524 child = loadDocument(filename, false);
3526 // Set the parent name of the child document.
3527 // This makes insertion of citations and references in the child work,
3528 // when the target is in the parent or another child document.
3530 child->setParent(&buffer);
3534 bool GuiView::goToFileRow(string const & argument)
3538 size_t i = argument.find_last_of(' ');
3539 if (i != string::npos) {
3540 file_name = os::internal_path(trim(argument.substr(0, i)));
3541 istringstream is(argument.substr(i + 1));
3546 if (i == string::npos) {
3547 LYXERR0("Wrong argument: " << argument);
3551 string const abstmp = package().temp_dir().absFileName();
3552 string const realtmp = package().temp_dir().realPath();
3553 // We have to use os::path_prefix_is() here, instead of
3554 // simply prefixIs(), because the file name comes from
3555 // an external application and may need case adjustment.
3556 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3557 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3558 // Needed by inverse dvi search. If it is a file
3559 // in tmpdir, call the apropriated function.
3560 // If tmpdir is a symlink, we may have the real
3561 // path passed back, so we correct for that.
3562 if (!prefixIs(file_name, abstmp))
3563 file_name = subst(file_name, realtmp, abstmp);
3564 buf = theBufferList().getBufferFromTmp(file_name);
3566 // Must replace extension of the file to be .lyx
3567 // and get full path
3568 FileName const s = fileSearch(string(),
3569 support::changeExtension(file_name, ".lyx"), "lyx");
3570 // Either change buffer or load the file
3571 if (theBufferList().exists(s))
3572 buf = theBufferList().getBuffer(s);
3573 else if (s.exists()) {
3574 buf = loadDocument(s);
3579 _("File does not exist: %1$s"),
3580 makeDisplayPath(file_name)));
3586 _("No buffer for file: %1$s."),
3587 makeDisplayPath(file_name))
3592 bool success = documentBufferView()->setCursorFromRow(row);
3594 LYXERR(Debug::LATEX,
3595 "setCursorFromRow: invalid position for row " << row);
3596 frontend::Alert::error(_("Inverse Search Failed"),
3597 _("Invalid position requested by inverse search.\n"
3598 "You may need to update the viewed document."));
3604 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3606 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3607 menu->exec(QCursor::pos());
3612 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3613 Buffer const * orig, Buffer * clone, string const & format)
3615 Buffer::ExportStatus const status = func(format);
3617 // the cloning operation will have produced a clone of the entire set of
3618 // documents, starting from the master. so we must delete those.
3619 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3621 busyBuffers.remove(orig);
3626 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3627 Buffer const * orig, Buffer * clone, string const & format)
3629 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3631 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3635 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3636 Buffer const * orig, Buffer * clone, string const & format)
3638 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3640 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3644 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3645 Buffer const * orig, Buffer * clone, string const & format)
3647 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3649 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3653 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3654 string const & argument,
3655 Buffer const * used_buffer,
3656 docstring const & msg,
3657 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3658 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3659 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3665 string format = argument;
3667 format = used_buffer->params().getDefaultOutputFormat();
3668 processing_format = format;
3670 progress_->clearMessages();
3673 #if EXPORT_in_THREAD
3675 GuiViewPrivate::busyBuffers.insert(used_buffer);
3676 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3677 if (!cloned_buffer) {
3678 Alert::error(_("Export Error"),
3679 _("Error cloning the Buffer."));
3682 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3687 setPreviewFuture(f);
3688 last_export_format = used_buffer->params().bufferFormat();
3691 // We are asynchronous, so we don't know here anything about the success
3694 Buffer::ExportStatus status;
3696 status = (used_buffer->*syncFunc)(format, false);
3697 } else if (previewFunc) {
3698 status = (used_buffer->*previewFunc)(format);
3701 handleExportStatus(gv_, status, format);
3703 return (status == Buffer::ExportSuccess
3704 || status == Buffer::PreviewSuccess);
3708 Buffer::ExportStatus status;
3710 status = (used_buffer->*syncFunc)(format, true);
3711 } else if (previewFunc) {
3712 status = (used_buffer->*previewFunc)(format);
3715 handleExportStatus(gv_, status, format);
3717 return (status == Buffer::ExportSuccess
3718 || status == Buffer::PreviewSuccess);
3722 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3724 BufferView * bv = currentBufferView();
3725 LASSERT(bv, return);
3727 // Let the current BufferView dispatch its own actions.
3728 bv->dispatch(cmd, dr);
3729 if (dr.dispatched()) {
3730 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3731 updateDialog("document", "");
3735 // Try with the document BufferView dispatch if any.
3736 BufferView * doc_bv = documentBufferView();
3737 if (doc_bv && doc_bv != bv) {
3738 doc_bv->dispatch(cmd, dr);
3739 if (dr.dispatched()) {
3740 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3741 updateDialog("document", "");
3746 // Then let the current Cursor dispatch its own actions.
3747 bv->cursor().dispatch(cmd);
3749 // update completion. We do it here and not in
3750 // processKeySym to avoid another redraw just for a
3751 // changed inline completion
3752 if (cmd.origin() == FuncRequest::KEYBOARD) {
3753 if (cmd.action() == LFUN_SELF_INSERT
3754 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3755 updateCompletion(bv->cursor(), true, true);
3756 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3757 updateCompletion(bv->cursor(), false, true);
3759 updateCompletion(bv->cursor(), false, false);
3762 dr = bv->cursor().result();
3766 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3768 BufferView * bv = currentBufferView();
3769 // By default we won't need any update.
3770 dr.screenUpdate(Update::None);
3771 // assume cmd will be dispatched
3772 dr.dispatched(true);
3774 Buffer * doc_buffer = documentBufferView()
3775 ? &(documentBufferView()->buffer()) : 0;
3777 if (cmd.origin() == FuncRequest::TOC) {
3778 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3779 // FIXME: do we need to pass a DispatchResult object here?
3780 toc->doDispatch(bv->cursor(), cmd);
3784 string const argument = to_utf8(cmd.argument());
3786 switch(cmd.action()) {
3787 case LFUN_BUFFER_CHILD_OPEN:
3788 openChildDocument(to_utf8(cmd.argument()));
3791 case LFUN_BUFFER_IMPORT:
3792 importDocument(to_utf8(cmd.argument()));
3795 case LFUN_MASTER_BUFFER_EXPORT:
3797 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3799 case LFUN_BUFFER_EXPORT: {
3802 // GCC only sees strfwd.h when building merged
3803 if (::lyx::operator==(cmd.argument(), "custom")) {
3804 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3805 // so the following test should not be needed.
3806 // In principle, we could try to switch to such a view...
3807 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3808 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3812 string const dest = cmd.getArg(1);
3813 FileName target_dir;
3814 if (!dest.empty() && FileName::isAbsolute(dest))
3815 target_dir = FileName(support::onlyPath(dest));
3817 target_dir = doc_buffer->fileName().onlyPath();
3819 string const format = (argument.empty() || argument == "default") ?
3820 doc_buffer->params().getDefaultOutputFormat() : argument;
3822 if ((dest.empty() && doc_buffer->isUnnamed())
3823 || !target_dir.isDirWritable()) {
3824 exportBufferAs(*doc_buffer, from_utf8(format));
3827 /* TODO/Review: Is it a problem to also export the children?
3828 See the update_unincluded flag */
3829 d.asyncBufferProcessing(format,
3832 &GuiViewPrivate::exportAndDestroy,
3834 0, cmd.allowAsync());
3835 // TODO Inform user about success
3839 case LFUN_BUFFER_EXPORT_AS: {
3840 LASSERT(doc_buffer, break);
3841 docstring f = cmd.argument();
3843 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3844 exportBufferAs(*doc_buffer, f);
3848 case LFUN_BUFFER_UPDATE: {
3849 d.asyncBufferProcessing(argument,
3852 &GuiViewPrivate::compileAndDestroy,
3854 0, cmd.allowAsync());
3857 case LFUN_BUFFER_VIEW: {
3858 d.asyncBufferProcessing(argument,
3860 _("Previewing ..."),
3861 &GuiViewPrivate::previewAndDestroy,
3863 &Buffer::preview, cmd.allowAsync());
3866 case LFUN_MASTER_BUFFER_UPDATE: {
3867 d.asyncBufferProcessing(argument,
3868 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3870 &GuiViewPrivate::compileAndDestroy,
3872 0, cmd.allowAsync());
3875 case LFUN_MASTER_BUFFER_VIEW: {
3876 d.asyncBufferProcessing(argument,
3877 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3879 &GuiViewPrivate::previewAndDestroy,
3880 0, &Buffer::preview, cmd.allowAsync());
3883 case LFUN_EXPORT_CANCEL: {
3884 Systemcall::killscript();
3887 case LFUN_BUFFER_SWITCH: {
3888 string const file_name = to_utf8(cmd.argument());
3889 if (!FileName::isAbsolute(file_name)) {
3891 dr.setMessage(_("Absolute filename expected."));
3895 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3898 dr.setMessage(_("Document not loaded"));
3902 // Do we open or switch to the buffer in this view ?
3903 if (workArea(*buffer)
3904 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3909 // Look for the buffer in other views
3910 QList<int> const ids = guiApp->viewIds();
3912 for (; i != ids.size(); ++i) {
3913 GuiView & gv = guiApp->view(ids[i]);
3914 if (gv.workArea(*buffer)) {
3916 gv.activateWindow();
3918 gv.setBuffer(buffer);
3923 // If necessary, open a new window as a last resort
3924 if (i == ids.size()) {
3925 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
3931 case LFUN_BUFFER_NEXT:
3932 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
3935 case LFUN_BUFFER_MOVE_NEXT:
3936 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
3939 case LFUN_BUFFER_PREVIOUS:
3940 gotoNextOrPreviousBuffer(PREVBUFFER, false);
3943 case LFUN_BUFFER_MOVE_PREVIOUS:
3944 gotoNextOrPreviousBuffer(PREVBUFFER, true);
3947 case LFUN_BUFFER_CHKTEX:
3948 LASSERT(doc_buffer, break);
3949 doc_buffer->runChktex();
3952 case LFUN_COMMAND_EXECUTE: {
3953 command_execute_ = true;
3954 minibuffer_focus_ = true;
3957 case LFUN_DROP_LAYOUTS_CHOICE:
3958 d.layout_->showPopup();
3961 case LFUN_MENU_OPEN:
3962 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
3963 menu->exec(QCursor::pos());
3966 case LFUN_FILE_INSERT:
3967 insertLyXFile(cmd.argument());
3970 case LFUN_FILE_INSERT_PLAINTEXT:
3971 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
3972 string const fname = to_utf8(cmd.argument());
3973 if (!fname.empty() && !FileName::isAbsolute(fname)) {
3974 dr.setMessage(_("Absolute filename expected."));
3978 FileName filename(fname);
3979 if (fname.empty()) {
3980 FileDialog dlg(qt_("Select file to insert"));
3982 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
3983 QStringList(qt_("All Files (*)")));
3985 if (result.first == FileDialog::Later || result.second.isEmpty()) {
3986 dr.setMessage(_("Canceled."));
3990 filename.set(fromqstr(result.second));
3994 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
3995 bv->dispatch(new_cmd, dr);
4000 case LFUN_BUFFER_RELOAD: {
4001 LASSERT(doc_buffer, break);
4004 bool drop = (cmd.argument() == "dump");
4007 if (!drop && !doc_buffer->isClean()) {
4008 docstring const file =
4009 makeDisplayPath(doc_buffer->absFileName(), 20);
4010 if (doc_buffer->notifiesExternalModification()) {
4011 docstring text = _("The current version will be lost. "
4012 "Are you sure you want to load the version on disk "
4013 "of the document %1$s?");
4014 ret = Alert::prompt(_("Reload saved document?"),
4015 bformat(text, file), 1, 1,
4016 _("&Reload"), _("&Cancel"));
4018 docstring text = _("Any changes will be lost. "
4019 "Are you sure you want to revert to the saved version "
4020 "of the document %1$s?");
4021 ret = Alert::prompt(_("Revert to saved document?"),
4022 bformat(text, file), 1, 1,
4023 _("&Revert"), _("&Cancel"));
4028 doc_buffer->markClean();
4029 reloadBuffer(*doc_buffer);
4030 dr.forceBufferUpdate();
4035 case LFUN_BUFFER_WRITE:
4036 LASSERT(doc_buffer, break);
4037 saveBuffer(*doc_buffer);
4040 case LFUN_BUFFER_WRITE_AS:
4041 LASSERT(doc_buffer, break);
4042 renameBuffer(*doc_buffer, cmd.argument());
4045 case LFUN_BUFFER_WRITE_ALL: {
4046 Buffer * first = theBufferList().first();
4049 message(_("Saving all documents..."));
4050 // We cannot use a for loop as the buffer list cycles.
4053 if (!b->isClean()) {
4055 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4057 b = theBufferList().next(b);
4058 } while (b != first);
4059 dr.setMessage(_("All documents saved."));
4063 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4064 LASSERT(doc_buffer, break);
4065 doc_buffer->clearExternalModification();
4068 case LFUN_BUFFER_CLOSE:
4072 case LFUN_BUFFER_CLOSE_ALL:
4076 case LFUN_DEVEL_MODE_TOGGLE:
4077 devel_mode_ = !devel_mode_;
4079 dr.setMessage(_("Developer mode is now enabled."));
4081 dr.setMessage(_("Developer mode is now disabled."));
4084 case LFUN_TOOLBAR_TOGGLE: {
4085 string const name = cmd.getArg(0);
4086 if (GuiToolbar * t = toolbar(name))
4091 case LFUN_TOOLBAR_MOVABLE: {
4092 string const name = cmd.getArg(0);
4094 // toggle (all) toolbars movablility
4095 toolbarsMovable_ = !toolbarsMovable_;
4096 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4097 GuiToolbar * tb = toolbar(ti.name);
4098 if (tb && tb->isMovable() != toolbarsMovable_)
4099 // toggle toolbar movablity if it does not fit lock
4100 // (all) toolbars positions state silent = true, since
4101 // status bar notifications are slow
4104 if (toolbarsMovable_)
4105 dr.setMessage(_("Toolbars unlocked."));
4107 dr.setMessage(_("Toolbars locked."));
4108 } else if (GuiToolbar * t = toolbar(name)) {
4109 // toggle current toolbar movablity
4111 // update lock (all) toolbars positions
4112 updateLockToolbars();
4117 case LFUN_ICON_SIZE: {
4118 QSize size = d.iconSize(cmd.argument());
4120 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4121 size.width(), size.height()));
4125 case LFUN_DIALOG_UPDATE: {
4126 string const name = to_utf8(cmd.argument());
4127 if (name == "prefs" || name == "document")
4128 updateDialog(name, string());
4129 else if (name == "paragraph")
4130 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4131 else if (currentBufferView()) {
4132 Inset * inset = currentBufferView()->editedInset(name);
4133 // Can only update a dialog connected to an existing inset
4135 // FIXME: get rid of this indirection; GuiView ask the inset
4136 // if he is kind enough to update itself...
4137 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4138 //FIXME: pass DispatchResult here?
4139 inset->dispatch(currentBufferView()->cursor(), fr);
4145 case LFUN_DIALOG_TOGGLE: {
4146 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4147 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4148 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4152 case LFUN_DIALOG_DISCONNECT_INSET:
4153 disconnectDialog(to_utf8(cmd.argument()));
4156 case LFUN_DIALOG_HIDE: {
4157 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4161 case LFUN_DIALOG_SHOW: {
4162 string const name = cmd.getArg(0);
4163 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4165 if (name == "latexlog") {
4166 // gettatus checks that
4167 LATTEST(doc_buffer);
4168 Buffer::LogType type;
4169 string const logfile = doc_buffer->logName(&type);
4171 case Buffer::latexlog:
4174 case Buffer::buildlog:
4175 sdata = "literate ";
4178 sdata += Lexer::quoteString(logfile);
4179 showDialog("log", sdata);
4180 } else if (name == "vclog") {
4181 // getStatus checks that
4182 LATTEST(doc_buffer);
4183 string const sdata2 = "vc " +
4184 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4185 showDialog("log", sdata2);
4186 } else if (name == "symbols") {
4187 sdata = bv->cursor().getEncoding()->name();
4189 showDialog("symbols", sdata);
4191 } else if (name == "prefs" && isFullScreen()) {
4192 lfunUiToggle("fullscreen");
4193 showDialog("prefs", sdata);
4195 showDialog(name, sdata);
4200 dr.setMessage(cmd.argument());
4203 case LFUN_UI_TOGGLE: {
4204 string arg = cmd.getArg(0);
4205 if (!lfunUiToggle(arg)) {
4206 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4207 dr.setMessage(bformat(msg, from_utf8(arg)));
4209 // Make sure the keyboard focus stays in the work area.
4214 case LFUN_VIEW_SPLIT: {
4215 LASSERT(doc_buffer, break);
4216 string const orientation = cmd.getArg(0);
4217 d.splitter_->setOrientation(orientation == "vertical"
4218 ? Qt::Vertical : Qt::Horizontal);
4219 TabWorkArea * twa = addTabWorkArea();
4220 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4221 setCurrentWorkArea(wa);
4224 case LFUN_TAB_GROUP_CLOSE:
4225 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4226 closeTabWorkArea(twa);
4227 d.current_work_area_ = 0;
4228 twa = d.currentTabWorkArea();
4229 // Switch to the next GuiWorkArea in the found TabWorkArea.
4231 // Make sure the work area is up to date.
4232 setCurrentWorkArea(twa->currentWorkArea());
4234 setCurrentWorkArea(0);
4239 case LFUN_VIEW_CLOSE:
4240 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4241 closeWorkArea(twa->currentWorkArea());
4242 d.current_work_area_ = 0;
4243 twa = d.currentTabWorkArea();
4244 // Switch to the next GuiWorkArea in the found TabWorkArea.
4246 // Make sure the work area is up to date.
4247 setCurrentWorkArea(twa->currentWorkArea());
4249 setCurrentWorkArea(0);
4254 case LFUN_COMPLETION_INLINE:
4255 if (d.current_work_area_)
4256 d.current_work_area_->completer().showInline();
4259 case LFUN_COMPLETION_POPUP:
4260 if (d.current_work_area_)
4261 d.current_work_area_->completer().showPopup();
4266 if (d.current_work_area_)
4267 d.current_work_area_->completer().tab();
4270 case LFUN_COMPLETION_CANCEL:
4271 if (d.current_work_area_) {
4272 if (d.current_work_area_->completer().popupVisible())
4273 d.current_work_area_->completer().hidePopup();
4275 d.current_work_area_->completer().hideInline();
4279 case LFUN_COMPLETION_ACCEPT:
4280 if (d.current_work_area_)
4281 d.current_work_area_->completer().activate();
4284 case LFUN_BUFFER_ZOOM_IN:
4285 case LFUN_BUFFER_ZOOM_OUT:
4286 case LFUN_BUFFER_ZOOM: {
4287 if (cmd.argument().empty()) {
4288 if (cmd.action() == LFUN_BUFFER_ZOOM)
4290 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4295 if (cmd.action() == LFUN_BUFFER_ZOOM)
4296 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4297 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4298 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4300 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4303 // Actual zoom value: default zoom + fractional extra value
4304 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4305 if (zoom < static_cast<int>(zoom_min_))
4308 lyxrc.currentZoom = zoom;
4310 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4311 lyxrc.currentZoom, lyxrc.defaultZoom));
4313 guiApp->fontLoader().update();
4314 dr.screenUpdate(Update::Force | Update::FitCursor);
4318 case LFUN_VC_REGISTER:
4319 case LFUN_VC_RENAME:
4321 case LFUN_VC_CHECK_IN:
4322 case LFUN_VC_CHECK_OUT:
4323 case LFUN_VC_REPO_UPDATE:
4324 case LFUN_VC_LOCKING_TOGGLE:
4325 case LFUN_VC_REVERT:
4326 case LFUN_VC_UNDO_LAST:
4327 case LFUN_VC_COMMAND:
4328 case LFUN_VC_COMPARE:
4329 dispatchVC(cmd, dr);
4332 case LFUN_SERVER_GOTO_FILE_ROW:
4333 if(goToFileRow(to_utf8(cmd.argument())))
4334 dr.screenUpdate(Update::Force | Update::FitCursor);
4337 case LFUN_LYX_ACTIVATE:
4341 case LFUN_FORWARD_SEARCH: {
4342 // it seems safe to assume we have a document buffer, since
4343 // getStatus wants one.
4344 LATTEST(doc_buffer);
4345 Buffer const * doc_master = doc_buffer->masterBuffer();
4346 FileName const path(doc_master->temppath());
4347 string const texname = doc_master->isChild(doc_buffer)
4348 ? DocFileName(changeExtension(
4349 doc_buffer->absFileName(),
4350 "tex")).mangledFileName()
4351 : doc_buffer->latexName();
4352 string const fulltexname =
4353 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4354 string const mastername =
4355 removeExtension(doc_master->latexName());
4356 FileName const dviname(addName(path.absFileName(),
4357 addExtension(mastername, "dvi")));
4358 FileName const pdfname(addName(path.absFileName(),
4359 addExtension(mastername, "pdf")));
4360 bool const have_dvi = dviname.exists();
4361 bool const have_pdf = pdfname.exists();
4362 if (!have_dvi && !have_pdf) {
4363 dr.setMessage(_("Please, preview the document first."));
4366 string outname = dviname.onlyFileName();
4367 string command = lyxrc.forward_search_dvi;
4368 if (!have_dvi || (have_pdf &&
4369 pdfname.lastModified() > dviname.lastModified())) {
4370 outname = pdfname.onlyFileName();
4371 command = lyxrc.forward_search_pdf;
4374 DocIterator cur = bv->cursor();
4375 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4376 LYXERR(Debug::ACTION, "Forward search: row:" << row
4378 if (row == -1 || command.empty()) {
4379 dr.setMessage(_("Couldn't proceed."));
4382 string texrow = convert<string>(row);
4384 command = subst(command, "$$n", texrow);
4385 command = subst(command, "$$f", fulltexname);
4386 command = subst(command, "$$t", texname);
4387 command = subst(command, "$$o", outname);
4389 PathChanger p(path);
4391 one.startscript(Systemcall::DontWait, command);
4395 case LFUN_SPELLING_CONTINUOUSLY:
4396 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4397 dr.screenUpdate(Update::Force);
4401 // The LFUN must be for one of BufferView, Buffer or Cursor;
4403 dispatchToBufferView(cmd, dr);
4407 // Part of automatic menu appearance feature.
4408 if (isFullScreen()) {
4409 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4413 // Need to update bv because many LFUNs here might have destroyed it
4414 bv = currentBufferView();
4416 // Clear non-empty selections
4417 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4419 Cursor & cur = bv->cursor();
4420 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4421 cur.clearSelection();
4427 bool GuiView::lfunUiToggle(string const & ui_component)
4429 if (ui_component == "scrollbar") {
4430 // hide() is of no help
4431 if (d.current_work_area_->verticalScrollBarPolicy() ==
4432 Qt::ScrollBarAlwaysOff)
4434 d.current_work_area_->setVerticalScrollBarPolicy(
4435 Qt::ScrollBarAsNeeded);
4437 d.current_work_area_->setVerticalScrollBarPolicy(
4438 Qt::ScrollBarAlwaysOff);
4439 } else if (ui_component == "statusbar") {
4440 statusBar()->setVisible(!statusBar()->isVisible());
4441 } else if (ui_component == "menubar") {
4442 menuBar()->setVisible(!menuBar()->isVisible());
4444 if (ui_component == "frame") {
4446 getContentsMargins(&l, &t, &r, &b);
4447 //are the frames in default state?
4448 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4450 setContentsMargins(-2, -2, -2, -2);
4452 setContentsMargins(0, 0, 0, 0);
4455 if (ui_component == "fullscreen") {
4463 void GuiView::toggleFullScreen()
4465 if (isFullScreen()) {
4466 for (int i = 0; i != d.splitter_->count(); ++i)
4467 d.tabWorkArea(i)->setFullScreen(false);
4468 setContentsMargins(0, 0, 0, 0);
4469 setWindowState(windowState() ^ Qt::WindowFullScreen);
4472 statusBar()->show();
4475 hideDialogs("prefs", 0);
4476 for (int i = 0; i != d.splitter_->count(); ++i)
4477 d.tabWorkArea(i)->setFullScreen(true);
4478 setContentsMargins(-2, -2, -2, -2);
4480 setWindowState(windowState() ^ Qt::WindowFullScreen);
4481 if (lyxrc.full_screen_statusbar)
4482 statusBar()->hide();
4483 if (lyxrc.full_screen_menubar)
4485 if (lyxrc.full_screen_toolbars) {
4486 ToolbarMap::iterator end = d.toolbars_.end();
4487 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4492 // give dialogs like the TOC a chance to adapt
4497 Buffer const * GuiView::updateInset(Inset const * inset)
4502 Buffer const * inset_buffer = &(inset->buffer());
4504 for (int i = 0; i != d.splitter_->count(); ++i) {
4505 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4508 Buffer const * buffer = &(wa->bufferView().buffer());
4509 if (inset_buffer == buffer)
4510 wa->scheduleRedraw(true);
4512 return inset_buffer;
4516 void GuiView::restartCaret()
4518 /* When we move around, or type, it's nice to be able to see
4519 * the caret immediately after the keypress.
4521 if (d.current_work_area_)
4522 d.current_work_area_->startBlinkingCaret();
4524 // Take this occasion to update the other GUI elements.
4530 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4532 if (d.current_work_area_)
4533 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4538 // This list should be kept in sync with the list of insets in
4539 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4540 // dialog should have the same name as the inset.
4541 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4542 // docs in LyXAction.cpp.
4544 char const * const dialognames[] = {
4546 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4547 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4548 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4549 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4550 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4551 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4552 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4553 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4555 char const * const * const end_dialognames =
4556 dialognames + (sizeof(dialognames) / sizeof(char *));
4560 cmpCStr(char const * name) : name_(name) {}
4561 bool operator()(char const * other) {
4562 return strcmp(other, name_) == 0;
4569 bool isValidName(string const & name)
4571 return find_if(dialognames, end_dialognames,
4572 cmpCStr(name.c_str())) != end_dialognames;
4578 void GuiView::resetDialogs()
4580 // Make sure that no LFUN uses any GuiView.
4581 guiApp->setCurrentView(0);
4585 constructToolbars();
4586 guiApp->menus().fillMenuBar(menuBar(), this, false);
4587 d.layout_->updateContents(true);
4588 // Now update controls with current buffer.
4589 guiApp->setCurrentView(this);
4595 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4597 if (!isValidName(name))
4600 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4602 if (it != d.dialogs_.end()) {
4604 it->second->hideView();
4605 return it->second.get();
4608 Dialog * dialog = build(name);
4609 d.dialogs_[name].reset(dialog);
4610 if (lyxrc.allow_geometry_session)
4611 dialog->restoreSession();
4618 void GuiView::showDialog(string const & name, string const & sdata,
4621 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4625 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4631 const string name = fromqstr(qname);
4632 const string sdata = fromqstr(qdata);
4636 Dialog * dialog = findOrBuild(name, false);
4638 bool const visible = dialog->isVisibleView();
4639 dialog->showData(sdata);
4640 if (currentBufferView())
4641 currentBufferView()->editInset(name, inset);
4642 // We only set the focus to the new dialog if it was not yet
4643 // visible in order not to change the existing previous behaviour
4645 // activateWindow is needed for floating dockviews
4646 dialog->asQWidget()->raise();
4647 dialog->asQWidget()->activateWindow();
4648 dialog->asQWidget()->setFocus();
4652 catch (ExceptionMessage const & ex) {
4660 bool GuiView::isDialogVisible(string const & name) const
4662 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4663 if (it == d.dialogs_.end())
4665 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4669 void GuiView::hideDialog(string const & name, Inset * inset)
4671 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4672 if (it == d.dialogs_.end())
4676 if (!currentBufferView())
4678 if (inset != currentBufferView()->editedInset(name))
4682 Dialog * const dialog = it->second.get();
4683 if (dialog->isVisibleView())
4685 if (currentBufferView())
4686 currentBufferView()->editInset(name, 0);
4690 void GuiView::disconnectDialog(string const & name)
4692 if (!isValidName(name))
4694 if (currentBufferView())
4695 currentBufferView()->editInset(name, 0);
4699 void GuiView::hideAll() const
4701 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4702 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4704 for(; it != end; ++it)
4705 it->second->hideView();
4709 void GuiView::updateDialogs()
4711 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4712 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4714 for(; it != end; ++it) {
4715 Dialog * dialog = it->second.get();
4717 if (dialog->needBufferOpen() && !documentBufferView())
4718 hideDialog(fromqstr(dialog->name()), 0);
4719 else if (dialog->isVisibleView())
4720 dialog->checkStatus();
4727 Dialog * createDialog(GuiView & lv, string const & name);
4729 // will be replaced by a proper factory...
4730 Dialog * createGuiAbout(GuiView & lv);
4731 Dialog * createGuiBibtex(GuiView & lv);
4732 Dialog * createGuiChanges(GuiView & lv);
4733 Dialog * createGuiCharacter(GuiView & lv);
4734 Dialog * createGuiCitation(GuiView & lv);
4735 Dialog * createGuiCompare(GuiView & lv);
4736 Dialog * createGuiCompareHistory(GuiView & lv);
4737 Dialog * createGuiDelimiter(GuiView & lv);
4738 Dialog * createGuiDocument(GuiView & lv);
4739 Dialog * createGuiErrorList(GuiView & lv);
4740 Dialog * createGuiExternal(GuiView & lv);
4741 Dialog * createGuiGraphics(GuiView & lv);
4742 Dialog * createGuiInclude(GuiView & lv);
4743 Dialog * createGuiIndex(GuiView & lv);
4744 Dialog * createGuiListings(GuiView & lv);
4745 Dialog * createGuiLog(GuiView & lv);
4746 Dialog * createGuiLyXFiles(GuiView & lv);
4747 Dialog * createGuiMathMatrix(GuiView & lv);
4748 Dialog * createGuiNote(GuiView & lv);
4749 Dialog * createGuiParagraph(GuiView & lv);
4750 Dialog * createGuiPhantom(GuiView & lv);
4751 Dialog * createGuiPreferences(GuiView & lv);
4752 Dialog * createGuiPrint(GuiView & lv);
4753 Dialog * createGuiPrintindex(GuiView & lv);
4754 Dialog * createGuiRef(GuiView & lv);
4755 Dialog * createGuiSearch(GuiView & lv);
4756 Dialog * createGuiSearchAdv(GuiView & lv);
4757 Dialog * createGuiSendTo(GuiView & lv);
4758 Dialog * createGuiShowFile(GuiView & lv);
4759 Dialog * createGuiSpellchecker(GuiView & lv);
4760 Dialog * createGuiSymbols(GuiView & lv);
4761 Dialog * createGuiTabularCreate(GuiView & lv);
4762 Dialog * createGuiTexInfo(GuiView & lv);
4763 Dialog * createGuiToc(GuiView & lv);
4764 Dialog * createGuiThesaurus(GuiView & lv);
4765 Dialog * createGuiViewSource(GuiView & lv);
4766 Dialog * createGuiWrap(GuiView & lv);
4767 Dialog * createGuiProgressView(GuiView & lv);
4771 Dialog * GuiView::build(string const & name)
4773 LASSERT(isValidName(name), return 0);
4775 Dialog * dialog = createDialog(*this, name);
4779 if (name == "aboutlyx")
4780 return createGuiAbout(*this);
4781 if (name == "bibtex")
4782 return createGuiBibtex(*this);
4783 if (name == "changes")
4784 return createGuiChanges(*this);
4785 if (name == "character")
4786 return createGuiCharacter(*this);
4787 if (name == "citation")
4788 return createGuiCitation(*this);
4789 if (name == "compare")
4790 return createGuiCompare(*this);
4791 if (name == "comparehistory")
4792 return createGuiCompareHistory(*this);
4793 if (name == "document")
4794 return createGuiDocument(*this);
4795 if (name == "errorlist")
4796 return createGuiErrorList(*this);
4797 if (name == "external")
4798 return createGuiExternal(*this);
4800 return createGuiShowFile(*this);
4801 if (name == "findreplace")
4802 return createGuiSearch(*this);
4803 if (name == "findreplaceadv")
4804 return createGuiSearchAdv(*this);
4805 if (name == "graphics")
4806 return createGuiGraphics(*this);
4807 if (name == "include")
4808 return createGuiInclude(*this);
4809 if (name == "index")
4810 return createGuiIndex(*this);
4811 if (name == "index_print")
4812 return createGuiPrintindex(*this);
4813 if (name == "listings")
4814 return createGuiListings(*this);
4816 return createGuiLog(*this);
4817 if (name == "lyxfiles")
4818 return createGuiLyXFiles(*this);
4819 if (name == "mathdelimiter")
4820 return createGuiDelimiter(*this);
4821 if (name == "mathmatrix")
4822 return createGuiMathMatrix(*this);
4824 return createGuiNote(*this);
4825 if (name == "paragraph")
4826 return createGuiParagraph(*this);
4827 if (name == "phantom")
4828 return createGuiPhantom(*this);
4829 if (name == "prefs")
4830 return createGuiPreferences(*this);
4832 return createGuiRef(*this);
4833 if (name == "sendto")
4834 return createGuiSendTo(*this);
4835 if (name == "spellchecker")
4836 return createGuiSpellchecker(*this);
4837 if (name == "symbols")
4838 return createGuiSymbols(*this);
4839 if (name == "tabularcreate")
4840 return createGuiTabularCreate(*this);
4841 if (name == "texinfo")
4842 return createGuiTexInfo(*this);
4843 if (name == "thesaurus")
4844 return createGuiThesaurus(*this);
4846 return createGuiToc(*this);
4847 if (name == "view-source")
4848 return createGuiViewSource(*this);
4850 return createGuiWrap(*this);
4851 if (name == "progress")
4852 return createGuiProgressView(*this);
4858 SEMenu::SEMenu(QWidget * parent)
4860 QAction * action = addAction(qt_("Disable Shell Escape"));
4861 connect(action, SIGNAL(triggered()),
4862 parent, SLOT(disableShellEscape()));
4865 } // namespace frontend
4868 #include "moc_GuiView.cpp"