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 "LayoutFile.h"
58 #include "LyXAction.h"
62 #include "Paragraph.h"
63 #include "SpellChecker.h"
66 #include "TextClass.h"
71 #include "support/convert.h"
72 #include "support/debug.h"
73 #include "support/ExceptionMessage.h"
74 #include "support/FileName.h"
75 #include "support/filetools.h"
76 #include "support/gettext.h"
77 #include "support/filetools.h"
78 #include "support/ForkedCalls.h"
79 #include "support/lassert.h"
80 #include "support/lstrings.h"
81 #include "support/os.h"
82 #include "support/Package.h"
83 #include "support/PathChanger.h"
84 #include "support/Systemcall.h"
85 #include "support/Timeout.h"
86 #include "support/ProgressInterface.h"
89 #include <QApplication>
90 #include <QCloseEvent>
92 #include <QDesktopWidget>
93 #include <QDragEnterEvent>
96 #include <QFutureWatcher>
106 #include <QPushButton>
107 #include <QScrollBar>
109 #include <QShowEvent>
111 #include <QStackedWidget>
112 #include <QStatusBar>
113 #include <QSvgRenderer>
114 #include <QtConcurrentRun>
122 // sync with GuiAlert.cpp
123 #define EXPORT_in_THREAD 1
126 #include "support/bind.h"
130 #ifdef HAVE_SYS_TIME_H
131 # include <sys/time.h>
139 using namespace lyx::support;
143 using support::addExtension;
144 using support::changeExtension;
145 using support::removeExtension;
151 class BackgroundWidget : public QWidget
154 BackgroundWidget(int width, int height)
155 : width_(width), height_(height)
157 LYXERR(Debug::GUI, "show banner: " << lyxrc.show_banner);
158 if (!lyxrc.show_banner)
160 /// The text to be written on top of the pixmap
161 QString const htext = qt_("The Document\nProcessor[[welcome banner]]");
162 QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]");
163 /// The text to be written on top of the pixmap
164 QString const text = lyx_version ?
165 qt_("version ") + lyx_version : qt_("unknown version");
166 #if QT_VERSION >= 0x050000
167 QString imagedir = "images/";
168 FileName fname = imageLibFileSearch(imagedir, "banner", "svgz");
169 QSvgRenderer svgRenderer(toqstr(fname.absFileName()));
170 if (svgRenderer.isValid()) {
171 splash_ = QPixmap(splashSize());
172 QPainter painter(&splash_);
173 svgRenderer.render(&painter);
174 splash_.setDevicePixelRatio(pixelRatio());
176 splash_ = getPixmap("images/", "banner", "png");
179 splash_ = getPixmap("images/", "banner", "svgz,png");
182 QPainter pain(&splash_);
183 pain.setPen(QColor(0, 0, 0));
184 qreal const fsize = fontSize();
187 qreal locscale = htextsize.toFloat(&ok);
190 QPointF const position = textPosition(false);
191 QPointF const hposition = textPosition(true);
192 QRectF const hrect(hposition, splashSize());
194 "widget pixel ratio: " << pixelRatio() <<
195 " splash pixel ratio: " << splashPixelRatio() <<
196 " version text size,position: " << fsize << "@" << position.x() << "+" << position.y());
198 // The font used to display the version info
199 font.setStyleHint(QFont::SansSerif);
200 font.setWeight(QFont::Bold);
201 font.setPointSizeF(fsize);
203 pain.drawText(position, text);
204 // The font used to display the version info
205 font.setStyleHint(QFont::SansSerif);
206 font.setWeight(QFont::Normal);
207 font.setPointSizeF(hfsize);
208 // Check how long the logo gets with the current font
209 // and adapt if the font is running wider than what
211 QFontMetrics fm(font);
212 // Split the title into lines to measure the longest line
213 // in the current l7n.
214 QStringList titlesegs = htext.split('\n');
216 int hline = fm.height();
217 QStringList::const_iterator sit;
218 for (sit = titlesegs.constBegin(); sit != titlesegs.constEnd(); ++sit) {
219 if (fm.width(*sit) > wline)
220 wline = fm.width(*sit);
222 // The longest line in the reference font (for English)
223 // is 180. Calculate scale factor from that.
224 double const wscale = wline > 0 ? (180.0 / wline) : 1;
225 // Now do the same for the height (necessary for condensed fonts)
226 double const hscale = (34.0 / hline);
227 // take the lower of the two scale factors.
228 double const scale = min(wscale, hscale);
229 // Now rescale. Also consider l7n's offset factor.
230 font.setPointSizeF(hfsize * scale * locscale);
233 pain.drawText(hrect, Qt::AlignLeft, htext);
234 setFocusPolicy(Qt::StrongFocus);
237 void paintEvent(QPaintEvent *)
239 int const w = width_;
240 int const h = height_;
241 int const x = (width() - w) / 2;
242 int const y = (height() - h) / 2;
244 "widget pixel ratio: " << pixelRatio() <<
245 " splash pixel ratio: " << splashPixelRatio() <<
246 " paint pixmap: " << w << "x" << h << "@" << x << "+" << y);
248 pain.drawPixmap(x, y, w, h, splash_);
251 void keyPressEvent(QKeyEvent * ev)
254 setKeySymbol(&sym, ev);
256 guiApp->processKeySym(sym, q_key_state(ev->modifiers()));
268 /// Current ratio between physical pixels and device-independent pixels
269 double pixelRatio() const {
270 #if QT_VERSION >= 0x050000
271 return qt_scale_factor * devicePixelRatio();
277 qreal fontSize() const {
278 return toqstr(lyxrc.font_sizes[FONT_SIZE_NORMAL]).toDouble();
281 QPointF textPosition(bool const heading) const {
282 return heading ? QPointF(width_/2 - 18, height_/2 - 45)
283 : QPointF(width_/2 - 18, height_/2 + 45);
286 QSize splashSize() const {
288 static_cast<unsigned int>(width_ * pixelRatio()),
289 static_cast<unsigned int>(height_ * pixelRatio()));
292 /// Ratio between physical pixels and device-independent pixels of splash image
293 double splashPixelRatio() const {
294 #if QT_VERSION >= 0x050000
295 return splash_.devicePixelRatio();
303 /// Toolbar store providing access to individual toolbars by name.
304 typedef map<string, GuiToolbar *> ToolbarMap;
306 typedef shared_ptr<Dialog> DialogPtr;
311 class GuiView::GuiViewPrivate
314 GuiViewPrivate(GuiViewPrivate const &);
315 void operator=(GuiViewPrivate const &);
317 GuiViewPrivate(GuiView * gv)
318 : gv_(gv), current_work_area_(0), current_main_work_area_(0),
319 layout_(0), autosave_timeout_(5000),
322 // hardcode here the platform specific icon size
323 smallIconSize = 16; // scaling problems
324 normalIconSize = 20; // ok, default if iconsize.png is missing
325 bigIconSize = 26; // better for some math icons
326 hugeIconSize = 32; // better for hires displays
329 // if it exists, use width of iconsize.png as normal size
330 QString const dir = toqstr(addPath("images", lyxrc.icon_set));
331 FileName const fn = lyx::libFileSearch(dir, "iconsize.png");
333 QImage image(toqstr(fn.absFileName()));
334 if (image.width() < int(smallIconSize))
335 normalIconSize = smallIconSize;
336 else if (image.width() > int(giantIconSize))
337 normalIconSize = giantIconSize;
339 normalIconSize = image.width();
342 splitter_ = new QSplitter;
343 bg_widget_ = new BackgroundWidget(400, 250);
344 stack_widget_ = new QStackedWidget;
345 stack_widget_->addWidget(bg_widget_);
346 stack_widget_->addWidget(splitter_);
349 // TODO cleanup, remove the singleton, handle multiple Windows?
350 progress_ = ProgressInterface::instance();
351 if (!dynamic_cast<GuiProgress*>(progress_)) {
352 progress_ = new GuiProgress; // TODO who deletes it
353 ProgressInterface::setInstance(progress_);
356 dynamic_cast<GuiProgress*>(progress_),
357 SIGNAL(updateStatusBarMessage(QString const&)),
358 gv, SLOT(updateStatusBarMessage(QString const&)));
360 dynamic_cast<GuiProgress*>(progress_),
361 SIGNAL(clearMessageText()),
362 gv, SLOT(clearMessageText()));
369 delete stack_widget_;
374 stack_widget_->setCurrentWidget(bg_widget_);
375 bg_widget_->setUpdatesEnabled(true);
376 bg_widget_->setFocus();
379 int tabWorkAreaCount()
381 return splitter_->count();
384 TabWorkArea * tabWorkArea(int i)
386 return dynamic_cast<TabWorkArea *>(splitter_->widget(i));
389 TabWorkArea * currentTabWorkArea()
391 int areas = tabWorkAreaCount();
393 // The first TabWorkArea is always the first one, if any.
394 return tabWorkArea(0);
396 for (int i = 0; i != areas; ++i) {
397 TabWorkArea * twa = tabWorkArea(i);
398 if (current_main_work_area_ == twa->currentWorkArea())
402 // None has the focus so we just take the first one.
403 return tabWorkArea(0);
406 int countWorkAreasOf(Buffer & buf)
408 int areas = tabWorkAreaCount();
410 for (int i = 0; i != areas; ++i) {
411 TabWorkArea * twa = tabWorkArea(i);
412 if (twa->workArea(buf))
418 void setPreviewFuture(QFuture<Buffer::ExportStatus> const & f)
420 if (processing_thread_watcher_.isRunning()) {
421 // we prefer to cancel this preview in order to keep a snappy
425 processing_thread_watcher_.setFuture(f);
428 QSize iconSize(docstring const & icon_size)
431 if (icon_size == "small")
432 size = smallIconSize;
433 else if (icon_size == "normal")
434 size = normalIconSize;
435 else if (icon_size == "big")
437 else if (icon_size == "huge")
439 else if (icon_size == "giant")
440 size = giantIconSize;
442 size = icon_size.empty() ? normalIconSize : convert<int>(icon_size);
444 if (size < smallIconSize)
445 size = smallIconSize;
447 return QSize(size, size);
450 QSize iconSize(QString const & icon_size)
452 return iconSize(qstring_to_ucs4(icon_size));
455 string & iconSize(QSize const & qsize)
457 LATTEST(qsize.width() == qsize.height());
459 static string icon_size;
461 unsigned int size = qsize.width();
463 if (size < smallIconSize)
464 size = smallIconSize;
466 if (size == smallIconSize)
468 else if (size == normalIconSize)
469 icon_size = "normal";
470 else if (size == bigIconSize)
472 else if (size == hugeIconSize)
474 else if (size == giantIconSize)
477 icon_size = convert<string>(size);
484 GuiWorkArea * current_work_area_;
485 GuiWorkArea * current_main_work_area_;
486 QSplitter * splitter_;
487 QStackedWidget * stack_widget_;
488 BackgroundWidget * bg_widget_;
490 ToolbarMap toolbars_;
491 ProgressInterface* progress_;
492 /// The main layout box.
494 * \warning Don't Delete! The layout box is actually owned by
495 * whichever toolbar contains it. All the GuiView class needs is a
496 * means of accessing it.
498 * FIXME: replace that with a proper model so that we are not limited
499 * to only one dialog.
504 map<string, DialogPtr> dialogs_;
506 unsigned int smallIconSize;
507 unsigned int normalIconSize;
508 unsigned int bigIconSize;
509 unsigned int hugeIconSize;
510 unsigned int giantIconSize;
512 QTimer statusbar_timer_;
513 /// auto-saving of buffers
514 Timeout autosave_timeout_;
515 /// flag against a race condition due to multiclicks, see bug #1119
519 TocModels toc_models_;
522 QFutureWatcher<docstring> autosave_watcher_;
523 QFutureWatcher<Buffer::ExportStatus> processing_thread_watcher_;
525 string last_export_format;
526 string processing_format;
528 static QSet<Buffer const *> busyBuffers;
529 static Buffer::ExportStatus previewAndDestroy(Buffer const * orig,
530 Buffer * buffer, string const & format);
531 static Buffer::ExportStatus exportAndDestroy(Buffer const * orig,
532 Buffer * buffer, string const & format);
533 static Buffer::ExportStatus compileAndDestroy(Buffer const * orig,
534 Buffer * buffer, string const & format);
535 static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer);
538 static Buffer::ExportStatus runAndDestroy(const T& func,
539 Buffer const * orig, Buffer * buffer, string const & format);
541 // TODO syncFunc/previewFunc: use bind
542 bool asyncBufferProcessing(string const & argument,
543 Buffer const * used_buffer,
544 docstring const & msg,
545 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
546 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
547 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
550 QVector<GuiWorkArea*> guiWorkAreas();
553 QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
556 GuiView::GuiView(int id)
557 : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0),
558 command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true),
561 connect(this, SIGNAL(bufferViewChanged()),
562 this, SLOT(onBufferViewChanged()));
564 // GuiToolbars *must* be initialised before the menu bar.
565 setIconSize(QSize(d.normalIconSize, d.normalIconSize)); // at least on Mac the default is 32 otherwise, which is huge
568 // set ourself as the current view. This is needed for the menu bar
569 // filling, at least for the static special menu item on Mac. Otherwise
570 // they are greyed out.
571 guiApp->setCurrentView(this);
573 // Fill up the menu bar.
574 guiApp->menus().fillMenuBar(menuBar(), this, true);
576 setCentralWidget(d.stack_widget_);
578 // Start autosave timer
579 if (lyxrc.autosave) {
580 // The connection is closed when this is destroyed.
581 d.autosave_timeout_.timeout.connect([this](){ autoSave();});
582 d.autosave_timeout_.setTimeout(lyxrc.autosave * 1000);
583 d.autosave_timeout_.start();
585 connect(&d.statusbar_timer_, SIGNAL(timeout()),
586 this, SLOT(clearMessage()));
588 // We don't want to keep the window in memory if it is closed.
589 setAttribute(Qt::WA_DeleteOnClose, true);
591 #if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC)
592 // QIcon::fromTheme was introduced in Qt 4.6
593 #if (QT_VERSION >= 0x040600)
594 // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac,
595 // since the icon is provided in the application bundle. We use a themed
596 // version when available and use the bundled one as fallback.
597 setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png")));
599 setWindowIcon(getPixmap("images/", "lyx", "svg,png"));
605 // use tabbed dock area for multiple docks
606 // (such as "source" and "messages")
607 setDockOptions(QMainWindow::ForceTabbedDocks);
610 setAcceptDrops(true);
612 // add busy indicator to statusbar
613 QLabel * busylabel = new QLabel(statusBar());
614 statusBar()->addPermanentWidget(busylabel);
615 search_mode mode = theGuiApp()->imageSearchMode();
616 QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName());
617 QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel);
618 busylabel->setMovie(busyanim);
622 connect(&d.processing_thread_watcher_, SIGNAL(started()),
623 busylabel, SLOT(show()));
624 connect(&d.processing_thread_watcher_, SIGNAL(finished()),
625 busylabel, SLOT(hide()));
627 QFontMetrics const fm(statusBar()->fontMetrics());
628 int const iconheight = max(int(d.normalIconSize), fm.height());
629 QSize const iconsize(iconheight, iconheight);
631 QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize);
632 shell_escape_ = new QLabel(statusBar());
633 shell_escape_->setPixmap(shellescape);
634 shell_escape_->setScaledContents(true);
635 shell_escape_->setAlignment(Qt::AlignCenter);
636 shell_escape_->setContextMenuPolicy(Qt::CustomContextMenu);
637 shell_escape_->setToolTip(qt_("WARNING: LaTeX is allowed to execute "
638 "external commands for this document. "
639 "Right click to change."));
640 SEMenu * menu = new SEMenu(this);
641 connect(shell_escape_, SIGNAL(customContextMenuRequested(QPoint)),
642 menu, SLOT(showMenu(QPoint)));
643 shell_escape_->hide();
644 statusBar()->addPermanentWidget(shell_escape_);
646 QPixmap readonly = QIcon(getPixmap("images/", "emblem-readonly", "svgz,png")).pixmap(iconsize);
647 read_only_ = new QLabel(statusBar());
648 read_only_->setPixmap(readonly);
649 read_only_->setScaledContents(true);
650 read_only_->setAlignment(Qt::AlignCenter);
652 statusBar()->addPermanentWidget(read_only_);
654 version_control_ = new QLabel(statusBar());
655 version_control_->setAlignment(Qt::AlignCenter);
656 version_control_->setFrameStyle(QFrame::StyledPanel);
657 version_control_->hide();
658 statusBar()->addPermanentWidget(version_control_);
660 statusBar()->setSizeGripEnabled(true);
663 connect(&d.autosave_watcher_, SIGNAL(finished()), this,
664 SLOT(autoSaveThreadFinished()));
666 connect(&d.processing_thread_watcher_, SIGNAL(started()), this,
667 SLOT(processingThreadStarted()));
668 connect(&d.processing_thread_watcher_, SIGNAL(finished()), this,
669 SLOT(processingThreadFinished()));
671 connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)),
672 SLOT(doShowDialog(QString const &, QString const &, Inset *)));
674 // set custom application bars context menu, e.g. tool bar and menu bar
675 setContextMenuPolicy(Qt::CustomContextMenu);
676 connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
677 SLOT(toolBarPopup(const QPoint &)));
679 // Forbid too small unresizable window because it can happen
680 // with some window manager under X11.
681 setMinimumSize(300, 200);
683 if (lyxrc.allow_geometry_session) {
684 // Now take care of session management.
689 // no session handling, default to a sane size.
690 setGeometry(50, 50, 690, 510);
693 // clear session data if any.
695 settings.remove("views");
705 void GuiView::disableShellEscape()
707 BufferView * bv = documentBufferView();
710 theSession().shellescapeFiles().remove(bv->buffer().absFileName());
711 bv->buffer().params().shell_escape = false;
712 bv->processUpdateFlags(Update::Force);
716 QVector<GuiWorkArea*> GuiView::GuiViewPrivate::guiWorkAreas()
718 QVector<GuiWorkArea*> areas;
719 for (int i = 0; i < tabWorkAreaCount(); i++) {
720 TabWorkArea* ta = tabWorkArea(i);
721 for (int u = 0; u < ta->count(); u++) {
722 areas << ta->workArea(u);
728 static void handleExportStatus(GuiView * view, Buffer::ExportStatus status,
729 string const & format)
731 docstring const fmt = theFormats().prettyName(format);
734 case Buffer::ExportSuccess:
735 msg = bformat(_("Successful export to format: %1$s"), fmt);
737 case Buffer::ExportCancel:
738 msg = _("Document export cancelled.");
740 case Buffer::ExportError:
741 case Buffer::ExportNoPathToFormat:
742 case Buffer::ExportTexPathHasSpaces:
743 case Buffer::ExportConverterError:
744 msg = bformat(_("Error while exporting format: %1$s"), fmt);
746 case Buffer::PreviewSuccess:
747 msg = bformat(_("Successful preview of format: %1$s"), fmt);
749 case Buffer::PreviewError:
750 msg = bformat(_("Error while previewing format: %1$s"), fmt);
752 case Buffer::ExportKilled:
753 msg = bformat(_("Conversion cancelled while previewing format: %1$s"), fmt);
760 void GuiView::processingThreadStarted()
765 void GuiView::processingThreadFinished()
767 QFutureWatcher<Buffer::ExportStatus> const * watcher =
768 static_cast<QFutureWatcher<Buffer::ExportStatus> const *>(sender());
770 Buffer::ExportStatus const status = watcher->result();
771 handleExportStatus(this, status, d.processing_format);
774 BufferView const * const bv = currentBufferView();
775 if (bv && !bv->buffer().errorList("Export").empty()) {
780 bool const error = (status != Buffer::ExportSuccess &&
781 status != Buffer::PreviewSuccess &&
782 status != Buffer::ExportCancel);
784 ErrorList & el = bv->buffer().errorList(d.last_export_format);
785 // at this point, we do not know if buffer-view or
786 // master-buffer-view was called. If there was an export error,
787 // and the current buffer's error log is empty, we guess that
788 // it must be master-buffer-view that was called so we set
790 errors(d.last_export_format, el.empty());
795 void GuiView::autoSaveThreadFinished()
797 QFutureWatcher<docstring> const * watcher =
798 static_cast<QFutureWatcher<docstring> const *>(sender());
799 message(watcher->result());
804 void GuiView::saveLayout() const
807 settings.setValue("zoom_ratio", zoom_ratio_);
808 settings.setValue("devel_mode", devel_mode_);
809 settings.beginGroup("views");
810 settings.beginGroup(QString::number(id_));
811 #if defined(Q_WS_X11) || defined(QPA_XCB)
812 settings.setValue("pos", pos());
813 settings.setValue("size", size());
815 settings.setValue("geometry", saveGeometry());
817 settings.setValue("layout", saveState(0));
818 settings.setValue("icon_size", toqstr(d.iconSize(iconSize())));
822 void GuiView::saveUISettings() const
826 // Save the toolbar private states
827 ToolbarMap::iterator end = d.toolbars_.end();
828 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
829 it->second->saveSession(settings);
830 // Now take care of all other dialogs
831 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
832 for (; it!= d.dialogs_.end(); ++it)
833 it->second->saveSession(settings);
837 bool GuiView::restoreLayout()
840 zoom_ratio_ = settings.value("zoom_ratio", 1.0).toDouble();
841 // Actual zoom value: default zoom + fractional offset
842 int zoom = lyxrc.defaultZoom * zoom_ratio_;
843 if (zoom < static_cast<int>(zoom_min_))
845 lyxrc.currentZoom = zoom;
846 devel_mode_ = settings.value("devel_mode", devel_mode_).toBool();
847 settings.beginGroup("views");
848 settings.beginGroup(QString::number(id_));
849 QString const icon_key = "icon_size";
850 if (!settings.contains(icon_key))
853 //code below is skipped when when ~/.config/LyX is (re)created
854 setIconSize(d.iconSize(settings.value(icon_key).toString()));
856 #if defined(Q_WS_X11) || defined(QPA_XCB)
857 QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint();
858 QSize size = settings.value("size", QSize(690, 510)).toSize();
862 // Work-around for bug #6034: the window ends up in an undetermined
863 // state when trying to restore a maximized window when it is
864 // already maximized.
865 if (!(windowState() & Qt::WindowMaximized))
866 if (!restoreGeometry(settings.value("geometry").toByteArray()))
867 setGeometry(50, 50, 690, 510);
869 // Make sure layout is correctly oriented.
870 setLayoutDirection(qApp->layoutDirection());
872 // Allow the toc and view-source dock widget to be restored if needed.
874 if ((dialog = findOrBuild("toc", true)))
875 // see bug 5082. At least setup title and enabled state.
876 // Visibility will be adjusted by restoreState below.
877 dialog->prepareView();
878 if ((dialog = findOrBuild("view-source", true)))
879 dialog->prepareView();
880 if ((dialog = findOrBuild("progress", true)))
881 dialog->prepareView();
883 if (!restoreState(settings.value("layout").toByteArray(), 0))
886 // init the toolbars that have not been restored
887 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
888 Toolbars::Infos::iterator end = guiApp->toolbars().end();
889 for (; cit != end; ++cit) {
890 GuiToolbar * tb = toolbar(cit->name);
891 if (tb && !tb->isRestored())
892 initToolbar(cit->name);
895 // update lock (all) toolbars positions
896 updateLockToolbars();
903 GuiToolbar * GuiView::toolbar(string const & name)
905 ToolbarMap::iterator it = d.toolbars_.find(name);
906 if (it != d.toolbars_.end())
909 LYXERR(Debug::GUI, "Toolbar::display: no toolbar named " << name);
914 void GuiView::updateLockToolbars()
916 toolbarsMovable_ = false;
917 for (ToolbarInfo const & info : guiApp->toolbars()) {
918 GuiToolbar * tb = toolbar(info.name);
919 if (tb && tb->isMovable())
920 toolbarsMovable_ = true;
925 void GuiView::constructToolbars()
927 ToolbarMap::iterator it = d.toolbars_.begin();
928 for (; it != d.toolbars_.end(); ++it)
932 // I don't like doing this here, but the standard toolbar
933 // destroys this object when it's destroyed itself (vfr)
934 d.layout_ = new LayoutBox(*this);
935 d.stack_widget_->addWidget(d.layout_);
936 d.layout_->move(0,0);
938 // extracts the toolbars from the backend
939 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
940 Toolbars::Infos::iterator end = guiApp->toolbars().end();
941 for (; cit != end; ++cit)
942 d.toolbars_[cit->name] = new GuiToolbar(*cit, *this);
946 void GuiView::initToolbars()
948 // extracts the toolbars from the backend
949 Toolbars::Infos::iterator cit = guiApp->toolbars().begin();
950 Toolbars::Infos::iterator end = guiApp->toolbars().end();
951 for (; cit != end; ++cit)
952 initToolbar(cit->name);
956 void GuiView::initToolbar(string const & name)
958 GuiToolbar * tb = toolbar(name);
961 int const visibility = guiApp->toolbars().defaultVisibility(name);
962 bool newline = !(visibility & Toolbars::SAMEROW);
963 tb->setVisible(false);
964 tb->setVisibility(visibility);
966 if (visibility & Toolbars::TOP) {
968 addToolBarBreak(Qt::TopToolBarArea);
969 addToolBar(Qt::TopToolBarArea, tb);
972 if (visibility & Toolbars::BOTTOM) {
974 addToolBarBreak(Qt::BottomToolBarArea);
975 addToolBar(Qt::BottomToolBarArea, tb);
978 if (visibility & Toolbars::LEFT) {
980 addToolBarBreak(Qt::LeftToolBarArea);
981 addToolBar(Qt::LeftToolBarArea, tb);
984 if (visibility & Toolbars::RIGHT) {
986 addToolBarBreak(Qt::RightToolBarArea);
987 addToolBar(Qt::RightToolBarArea, tb);
990 if (visibility & Toolbars::ON)
991 tb->setVisible(true);
993 tb->setMovable(true);
997 TocModels & GuiView::tocModels()
999 return d.toc_models_;
1003 void GuiView::setFocus()
1005 LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this);
1006 QMainWindow::setFocus();
1010 bool GuiView::hasFocus() const
1012 if (currentWorkArea())
1013 return currentWorkArea()->hasFocus();
1014 if (currentMainWorkArea())
1015 return currentMainWorkArea()->hasFocus();
1016 return d.bg_widget_->hasFocus();
1020 void GuiView::focusInEvent(QFocusEvent * e)
1022 LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this);
1023 QMainWindow::focusInEvent(e);
1024 // Make sure guiApp points to the correct view.
1025 guiApp->setCurrentView(this);
1026 if (currentWorkArea())
1027 currentWorkArea()->setFocus();
1028 else if (currentMainWorkArea())
1029 currentMainWorkArea()->setFocus();
1031 d.bg_widget_->setFocus();
1035 void GuiView::showEvent(QShowEvent * e)
1037 LYXERR(Debug::GUI, "Passed Geometry "
1038 << size().height() << "x" << size().width()
1039 << "+" << pos().x() << "+" << pos().y());
1041 if (d.splitter_->count() == 0)
1042 // No work area, switch to the background widget.
1046 QMainWindow::showEvent(e);
1050 bool GuiView::closeScheduled()
1057 bool GuiView::prepareAllBuffersForLogout()
1059 Buffer * first = theBufferList().first();
1063 // First, iterate over all buffers and ask the users if unsaved
1064 // changes should be saved.
1065 // We cannot use a for loop as the buffer list cycles.
1068 if (!saveBufferIfNeeded(const_cast<Buffer &>(*b), false))
1070 b = theBufferList().next(b);
1071 } while (b != first);
1073 // Next, save session state
1074 // When a view/window was closed before without quitting LyX, there
1075 // are already entries in the lastOpened list.
1076 theSession().lastOpened().clear();
1083 /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas
1084 ** is responsibility of the container (e.g., dialog)
1086 void GuiView::closeEvent(QCloseEvent * close_event)
1088 LYXERR(Debug::DEBUG, "GuiView::closeEvent()");
1090 if (!GuiViewPrivate::busyBuffers.isEmpty()) {
1091 Alert::warning(_("Exit LyX"),
1092 _("LyX could not be closed because documents are being processed by LyX."));
1093 close_event->setAccepted(false);
1097 // If the user pressed the x (so we didn't call closeView
1098 // programmatically), we want to clear all existing entries.
1100 theSession().lastOpened().clear();
1105 // it can happen that this event arrives without selecting the view,
1106 // e.g. when clicking the close button on a background window.
1108 if (!closeWorkAreaAll()) {
1110 close_event->ignore();
1114 // Make sure that nothing will use this to be closed View.
1115 guiApp->unregisterView(this);
1117 if (isFullScreen()) {
1118 // Switch off fullscreen before closing.
1123 // Make sure the timer time out will not trigger a statusbar update.
1124 d.statusbar_timer_.stop();
1126 // Saving fullscreen requires additional tweaks in the toolbar code.
1127 // It wouldn't also work under linux natively.
1128 if (lyxrc.allow_geometry_session) {
1133 close_event->accept();
1137 void GuiView::dragEnterEvent(QDragEnterEvent * event)
1139 if (event->mimeData()->hasUrls())
1141 /// \todo Ask lyx-devel is this is enough:
1142 /// if (event->mimeData()->hasFormat("text/plain"))
1143 /// event->acceptProposedAction();
1147 void GuiView::dropEvent(QDropEvent * event)
1149 QList<QUrl> files = event->mimeData()->urls();
1150 if (files.isEmpty())
1153 LYXERR(Debug::GUI, "GuiView::dropEvent: got URLs!");
1154 for (int i = 0; i != files.size(); ++i) {
1155 string const file = os::internal_path(fromqstr(
1156 files.at(i).toLocalFile()));
1160 string const ext = support::getExtension(file);
1161 vector<const Format *> found_formats;
1163 // Find all formats that have the correct extension.
1164 vector<const Format *> const & import_formats
1165 = theConverters().importableFormats();
1166 vector<const Format *>::const_iterator it = import_formats.begin();
1167 for (; it != import_formats.end(); ++it)
1168 if ((*it)->hasExtension(ext))
1169 found_formats.push_back(*it);
1172 if (found_formats.size() >= 1) {
1173 if (found_formats.size() > 1) {
1174 //FIXME: show a dialog to choose the correct importable format
1175 LYXERR(Debug::FILES,
1176 "Multiple importable formats found, selecting first");
1178 string const arg = found_formats[0]->name() + " " + file;
1179 cmd = FuncRequest(LFUN_BUFFER_IMPORT, arg);
1182 //FIXME: do we have to explicitly check whether it's a lyx file?
1183 LYXERR(Debug::FILES,
1184 "No formats found, trying to open it as a lyx file");
1185 cmd = FuncRequest(LFUN_FILE_OPEN, file);
1187 // add the functions to the queue
1188 guiApp->addToFuncRequestQueue(cmd);
1191 // now process the collected functions. We perform the events
1192 // asynchronously. This prevents potential problems in case the
1193 // BufferView is closed within an event.
1194 guiApp->processFuncRequestQueueAsync();
1198 void GuiView::message(docstring const & str)
1200 if (ForkedProcess::iAmAChild())
1203 // call is moved to GUI-thread by GuiProgress
1204 d.progress_->appendMessage(toqstr(str));
1208 void GuiView::clearMessageText()
1210 message(docstring());
1214 void GuiView::updateStatusBarMessage(QString const & str)
1216 statusBar()->showMessage(str);
1217 d.statusbar_timer_.stop();
1218 d.statusbar_timer_.start(3000);
1222 void GuiView::clearMessage()
1224 // FIXME: This code was introduced in r19643 to fix bug #4123. However,
1225 // the hasFocus function mostly returns false, even if the focus is on
1226 // a workarea in this view.
1230 d.statusbar_timer_.stop();
1234 void GuiView::updateWindowTitle(GuiWorkArea * wa)
1236 if (wa != d.current_work_area_
1237 || wa->bufferView().buffer().isInternal())
1239 Buffer const & buf = wa->bufferView().buffer();
1240 // Set the windows title
1241 docstring title = buf.fileName().displayName(130) + from_ascii("[*]");
1242 if (buf.notifiesExternalModification()) {
1243 title = bformat(_("%1$s (modified externally)"), title);
1244 // If the external modification status has changed, then maybe the status of
1245 // buffer-save has changed too.
1249 title += from_ascii(" - LyX");
1251 setWindowTitle(toqstr(title));
1252 // Sets the path for the window: this is used by OSX to
1253 // allow a context click on the title bar showing a menu
1254 // with the path up to the file
1255 setWindowFilePath(toqstr(buf.absFileName()));
1256 // Tell Qt whether the current document is changed
1257 setWindowModified(!buf.isClean());
1259 if (buf.params().shell_escape)
1260 shell_escape_->show();
1262 shell_escape_->hide();
1264 if (buf.hasReadonlyFlag())
1269 if (buf.lyxvc().inUse()) {
1270 version_control_->show();
1271 version_control_->setText(toqstr(buf.lyxvc().vcstatus()));
1273 version_control_->hide();
1277 void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
1279 if (d.current_work_area_)
1280 // disconnect the current work area from all slots
1281 QObject::disconnect(d.current_work_area_, 0, this, 0);
1283 disconnectBufferView();
1284 connectBufferView(wa->bufferView());
1285 connectBuffer(wa->bufferView().buffer());
1286 d.current_work_area_ = wa;
1287 QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
1288 this, SLOT(updateWindowTitle(GuiWorkArea *)));
1289 QObject::connect(wa, SIGNAL(busy(bool)),
1290 this, SLOT(setBusy(bool)));
1291 // connection of a signal to a signal
1292 QObject::connect(wa, SIGNAL(bufferViewChanged()),
1293 this, SIGNAL(bufferViewChanged()));
1294 Q_EMIT updateWindowTitle(wa);
1295 Q_EMIT bufferViewChanged();
1299 void GuiView::onBufferViewChanged()
1302 // Buffer-dependent dialogs must be updated. This is done here because
1303 // some dialogs require buffer()->text.
1308 void GuiView::on_lastWorkAreaRemoved()
1311 // We already are in a close event. Nothing more to do.
1314 if (d.splitter_->count() > 1)
1315 // We have a splitter so don't close anything.
1318 // Reset and updates the dialogs.
1319 Q_EMIT bufferViewChanged();
1324 if (lyxrc.open_buffers_in_tabs)
1325 // Nothing more to do, the window should stay open.
1328 if (guiApp->viewIds().size() > 1) {
1334 // On Mac we also close the last window because the application stay
1335 // resident in memory. On other platforms we don't close the last
1336 // window because this would quit the application.
1342 void GuiView::updateStatusBar()
1344 // let the user see the explicit message
1345 if (d.statusbar_timer_.isActive())
1352 void GuiView::showMessage()
1356 QString msg = toqstr(theGuiApp()->viewStatusMessage());
1357 if (msg.isEmpty()) {
1358 BufferView const * bv = currentBufferView();
1360 msg = toqstr(bv->cursor().currentState(devel_mode_));
1362 msg = qt_("Welcome to LyX!");
1364 statusBar()->showMessage(msg);
1368 bool GuiView::event(QEvent * e)
1372 // Useful debug code:
1373 //case QEvent::ActivationChange:
1374 //case QEvent::WindowDeactivate:
1375 //case QEvent::Paint:
1376 //case QEvent::Enter:
1377 //case QEvent::Leave:
1378 //case QEvent::HoverEnter:
1379 //case QEvent::HoverLeave:
1380 //case QEvent::HoverMove:
1381 //case QEvent::StatusTip:
1382 //case QEvent::DragEnter:
1383 //case QEvent::DragLeave:
1384 //case QEvent::Drop:
1387 case QEvent::WindowActivate: {
1388 GuiView * old_view = guiApp->currentView();
1389 if (this == old_view) {
1391 return QMainWindow::event(e);
1393 if (old_view && old_view->currentBufferView()) {
1394 // save current selection to the selection buffer to allow
1395 // middle-button paste in this window.
1396 cap::saveSelection(old_view->currentBufferView()->cursor());
1398 guiApp->setCurrentView(this);
1399 if (d.current_work_area_)
1400 on_currentWorkAreaChanged(d.current_work_area_);
1404 return QMainWindow::event(e);
1407 case QEvent::ShortcutOverride: {
1409 if (isFullScreen() && menuBar()->isHidden()) {
1410 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
1411 // FIXME: we should also try to detect special LyX shortcut such as
1412 // Alt-P and Alt-M. Right now there is a hack in
1413 // GuiWorkArea::processKeySym() that hides again the menubar for
1415 if (ke->modifiers() & Qt::AltModifier && ke->key() != Qt::Key_Alt) {
1417 return QMainWindow::event(e);
1420 return QMainWindow::event(e);
1424 return QMainWindow::event(e);
1428 void GuiView::resetWindowTitle()
1430 setWindowTitle(qt_("LyX"));
1433 bool GuiView::focusNextPrevChild(bool /*next*/)
1440 bool GuiView::busy() const
1446 void GuiView::setBusy(bool busy)
1448 bool const busy_before = busy_ > 0;
1449 busy ? ++busy_ : --busy_;
1450 if ((busy_ > 0) == busy_before)
1451 // busy state didn't change
1455 QApplication::setOverrideCursor(Qt::WaitCursor);
1458 QApplication::restoreOverrideCursor();
1463 void GuiView::resetCommandExecute()
1465 command_execute_ = false;
1470 double GuiView::pixelRatio() const
1472 #if QT_VERSION >= 0x050000
1473 return qt_scale_factor * devicePixelRatio();
1480 GuiWorkArea * GuiView::workArea(int index)
1482 if (TabWorkArea * twa = d.currentTabWorkArea())
1483 if (index < twa->count())
1484 return twa->workArea(index);
1489 GuiWorkArea * GuiView::workArea(Buffer & buffer)
1491 if (currentWorkArea()
1492 && ¤tWorkArea()->bufferView().buffer() == &buffer)
1493 return currentWorkArea();
1494 if (TabWorkArea * twa = d.currentTabWorkArea())
1495 return twa->workArea(buffer);
1500 GuiWorkArea * GuiView::addWorkArea(Buffer & buffer)
1502 // Automatically create a TabWorkArea if there are none yet.
1503 TabWorkArea * tab_widget = d.splitter_->count()
1504 ? d.currentTabWorkArea() : addTabWorkArea();
1505 return tab_widget->addWorkArea(buffer, *this);
1509 TabWorkArea * GuiView::addTabWorkArea()
1511 TabWorkArea * twa = new TabWorkArea;
1512 QObject::connect(twa, SIGNAL(currentWorkAreaChanged(GuiWorkArea *)),
1513 this, SLOT(on_currentWorkAreaChanged(GuiWorkArea *)));
1514 QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()),
1515 this, SLOT(on_lastWorkAreaRemoved()));
1517 d.splitter_->addWidget(twa);
1518 d.stack_widget_->setCurrentWidget(d.splitter_);
1523 GuiWorkArea const * GuiView::currentWorkArea() const
1525 return d.current_work_area_;
1529 GuiWorkArea * GuiView::currentWorkArea()
1531 return d.current_work_area_;
1535 GuiWorkArea const * GuiView::currentMainWorkArea() const
1537 if (!d.currentTabWorkArea())
1539 return d.currentTabWorkArea()->currentWorkArea();
1543 GuiWorkArea * GuiView::currentMainWorkArea()
1545 if (!d.currentTabWorkArea())
1547 return d.currentTabWorkArea()->currentWorkArea();
1551 void GuiView::setCurrentWorkArea(GuiWorkArea * wa)
1553 LYXERR(Debug::DEBUG, "Setting current wa: " << wa << endl);
1555 d.current_work_area_ = 0;
1557 Q_EMIT bufferViewChanged();
1561 // FIXME: I've no clue why this is here and why it accesses
1562 // theGuiApp()->currentView, which might be 0 (bug 6464).
1563 // See also 27525 (vfr).
1564 if (theGuiApp()->currentView() == this
1565 && theGuiApp()->currentView()->currentWorkArea() == wa)
1568 if (currentBufferView())
1569 cap::saveSelection(currentBufferView()->cursor());
1571 theGuiApp()->setCurrentView(this);
1572 d.current_work_area_ = wa;
1574 // We need to reset this now, because it will need to be
1575 // right if the tabWorkArea gets reset in the for loop. We
1576 // will change it back if we aren't in that case.
1577 GuiWorkArea * const old_cmwa = d.current_main_work_area_;
1578 d.current_main_work_area_ = wa;
1580 for (int i = 0; i != d.splitter_->count(); ++i) {
1581 if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) {
1582 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea()
1583 << ", Current main wa: " << currentMainWorkArea());
1588 d.current_main_work_area_ = old_cmwa;
1590 LYXERR(Debug::DEBUG, "This is not a tabbed wa");
1591 on_currentWorkAreaChanged(wa);
1592 BufferView & bv = wa->bufferView();
1593 bv.cursor().fixIfBroken();
1595 wa->setUpdatesEnabled(true);
1596 LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea());
1600 void GuiView::removeWorkArea(GuiWorkArea * wa)
1602 LASSERT(wa, return);
1603 if (wa == d.current_work_area_) {
1605 disconnectBufferView();
1606 d.current_work_area_ = 0;
1607 d.current_main_work_area_ = 0;
1610 bool found_twa = false;
1611 for (int i = 0; i != d.splitter_->count(); ++i) {
1612 TabWorkArea * twa = d.tabWorkArea(i);
1613 if (twa->removeWorkArea(wa)) {
1614 // Found in this tab group, and deleted the GuiWorkArea.
1616 if (twa->count() != 0) {
1617 if (d.current_work_area_ == 0)
1618 // This means that we are closing the current GuiWorkArea, so
1619 // switch to the next GuiWorkArea in the found TabWorkArea.
1620 setCurrentWorkArea(twa->currentWorkArea());
1622 // No more WorkAreas in this tab group, so delete it.
1629 // It is not a tabbed work area (i.e., the search work area), so it
1630 // should be deleted by other means.
1631 LASSERT(found_twa, return);
1633 if (d.current_work_area_ == 0) {
1634 if (d.splitter_->count() != 0) {
1635 TabWorkArea * twa = d.currentTabWorkArea();
1636 setCurrentWorkArea(twa->currentWorkArea());
1638 // No more work areas, switch to the background widget.
1639 setCurrentWorkArea(0);
1645 LayoutBox * GuiView::getLayoutDialog() const
1651 void GuiView::updateLayoutList()
1654 d.layout_->updateContents(false);
1658 void GuiView::updateToolbars()
1660 ToolbarMap::iterator end = d.toolbars_.end();
1661 if (d.current_work_area_) {
1663 if (d.current_work_area_->bufferView().cursor().inMathed()
1664 && !d.current_work_area_->bufferView().cursor().inRegexped())
1665 context |= Toolbars::MATH;
1666 if (lyx::getStatus(FuncRequest(LFUN_LAYOUT_TABULAR)).enabled())
1667 context |= Toolbars::TABLE;
1668 if (currentBufferView()->buffer().areChangesPresent()
1669 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).enabled()
1670 && lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true))
1671 || (lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).enabled()
1672 && lyx::getStatus(FuncRequest(LFUN_CHANGES_OUTPUT)).onOff(true)))
1673 context |= Toolbars::REVIEW;
1674 if (lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled())
1675 context |= Toolbars::MATHMACROTEMPLATE;
1676 if (lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled())
1677 context |= Toolbars::IPA;
1678 if (command_execute_)
1679 context |= Toolbars::MINIBUFFER;
1680 if (minibuffer_focus_) {
1681 context |= Toolbars::MINIBUFFER_FOCUS;
1682 minibuffer_focus_ = false;
1685 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1686 it->second->update(context);
1688 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
1689 it->second->update();
1693 void GuiView::setBuffer(Buffer * newBuffer, bool switch_to)
1695 LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl);
1696 LASSERT(newBuffer, return);
1698 GuiWorkArea * wa = workArea(*newBuffer);
1701 newBuffer->masterBuffer()->updateBuffer();
1703 wa = addWorkArea(*newBuffer);
1704 // scroll to the position when the BufferView was last closed
1705 if (lyxrc.use_lastfilepos) {
1706 LastFilePosSection::FilePos filepos =
1707 theSession().lastFilePos().load(newBuffer->fileName());
1708 wa->bufferView().moveToPosition(filepos.pit, filepos.pos, 0, 0);
1711 //Disconnect the old buffer...there's no new one.
1714 connectBuffer(*newBuffer);
1715 connectBufferView(wa->bufferView());
1717 setCurrentWorkArea(wa);
1721 void GuiView::connectBuffer(Buffer & buf)
1723 buf.setGuiDelegate(this);
1727 void GuiView::disconnectBuffer()
1729 if (d.current_work_area_)
1730 d.current_work_area_->bufferView().buffer().setGuiDelegate(0);
1734 void GuiView::connectBufferView(BufferView & bv)
1736 bv.setGuiDelegate(this);
1740 void GuiView::disconnectBufferView()
1742 if (d.current_work_area_)
1743 d.current_work_area_->bufferView().setGuiDelegate(0);
1747 void GuiView::errors(string const & error_type, bool from_master)
1749 BufferView const * const bv = currentBufferView();
1753 ErrorList const & el = from_master ?
1754 bv->buffer().masterBuffer()->errorList(error_type) :
1755 bv->buffer().errorList(error_type);
1760 string err = error_type;
1762 err = "from_master|" + error_type;
1763 showDialog("errorlist", err);
1767 void GuiView::updateTocItem(string const & type, DocIterator const & dit)
1769 d.toc_models_.updateItem(toqstr(type), dit);
1773 void GuiView::structureChanged()
1775 // This is called from the Buffer, which has no way to ensure that cursors
1776 // in BufferView remain valid.
1777 if (documentBufferView())
1778 documentBufferView()->cursor().sanitize();
1779 // FIXME: This is slightly expensive, though less than the tocBackend update
1780 // (#9880). This also resets the view in the Toc Widget (#6675).
1781 d.toc_models_.reset(documentBufferView());
1782 // Navigator needs more than a simple update in this case. It needs to be
1784 updateDialog("toc", "");
1788 void GuiView::updateDialog(string const & name, string const & sdata)
1790 if (!isDialogVisible(name))
1793 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
1794 if (it == d.dialogs_.end())
1797 Dialog * const dialog = it->second.get();
1798 if (dialog->isVisibleView())
1799 dialog->initialiseParams(sdata);
1803 BufferView * GuiView::documentBufferView()
1805 return currentMainWorkArea()
1806 ? ¤tMainWorkArea()->bufferView()
1811 BufferView const * GuiView::documentBufferView() const
1813 return currentMainWorkArea()
1814 ? ¤tMainWorkArea()->bufferView()
1819 BufferView * GuiView::currentBufferView()
1821 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1825 BufferView const * GuiView::currentBufferView() const
1827 return d.current_work_area_ ? &d.current_work_area_->bufferView() : 0;
1831 docstring GuiView::GuiViewPrivate::autosaveAndDestroy(
1832 Buffer const * orig, Buffer * clone)
1834 bool const success = clone->autoSave();
1836 busyBuffers.remove(orig);
1838 ? _("Automatic save done.")
1839 : _("Automatic save failed!");
1843 void GuiView::autoSave()
1845 LYXERR(Debug::INFO, "Running autoSave()");
1847 Buffer * buffer = documentBufferView()
1848 ? &documentBufferView()->buffer() : 0;
1850 resetAutosaveTimers();
1854 GuiViewPrivate::busyBuffers.insert(buffer);
1855 QFuture<docstring> f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy,
1856 buffer, buffer->cloneBufferOnly());
1857 d.autosave_watcher_.setFuture(f);
1858 resetAutosaveTimers();
1862 void GuiView::resetAutosaveTimers()
1865 d.autosave_timeout_.restart();
1869 bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
1872 Buffer * buf = currentBufferView()
1873 ? ¤tBufferView()->buffer() : 0;
1874 Buffer * doc_buffer = documentBufferView()
1875 ? &(documentBufferView()->buffer()) : 0;
1878 /* In LyX/Mac, when a dialog is open, the menus of the
1879 application can still be accessed without giving focus to
1880 the main window. In this case, we want to disable the menu
1881 entries that are buffer-related.
1882 This code must not be used on Linux and Windows, since it
1883 would disable buffer-related entries when hovering over the
1884 menu (see bug #9574).
1886 if (cmd.origin() == FuncRequest::MENU && !hasFocus()) {
1892 // Check whether we need a buffer
1893 if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) {
1894 // no, exit directly
1895 flag.message(from_utf8(N_("Command not allowed with"
1896 "out any document open")));
1897 flag.setEnabled(false);
1901 if (cmd.origin() == FuncRequest::TOC) {
1902 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
1903 if (!toc || !toc->getStatus(documentBufferView()->cursor(), cmd, flag))
1904 flag.setEnabled(false);
1908 switch(cmd.action()) {
1909 case LFUN_BUFFER_IMPORT:
1912 case LFUN_MASTER_BUFFER_EXPORT:
1914 && (doc_buffer->parent() != 0
1915 || doc_buffer->hasChildren())
1916 && !d.processing_thread_watcher_.isRunning()
1917 // this launches a dialog, which would be in the wrong Buffer
1918 && !(::lyx::operator==(cmd.argument(), "custom"));
1921 case LFUN_MASTER_BUFFER_UPDATE:
1922 case LFUN_MASTER_BUFFER_VIEW:
1924 && (doc_buffer->parent() != 0
1925 || doc_buffer->hasChildren())
1926 && !d.processing_thread_watcher_.isRunning();
1929 case LFUN_BUFFER_UPDATE:
1930 case LFUN_BUFFER_VIEW: {
1931 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1935 string format = to_utf8(cmd.argument());
1936 if (cmd.argument().empty())
1937 format = doc_buffer->params().getDefaultOutputFormat();
1938 enable = doc_buffer->params().isExportable(format, true);
1942 case LFUN_BUFFER_RELOAD:
1943 enable = doc_buffer && !doc_buffer->isUnnamed()
1944 && doc_buffer->fileName().exists()
1945 && (!doc_buffer->isClean() || doc_buffer->notifiesExternalModification());
1948 case LFUN_BUFFER_CHILD_OPEN:
1949 enable = doc_buffer != 0;
1952 case LFUN_BUFFER_WRITE:
1953 enable = doc_buffer && (doc_buffer->isUnnamed() || !doc_buffer->isClean());
1956 //FIXME: This LFUN should be moved to GuiApplication.
1957 case LFUN_BUFFER_WRITE_ALL: {
1958 // We enable the command only if there are some modified buffers
1959 Buffer * first = theBufferList().first();
1964 // We cannot use a for loop as the buffer list is a cycle.
1966 if (!b->isClean()) {
1970 b = theBufferList().next(b);
1971 } while (b != first);
1975 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
1976 enable = doc_buffer && doc_buffer->notifiesExternalModification();
1979 case LFUN_BUFFER_EXPORT: {
1980 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1984 return doc_buffer->getStatus(cmd, flag);
1988 case LFUN_BUFFER_EXPORT_AS:
1989 if (!doc_buffer || d.processing_thread_watcher_.isRunning()) {
1994 case LFUN_BUFFER_WRITE_AS:
1995 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
1996 enable = doc_buffer != 0;
1999 case LFUN_EXPORT_CANCEL:
2000 enable = d.processing_thread_watcher_.isRunning();
2003 case LFUN_BUFFER_CLOSE:
2004 case LFUN_VIEW_CLOSE:
2005 enable = doc_buffer != 0;
2008 case LFUN_BUFFER_CLOSE_ALL:
2009 enable = theBufferList().last() != theBufferList().first();
2012 case LFUN_BUFFER_CHKTEX: {
2013 // hide if we have no checktex command
2014 if (lyxrc.chktex_command.empty()) {
2015 flag.setUnknown(true);
2019 if (!doc_buffer || !doc_buffer->params().isLatex()
2020 || d.processing_thread_watcher_.isRunning()) {
2021 // grey out, don't hide
2029 case LFUN_VIEW_SPLIT:
2030 if (cmd.getArg(0) == "vertical")
2031 enable = doc_buffer && (d.splitter_->count() == 1 ||
2032 d.splitter_->orientation() == Qt::Vertical);
2034 enable = doc_buffer && (d.splitter_->count() == 1 ||
2035 d.splitter_->orientation() == Qt::Horizontal);
2038 case LFUN_TAB_GROUP_CLOSE:
2039 enable = d.tabWorkAreaCount() > 1;
2042 case LFUN_DEVEL_MODE_TOGGLE:
2043 flag.setOnOff(devel_mode_);
2046 case LFUN_TOOLBAR_TOGGLE: {
2047 string const name = cmd.getArg(0);
2048 if (GuiToolbar * t = toolbar(name))
2049 flag.setOnOff(t->isVisible());
2052 docstring const msg =
2053 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2059 case LFUN_TOOLBAR_MOVABLE: {
2060 string const name = cmd.getArg(0);
2061 // use negation since locked == !movable
2063 // toolbar name * locks all toolbars
2064 flag.setOnOff(!toolbarsMovable_);
2065 else if (GuiToolbar * t = toolbar(name))
2066 flag.setOnOff(!(t->isMovable()));
2069 docstring const msg =
2070 bformat(_("Unknown toolbar \"%1$s\""), from_utf8(name));
2076 case LFUN_ICON_SIZE:
2077 flag.setOnOff(d.iconSize(cmd.argument()) == iconSize());
2080 case LFUN_DROP_LAYOUTS_CHOICE:
2084 case LFUN_UI_TOGGLE:
2085 flag.setOnOff(isFullScreen());
2088 case LFUN_DIALOG_DISCONNECT_INSET:
2091 case LFUN_DIALOG_HIDE:
2092 // FIXME: should we check if the dialog is shown?
2095 case LFUN_DIALOG_TOGGLE:
2096 flag.setOnOff(isDialogVisible(cmd.getArg(0)));
2099 case LFUN_DIALOG_SHOW: {
2100 string const name = cmd.getArg(0);
2102 enable = name == "aboutlyx"
2103 || name == "file" //FIXME: should be removed.
2104 || name == "lyxfiles"
2106 || name == "texinfo"
2107 || name == "progress"
2108 || name == "compare";
2109 else if (name == "character" || name == "symbols"
2110 || name == "mathdelimiter" || name == "mathmatrix") {
2111 if (!buf || buf->isReadonly())
2114 Cursor const & cur = currentBufferView()->cursor();
2115 enable = !(cur.inTexted() && cur.paragraph().isPassThru());
2118 else if (name == "latexlog")
2119 enable = FileName(doc_buffer->logName()).isReadableFile();
2120 else if (name == "spellchecker")
2121 enable = theSpellChecker()
2122 && !doc_buffer->isReadonly()
2123 && !doc_buffer->text().empty();
2124 else if (name == "vclog")
2125 enable = doc_buffer->lyxvc().inUse();
2129 case LFUN_DIALOG_UPDATE: {
2130 string const name = cmd.getArg(0);
2132 enable = name == "prefs";
2136 case LFUN_COMMAND_EXECUTE:
2138 case LFUN_MENU_OPEN:
2139 // Nothing to check.
2142 case LFUN_COMPLETION_INLINE:
2143 if (!d.current_work_area_
2144 || !d.current_work_area_->completer().inlinePossible(
2145 currentBufferView()->cursor()))
2149 case LFUN_COMPLETION_POPUP:
2150 if (!d.current_work_area_
2151 || !d.current_work_area_->completer().popupPossible(
2152 currentBufferView()->cursor()))
2157 if (!d.current_work_area_
2158 || !d.current_work_area_->completer().inlinePossible(
2159 currentBufferView()->cursor()))
2163 case LFUN_COMPLETION_ACCEPT:
2164 if (!d.current_work_area_
2165 || (!d.current_work_area_->completer().popupVisible()
2166 && !d.current_work_area_->completer().inlineVisible()
2167 && !d.current_work_area_->completer().completionAvailable()))
2171 case LFUN_COMPLETION_CANCEL:
2172 if (!d.current_work_area_
2173 || (!d.current_work_area_->completer().popupVisible()
2174 && !d.current_work_area_->completer().inlineVisible()))
2178 case LFUN_BUFFER_ZOOM_OUT:
2179 case LFUN_BUFFER_ZOOM_IN: {
2180 // only diff between these two is that the default for ZOOM_OUT
2182 bool const neg_zoom =
2183 convert<int>(cmd.argument()) < 0 ||
2184 (cmd.action() == LFUN_BUFFER_ZOOM_OUT && cmd.argument().empty());
2185 if (lyxrc.currentZoom <= zoom_min_ && neg_zoom) {
2186 docstring const msg =
2187 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2191 enable = doc_buffer;
2195 case LFUN_BUFFER_ZOOM: {
2196 bool const less_than_min_zoom =
2197 !cmd.argument().empty() && convert<int>(cmd.argument()) < zoom_min_;
2198 if (lyxrc.currentZoom <= zoom_min_ && less_than_min_zoom) {
2199 docstring const msg =
2200 bformat(_("Zoom level cannot be less than %1$d%."), zoom_min_);
2205 enable = doc_buffer;
2209 case LFUN_BUFFER_MOVE_NEXT:
2210 case LFUN_BUFFER_MOVE_PREVIOUS:
2211 // we do not cycle when moving
2212 case LFUN_BUFFER_NEXT:
2213 case LFUN_BUFFER_PREVIOUS:
2214 // because we cycle, it doesn't matter whether on first or last
2215 enable = (d.currentTabWorkArea()->count() > 1);
2217 case LFUN_BUFFER_SWITCH:
2218 // toggle on the current buffer, but do not toggle off
2219 // the other ones (is that a good idea?)
2221 && to_utf8(cmd.argument()) == doc_buffer->absFileName())
2222 flag.setOnOff(true);
2225 case LFUN_VC_REGISTER:
2226 enable = doc_buffer && !doc_buffer->lyxvc().inUse();
2228 case LFUN_VC_RENAME:
2229 enable = doc_buffer && doc_buffer->lyxvc().renameEnabled();
2232 enable = doc_buffer && doc_buffer->lyxvc().copyEnabled();
2234 case LFUN_VC_CHECK_IN:
2235 enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
2237 case LFUN_VC_CHECK_OUT:
2238 enable = doc_buffer && doc_buffer->lyxvc().checkOutEnabled();
2240 case LFUN_VC_LOCKING_TOGGLE:
2241 enable = doc_buffer && !doc_buffer->hasReadonlyFlag()
2242 && doc_buffer->lyxvc().lockingToggleEnabled();
2243 flag.setOnOff(enable && doc_buffer->lyxvc().locking());
2245 case LFUN_VC_REVERT:
2246 enable = doc_buffer && doc_buffer->lyxvc().inUse()
2247 && !doc_buffer->hasReadonlyFlag();
2249 case LFUN_VC_UNDO_LAST:
2250 enable = doc_buffer && doc_buffer->lyxvc().undoLastEnabled();
2252 case LFUN_VC_REPO_UPDATE:
2253 enable = doc_buffer && doc_buffer->lyxvc().repoUpdateEnabled();
2255 case LFUN_VC_COMMAND: {
2256 if (cmd.argument().empty())
2258 if (!doc_buffer && contains(cmd.getArg(0), 'D'))
2262 case LFUN_VC_COMPARE:
2263 enable = doc_buffer && doc_buffer->lyxvc().prepareFileRevisionEnabled();
2266 case LFUN_SERVER_GOTO_FILE_ROW:
2267 case LFUN_LYX_ACTIVATE:
2269 case LFUN_FORWARD_SEARCH:
2270 enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty());
2273 case LFUN_FILE_INSERT_PLAINTEXT:
2274 case LFUN_FILE_INSERT_PLAINTEXT_PARA:
2275 enable = documentBufferView() && documentBufferView()->cursor().inTexted();
2278 case LFUN_SPELLING_CONTINUOUSLY:
2279 flag.setOnOff(lyxrc.spellcheck_continuously);
2287 flag.setEnabled(false);
2293 static FileName selectTemplateFile()
2295 FileDialog dlg(qt_("Select template file"));
2296 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2297 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2299 FileDialog::Result result = dlg.open(toqstr(lyxrc.template_path),
2300 QStringList(qt_("LyX Documents (*.lyx)")));
2302 if (result.first == FileDialog::Later)
2304 if (result.second.isEmpty())
2306 return FileName(fromqstr(result.second));
2310 Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles)
2314 Buffer * newBuffer = 0;
2316 newBuffer = checkAndLoadLyXFile(filename);
2317 } catch (ExceptionMessage const & e) {
2324 message(_("Document not loaded."));
2328 setBuffer(newBuffer);
2329 newBuffer->errors("Parse");
2332 theSession().lastFiles().add(filename);
2333 theSession().writeFile();
2340 void GuiView::openDocument(string const & fname)
2342 string initpath = lyxrc.document_path;
2344 if (documentBufferView()) {
2345 string const trypath = documentBufferView()->buffer().filePath();
2346 // If directory is writeable, use this as default.
2347 if (FileName(trypath).isDirWritable())
2353 if (fname.empty()) {
2354 FileDialog dlg(qt_("Select document to open"));
2355 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2356 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2358 QStringList const filter(qt_("LyX Documents (*.lyx)"));
2359 FileDialog::Result result =
2360 dlg.open(toqstr(initpath), filter);
2362 if (result.first == FileDialog::Later)
2365 filename = fromqstr(result.second);
2367 // check selected filename
2368 if (filename.empty()) {
2369 message(_("Canceled."));
2375 // get absolute path of file and add ".lyx" to the filename if
2377 FileName const fullname =
2378 fileSearch(string(), filename, "lyx", support::may_not_exist);
2379 if (!fullname.empty())
2380 filename = fullname.absFileName();
2382 if (!fullname.onlyPath().isDirectory()) {
2383 Alert::warning(_("Invalid filename"),
2384 bformat(_("The directory in the given path\n%1$s\ndoes not exist."),
2385 from_utf8(fullname.absFileName())));
2389 // if the file doesn't exist and isn't already open (bug 6645),
2390 // let the user create one
2391 if (!fullname.exists() && !theBufferList().exists(fullname) &&
2392 !LyXVC::file_not_found_hook(fullname)) {
2393 // the user specifically chose this name. Believe him.
2394 Buffer * const b = newFile(filename, string(), true);
2400 docstring const disp_fn = makeDisplayPath(filename);
2401 message(bformat(_("Opening document %1$s..."), disp_fn));
2404 Buffer * buf = loadDocument(fullname);
2406 str2 = bformat(_("Document %1$s opened."), disp_fn);
2407 if (buf->lyxvc().inUse())
2408 str2 += " " + from_utf8(buf->lyxvc().versionString()) +
2409 " " + _("Version control detected.");
2411 str2 = bformat(_("Could not open document %1$s"), disp_fn);
2416 // FIXME: clean that
2417 static bool import(GuiView * lv, FileName const & filename,
2418 string const & format, ErrorList & errorList)
2420 FileName const lyxfile(support::changeExtension(filename.absFileName(), ".lyx"));
2422 string loader_format;
2423 vector<string> loaders = theConverters().loaders();
2424 if (find(loaders.begin(), loaders.end(), format) == loaders.end()) {
2425 vector<string>::const_iterator it = loaders.begin();
2426 vector<string>::const_iterator en = loaders.end();
2427 for (; it != en; ++it) {
2428 if (!theConverters().isReachable(format, *it))
2431 string const tofile =
2432 support::changeExtension(filename.absFileName(),
2433 theFormats().extension(*it));
2434 if (theConverters().convert(0, filename, FileName(tofile),
2435 filename, format, *it, errorList) != Converters::SUCCESS)
2437 loader_format = *it;
2440 if (loader_format.empty()) {
2441 frontend::Alert::error(_("Couldn't import file"),
2442 bformat(_("No information for importing the format %1$s."),
2443 theFormats().prettyName(format)));
2447 loader_format = format;
2449 if (loader_format == "lyx") {
2450 Buffer * buf = lv->loadDocument(lyxfile);
2454 Buffer * const b = newFile(lyxfile.absFileName(), string(), true);
2458 bool as_paragraphs = loader_format == "textparagraph";
2459 string filename2 = (loader_format == format) ? filename.absFileName()
2460 : support::changeExtension(filename.absFileName(),
2461 theFormats().extension(loader_format));
2462 lv->currentBufferView()->insertPlaintextFile(FileName(filename2),
2464 guiApp->setCurrentView(lv);
2465 lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
2472 void GuiView::importDocument(string const & argument)
2475 string filename = split(argument, format, ' ');
2477 LYXERR(Debug::INFO, format << " file: " << filename);
2479 // need user interaction
2480 if (filename.empty()) {
2481 string initpath = lyxrc.document_path;
2482 if (documentBufferView()) {
2483 string const trypath = documentBufferView()->buffer().filePath();
2484 // If directory is writeable, use this as default.
2485 if (FileName(trypath).isDirWritable())
2489 docstring const text = bformat(_("Select %1$s file to import"),
2490 theFormats().prettyName(format));
2492 FileDialog dlg(toqstr(text));
2493 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2494 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2496 docstring filter = theFormats().prettyName(format);
2499 filter += from_utf8(theFormats().extensions(format));
2502 FileDialog::Result result =
2503 dlg.open(toqstr(initpath), fileFilters(toqstr(filter)));
2505 if (result.first == FileDialog::Later)
2508 filename = fromqstr(result.second);
2510 // check selected filename
2511 if (filename.empty())
2512 message(_("Canceled."));
2515 if (filename.empty())
2518 // get absolute path of file
2519 FileName const fullname(support::makeAbsPath(filename));
2521 // Can happen if the user entered a path into the dialog
2523 if (fullname.onlyFileName().empty()) {
2524 docstring msg = bformat(_("The file name '%1$s' is invalid!\n"
2525 "Aborting import."),
2526 from_utf8(fullname.absFileName()));
2527 frontend::Alert::error(_("File name error"), msg);
2528 message(_("Canceled."));
2533 FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx"));
2535 // Check if the document already is open
2536 Buffer * buf = theBufferList().getBuffer(lyxfile);
2539 if (!closeBuffer()) {
2540 message(_("Canceled."));
2545 docstring const displaypath = makeDisplayPath(lyxfile.absFileName(), 30);
2547 // if the file exists already, and we didn't do
2548 // -i lyx thefile.lyx, warn
2549 if (lyxfile.exists() && fullname != lyxfile) {
2551 docstring text = bformat(_("The document %1$s already exists.\n\n"
2552 "Do you want to overwrite that document?"), displaypath);
2553 int const ret = Alert::prompt(_("Overwrite document?"),
2554 text, 0, 1, _("&Overwrite"), _("&Cancel"));
2557 message(_("Canceled."));
2562 message(bformat(_("Importing %1$s..."), displaypath));
2563 ErrorList errorList;
2564 if (import(this, fullname, format, errorList))
2565 message(_("imported."));
2567 message(_("file not imported!"));
2569 // FIXME (Abdel 12/08/06): Is there a need to display the error list here?
2573 void GuiView::newDocument(string const & filename, string templatefile,
2576 FileName initpath(lyxrc.document_path);
2577 if (documentBufferView()) {
2578 FileName const trypath(documentBufferView()->buffer().filePath());
2579 // If directory is writeable, use this as default.
2580 if (trypath.isDirWritable())
2584 if (from_template) {
2585 if (templatefile.empty())
2586 templatefile = selectTemplateFile().absFileName();
2587 if (templatefile.empty())
2592 if (filename.empty())
2593 b = newUnnamedFile(initpath, to_utf8(_("newfile")), templatefile);
2595 b = newFile(filename, templatefile, true);
2600 // If no new document could be created, it is unsure
2601 // whether there is a valid BufferView.
2602 if (currentBufferView())
2603 // Ensure the cursor is correctly positioned on screen.
2604 currentBufferView()->showCursor();
2608 void GuiView::insertLyXFile(docstring const & fname, bool ignorelang)
2610 BufferView * bv = documentBufferView();
2615 FileName filename(to_utf8(fname));
2616 if (filename.empty()) {
2617 // Launch a file browser
2619 string initpath = lyxrc.document_path;
2620 string const trypath = bv->buffer().filePath();
2621 // If directory is writeable, use this as default.
2622 if (FileName(trypath).isDirWritable())
2626 FileDialog dlg(qt_("Select LyX document to insert"));
2627 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2628 dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path));
2630 FileDialog::Result result = dlg.open(toqstr(initpath),
2631 QStringList(qt_("LyX Documents (*.lyx)")));
2633 if (result.first == FileDialog::Later)
2637 filename.set(fromqstr(result.second));
2639 // check selected filename
2640 if (filename.empty()) {
2641 // emit message signal.
2642 message(_("Canceled."));
2647 bv->insertLyXFile(filename, ignorelang);
2648 bv->buffer().errors("Parse");
2652 string const GuiView::getTemplatesPath(Buffer & b)
2654 // We start off with the user's templates path
2655 string result = addPath(package().user_support().absFileName(), "templates");
2656 // Check for the document language
2657 string const langcode = b.params().language->code();
2658 string const shortcode = langcode.substr(0, 2);
2659 if (!langcode.empty() && shortcode != "en") {
2660 string subpath = addPath(result, shortcode);
2661 string subpath_long = addPath(result, langcode);
2662 // If we have a subdirectory for the language already,
2664 FileName sp = FileName(subpath);
2665 if (sp.isDirectory())
2667 else if (FileName(subpath_long).isDirectory())
2668 result = subpath_long;
2670 // Ask whether we should create such a subdirectory
2671 docstring const text =
2672 bformat(_("It is suggested to save the template in a subdirectory\n"
2673 "appropriate to the document language (%1$s).\n"
2674 "This subdirectory does not exists yet.\n"
2675 "Do you want to create it?"),
2676 _(b.params().language->display()));
2677 if (Alert::prompt(_("Create Language Directory?"),
2678 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2679 // If the user agreed, we try to create it and report if this failed.
2680 if (!sp.createDirectory(0777))
2681 Alert::error(_("Subdirectory creation failed!"),
2682 _("Could not create subdirectory.\n"
2683 "The template will be saved in the parent directory."));
2689 // Do we have a layout category?
2690 string const cat = b.params().baseClass() ?
2691 b.params().baseClass()->category()
2694 string subpath = addPath(result, cat);
2695 // If we have a subdirectory for the category already,
2697 FileName sp = FileName(subpath);
2698 if (sp.isDirectory())
2701 // Ask whether we should create such a subdirectory
2702 docstring const text =
2703 bformat(_("It is suggested to save the template in a subdirectory\n"
2704 "appropriate to the layout category (%1$s).\n"
2705 "This subdirectory does not exists yet.\n"
2706 "Do you want to create it?"),
2708 if (Alert::prompt(_("Create Category Directory?"),
2709 text, 0, 1, _("&Yes, Create"), _("&No, Save Template in Parent Directory")) == 0) {
2710 // If the user agreed, we try to create it and report if this failed.
2711 if (!sp.createDirectory(0777))
2712 Alert::error(_("Subdirectory creation failed!"),
2713 _("Could not create subdirectory.\n"
2714 "The template will be saved in the parent directory."));
2724 bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
2726 FileName fname = b.fileName();
2727 FileName const oldname = fname;
2728 bool const as_template = (kind == LV_WRITE_AS_TEMPLATE);
2730 if (!newname.empty()) {
2733 fname = support::makeAbsPath(to_utf8(newname), getTemplatesPath(b));
2735 fname = support::makeAbsPath(to_utf8(newname),
2736 oldname.onlyPath().absFileName());
2738 // Switch to this Buffer.
2741 // No argument? Ask user through dialog.
2743 QString const title = as_template ? qt_("Choose a filename to save template as")
2744 : qt_("Choose a filename to save document as");
2745 FileDialog dlg(title);
2746 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2747 dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path));
2749 if (!isLyXFileName(fname.absFileName()))
2750 fname.changeExtension(".lyx");
2752 string const path = as_template ?
2754 : fname.onlyPath().absFileName();
2755 FileDialog::Result result =
2756 dlg.save(toqstr(path),
2757 QStringList(qt_("LyX Documents (*.lyx)")),
2758 toqstr(fname.onlyFileName()));
2760 if (result.first == FileDialog::Later)
2763 fname.set(fromqstr(result.second));
2768 if (!isLyXFileName(fname.absFileName()))
2769 fname.changeExtension(".lyx");
2772 // fname is now the new Buffer location.
2774 // if there is already a Buffer open with this name, we do not want
2775 // to have another one. (the second test makes sure we're not just
2776 // trying to overwrite ourselves, which is fine.)
2777 if (theBufferList().exists(fname) && fname != oldname
2778 && theBufferList().getBuffer(fname) != &b) {
2779 docstring const text =
2780 bformat(_("The file\n%1$s\nis already open in your current session.\n"
2781 "Please close it before attempting to overwrite it.\n"
2782 "Do you want to choose a new filename?"),
2783 from_utf8(fname.absFileName()));
2784 int const ret = Alert::prompt(_("Chosen File Already Open"),
2785 text, 0, 1, _("&Rename"), _("&Cancel"));
2787 case 0: return renameBuffer(b, docstring(), kind);
2788 case 1: return false;
2793 bool const existsLocal = fname.exists();
2794 bool const existsInVC = LyXVC::fileInVC(fname);
2795 if (existsLocal || existsInVC) {
2796 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2797 if (kind != LV_WRITE_AS && existsInVC) {
2798 // renaming to a name that is already in VC
2800 docstring text = bformat(_("The document %1$s "
2801 "is already registered.\n\n"
2802 "Do you want to choose a new name?"),
2804 docstring const title = (kind == LV_VC_RENAME) ?
2805 _("Rename document?") : _("Copy document?");
2806 docstring const button = (kind == LV_VC_RENAME) ?
2807 _("&Rename") : _("&Copy");
2808 int const ret = Alert::prompt(title, text, 0, 1,
2809 button, _("&Cancel"));
2811 case 0: return renameBuffer(b, docstring(), kind);
2812 case 1: return false;
2817 docstring text = bformat(_("The document %1$s "
2818 "already exists.\n\n"
2819 "Do you want to overwrite that document?"),
2821 int const ret = Alert::prompt(_("Overwrite document?"),
2822 text, 0, 2, _("&Overwrite"),
2823 _("&Rename"), _("&Cancel"));
2826 case 1: return renameBuffer(b, docstring(), kind);
2827 case 2: return false;
2833 case LV_VC_RENAME: {
2834 string msg = b.lyxvc().rename(fname);
2837 message(from_utf8(msg));
2841 string msg = b.lyxvc().copy(fname);
2844 message(from_utf8(msg));
2848 case LV_WRITE_AS_TEMPLATE:
2851 // LyXVC created the file already in case of LV_VC_RENAME or
2852 // LV_VC_COPY, but call saveBuffer() nevertheless to get
2853 // relative paths of included stuff right if we moved e.g. from
2854 // /a/b.lyx to /a/c/b.lyx.
2856 bool const saved = saveBuffer(b, fname);
2863 bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat)
2865 FileName fname = b.fileName();
2867 FileDialog dlg(qt_("Choose a filename to export the document as"));
2868 dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path));
2871 QString const anyformat = qt_("Guess from extension (*.*)");
2874 vector<Format const *> export_formats;
2875 for (Format const & f : theFormats())
2876 if (f.documentFormat())
2877 export_formats.push_back(&f);
2878 sort(export_formats.begin(), export_formats.end(), Format::formatSorter);
2879 map<QString, string> fmap;
2882 for (Format const * f : export_formats) {
2883 docstring const loc_prettyname = translateIfPossible(f->prettyname());
2884 QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"),
2886 from_ascii(f->extension())));
2887 types << loc_filter;
2888 fmap[loc_filter] = f->name();
2889 if (from_ascii(f->name()) == iformat) {
2890 filter = loc_filter;
2891 ext = f->extension();
2894 string ofname = fname.onlyFileName();
2896 ofname = support::changeExtension(ofname, ext);
2897 FileDialog::Result result =
2898 dlg.save(toqstr(fname.onlyPath().absFileName()),
2902 if (result.first != FileDialog::Chosen)
2906 fname.set(fromqstr(result.second));
2907 if (filter == anyformat)
2908 fmt_name = theFormats().getFormatFromExtension(fname.extension());
2910 fmt_name = fmap[filter];
2911 LYXERR(Debug::FILES, "filter=" << fromqstr(filter)
2912 << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName());
2914 if (fmt_name.empty() || fname.empty())
2917 // fname is now the new Buffer location.
2918 if (FileName(fname).exists()) {
2919 docstring const file = makeDisplayPath(fname.absFileName(), 30);
2920 docstring text = bformat(_("The document %1$s already "
2921 "exists.\n\nDo you want to "
2922 "overwrite that document?"),
2924 int const ret = Alert::prompt(_("Overwrite document?"),
2925 text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
2928 case 1: return exportBufferAs(b, from_ascii(fmt_name));
2929 case 2: return false;
2933 FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName());
2936 return dr.dispatched();
2940 bool GuiView::saveBuffer(Buffer & b)
2942 return saveBuffer(b, FileName());
2946 bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
2948 if (workArea(b) && workArea(b)->inDialogMode())
2951 if (fn.empty() && b.isUnnamed())
2952 return renameBuffer(b, docstring());
2954 bool const success = (fn.empty() ? b.save() : b.saveAs(fn));
2956 theSession().lastFiles().add(b.fileName());
2957 theSession().writeFile();
2961 // Switch to this Buffer.
2964 // FIXME: we don't tell the user *WHY* the save failed !!
2965 docstring const file = makeDisplayPath(b.absFileName(), 30);
2966 docstring text = bformat(_("The document %1$s could not be saved.\n\n"
2967 "Do you want to rename the document and "
2968 "try again?"), file);
2969 int const ret = Alert::prompt(_("Rename and save?"),
2970 text, 0, 2, _("&Rename"), _("&Retry"), _("&Cancel"));
2973 if (!renameBuffer(b, docstring()))
2982 return saveBuffer(b, fn);
2986 bool GuiView::hideWorkArea(GuiWorkArea * wa)
2988 return closeWorkArea(wa, false);
2992 // We only want to close the buffer if it is not visible in other workareas
2993 // of the same view, nor in other views, and if this is not a child
2994 bool GuiView::closeWorkArea(GuiWorkArea * wa)
2996 Buffer & buf = wa->bufferView().buffer();
2998 bool last_wa = d.countWorkAreasOf(buf) == 1
2999 && !inOtherView(buf) && !buf.parent();
3001 bool close_buffer = last_wa;
3004 if (lyxrc.close_buffer_with_last_view == "yes")
3006 else if (lyxrc.close_buffer_with_last_view == "no")
3007 close_buffer = false;
3010 if (buf.isUnnamed())
3011 file = from_utf8(buf.fileName().onlyFileName());
3013 file = buf.fileName().displayName(30);
3014 docstring const text = bformat(
3015 _("Last view on document %1$s is being closed.\n"
3016 "Would you like to close or hide the document?\n"
3018 "Hidden documents can be displayed back through\n"
3019 "the menu: View->Hidden->...\n"
3021 "To remove this question, set your preference in:\n"
3022 " Tools->Preferences->Look&Feel->UserInterface\n"
3024 int ret = Alert::prompt(_("Close or hide document?"),
3025 text, 0, 1, _("&Close"), _("&Hide"));
3026 close_buffer = (ret == 0);
3030 return closeWorkArea(wa, close_buffer);
3034 bool GuiView::closeBuffer()
3036 GuiWorkArea * wa = currentMainWorkArea();
3037 // coverity complained about this
3038 // it seems unnecessary, but perhaps is worth the check
3039 LASSERT(wa, return false);
3041 setCurrentWorkArea(wa);
3042 Buffer & buf = wa->bufferView().buffer();
3043 return closeWorkArea(wa, !buf.parent());
3047 void GuiView::writeSession() const {
3048 GuiWorkArea const * active_wa = currentMainWorkArea();
3049 for (int i = 0; i < d.splitter_->count(); ++i) {
3050 TabWorkArea * twa = d.tabWorkArea(i);
3051 for (int j = 0; j < twa->count(); ++j) {
3052 GuiWorkArea * wa = twa->workArea(j);
3053 Buffer & buf = wa->bufferView().buffer();
3054 theSession().lastOpened().add(buf.fileName(), wa == active_wa);
3060 bool GuiView::closeBufferAll()
3062 // Close the workareas in all other views
3063 QList<int> const ids = guiApp->viewIds();
3064 for (int i = 0; i != ids.size(); ++i) {
3065 if (id_ != ids[i] && !guiApp->view(ids[i]).closeWorkAreaAll())
3069 // Close our own workareas
3070 if (!closeWorkAreaAll())
3073 // Now close the hidden buffers. We prevent hidden buffers from being
3074 // dirty, so we can just close them.
3075 theBufferList().closeAll();
3080 bool GuiView::closeWorkAreaAll()
3082 setCurrentWorkArea(currentMainWorkArea());
3084 // We might be in a situation that there is still a tabWorkArea, but
3085 // there are no tabs anymore. This can happen when we get here after a
3086 // TabWorkArea::lastWorkAreaRemoved() signal. Therefore we count how
3087 // many TabWorkArea's have no documents anymore.
3090 // We have to call count() each time, because it can happen that
3091 // more than one splitter will disappear in one iteration (bug 5998).
3092 while (d.splitter_->count() > empty_twa) {
3093 TabWorkArea * twa = d.tabWorkArea(empty_twa);
3095 if (twa->count() == 0)
3098 setCurrentWorkArea(twa->currentWorkArea());
3099 if (!closeTabWorkArea(twa))
3107 bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer)
3112 Buffer & buf = wa->bufferView().buffer();
3114 if (GuiViewPrivate::busyBuffers.contains(&buf)) {
3115 Alert::warning(_("Close document"),
3116 _("Document could not be closed because it is being processed by LyX."));
3121 return closeBuffer(buf);
3123 if (!inMultiTabs(wa))
3124 if (!saveBufferIfNeeded(buf, true))
3132 bool GuiView::closeBuffer(Buffer & buf)
3134 bool success = true;
3135 ListOfBuffers clist = buf.getChildren();
3136 ListOfBuffers::const_iterator it = clist.begin();
3137 ListOfBuffers::const_iterator const bend = clist.end();
3138 for (; it != bend; ++it) {
3139 Buffer * child_buf = *it;
3140 if (theBufferList().isOthersChild(&buf, child_buf)) {
3141 child_buf->setParent(0);
3145 // FIXME: should we look in other tabworkareas?
3146 // ANSWER: I don't think so. I've tested, and if the child is
3147 // open in some other window, it closes without a problem.
3148 GuiWorkArea * child_wa = workArea(*child_buf);
3151 // If we are in a close_event all children will be closed in some time,
3152 // so no need to do it here. This will ensure that the children end up
3153 // in the session file in the correct order. If we close the master
3154 // buffer, we can close or release the child buffers here too.
3156 success = closeWorkArea(child_wa, true);
3160 // In this case the child buffer is open but hidden.
3161 // Even in this case, children can be dirty (e.g.,
3162 // after a label change in the master, see #11405).
3163 // Therefore, check this
3164 if (closing_ && (child_buf->isClean() || child_buf->paragraphs().empty()))
3165 // If we are in a close_event all children will be closed in some time,
3166 // so no need to do it here. This will ensure that the children end up
3167 // in the session file in the correct order. If we close the master
3168 // buffer, we can close or release the child buffers here too.
3170 // Save dirty buffers also if closing_!
3171 if (saveBufferIfNeeded(*child_buf, false)) {
3172 child_buf->removeAutosaveFile();
3173 theBufferList().release(child_buf);
3175 // Saving of dirty children has been cancelled.
3176 // Cancel the whole process.
3183 // goto bookmark to update bookmark pit.
3184 // FIXME: we should update only the bookmarks related to this buffer!
3185 LYXERR(Debug::DEBUG, "GuiView::closeBuffer()");
3186 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
3187 guiApp->gotoBookmark(i+1, false, false);
3189 if (saveBufferIfNeeded(buf, false)) {
3190 buf.removeAutosaveFile();
3191 theBufferList().release(&buf);
3195 // open all children again to avoid a crash because of dangling
3196 // pointers (bug 6603)
3202 bool GuiView::closeTabWorkArea(TabWorkArea * twa)
3204 while (twa == d.currentTabWorkArea()) {
3205 twa->setCurrentIndex(twa->count() - 1);
3207 GuiWorkArea * wa = twa->currentWorkArea();
3208 Buffer & b = wa->bufferView().buffer();
3210 // We only want to close the buffer if the same buffer is not visible
3211 // in another view, and if this is not a child and if we are closing
3212 // a view (not a tabgroup).
3213 bool const close_buffer =
3214 !inOtherView(b) && !b.parent() && closing_;
3216 if (!closeWorkArea(wa, close_buffer))
3223 bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding)
3225 if (buf.isClean() || buf.paragraphs().empty())
3228 // Switch to this Buffer.
3234 if (buf.isUnnamed()) {
3235 file = from_utf8(buf.fileName().onlyFileName());
3238 FileName filename = buf.fileName();
3240 file = filename.displayName(30);
3241 exists = filename.exists();
3244 // Bring this window to top before asking questions.
3249 if (hiding && buf.isUnnamed()) {
3250 docstring const text = bformat(_("The document %1$s has not been "
3251 "saved yet.\n\nDo you want to save "
3252 "the document?"), file);
3253 ret = Alert::prompt(_("Save new document?"),
3254 text, 0, 1, _("&Save"), _("&Cancel"));
3258 docstring const text = exists ?
3259 bformat(_("The document %1$s has unsaved changes."
3260 "\n\nDo you want to save the document or "
3261 "discard the changes?"), file) :
3262 bformat(_("The document %1$s has not been saved yet."
3263 "\n\nDo you want to save the document or "
3264 "discard it entirely?"), file);
3265 docstring const title = exists ?
3266 _("Save changed document?") : _("Save document?");
3267 ret = Alert::prompt(title, text, 0, 2,
3268 _("&Save"), _("&Discard"), _("&Cancel"));
3273 if (!saveBuffer(buf))
3277 // If we crash after this we could have no autosave file
3278 // but I guess this is really improbable (Jug).
3279 // Sometimes improbable things happen:
3280 // - see bug http://www.lyx.org/trac/ticket/6587 (ps)
3281 // buf.removeAutosaveFile();
3283 // revert all changes
3294 bool GuiView::inMultiTabs(GuiWorkArea * wa)
3296 Buffer & buf = wa->bufferView().buffer();
3298 for (int i = 0; i != d.splitter_->count(); ++i) {
3299 GuiWorkArea * wa_ = d.tabWorkArea(i)->workArea(buf);
3300 if (wa_ && wa_ != wa)
3303 return inOtherView(buf);
3307 bool GuiView::inOtherView(Buffer & buf)
3309 QList<int> const ids = guiApp->viewIds();
3311 for (int i = 0; i != ids.size(); ++i) {
3315 if (guiApp->view(ids[i]).workArea(buf))
3322 void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move)
3324 if (!documentBufferView())
3327 if (TabWorkArea * twa = d.currentTabWorkArea()) {
3328 Buffer * const curbuf = &documentBufferView()->buffer();
3329 int nwa = twa->count();
3330 for (int i = 0; i < nwa; ++i) {
3331 if (&workArea(i)->bufferView().buffer() == curbuf) {
3333 if (np == NEXTBUFFER)
3334 next_index = (i == nwa - 1 ? 0 : i + 1);
3336 next_index = (i == 0 ? nwa - 1 : i - 1);
3338 twa->moveTab(i, next_index);
3340 setBuffer(&workArea(next_index)->bufferView().buffer());
3348 /// make sure the document is saved
3349 static bool ensureBufferClean(Buffer * buffer)
3351 LASSERT(buffer, return false);
3352 if (buffer->isClean() && !buffer->isUnnamed())
3355 docstring const file = buffer->fileName().displayName(30);
3358 if (!buffer->isUnnamed()) {
3359 text = bformat(_("The document %1$s has unsaved "
3360 "changes.\n\nDo you want to save "
3361 "the document?"), file);
3362 title = _("Save changed document?");
3365 text = bformat(_("The document %1$s has not been "
3366 "saved yet.\n\nDo you want to save "
3367 "the document?"), file);
3368 title = _("Save new document?");
3370 int const ret = Alert::prompt(title, text, 0, 1, _("&Save"), _("&Cancel"));
3373 dispatch(FuncRequest(LFUN_BUFFER_WRITE));
3375 return buffer->isClean() && !buffer->isUnnamed();
3379 bool GuiView::reloadBuffer(Buffer & buf)
3381 currentBufferView()->cursor().reset();
3382 Buffer::ReadStatus status = buf.reload();
3383 return status == Buffer::ReadSuccess;
3387 void GuiView::checkExternallyModifiedBuffers()
3389 BufferList::iterator bit = theBufferList().begin();
3390 BufferList::iterator const bend = theBufferList().end();
3391 for (; bit != bend; ++bit) {
3392 Buffer * buf = *bit;
3393 if (buf->fileName().exists() && buf->isChecksumModified()) {
3394 docstring text = bformat(_("Document \n%1$s\n has been externally modified."
3395 " Reload now? Any local changes will be lost."),
3396 from_utf8(buf->absFileName()));
3397 int const ret = Alert::prompt(_("Reload externally changed document?"),
3398 text, 0, 1, _("&Reload"), _("&Cancel"));
3406 void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
3408 Buffer * buffer = documentBufferView()
3409 ? &(documentBufferView()->buffer()) : 0;
3411 switch (cmd.action()) {
3412 case LFUN_VC_REGISTER:
3413 if (!buffer || !ensureBufferClean(buffer))
3415 if (!buffer->lyxvc().inUse()) {
3416 if (buffer->lyxvc().registrer()) {
3417 reloadBuffer(*buffer);
3418 dr.clearMessageUpdate();
3423 case LFUN_VC_RENAME:
3424 case LFUN_VC_COPY: {
3425 if (!buffer || !ensureBufferClean(buffer))
3427 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3428 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3429 // Some changes are not yet committed.
3430 // We test here and not in getStatus(), since
3431 // this test is expensive.
3433 LyXVC::CommandResult ret =
3434 buffer->lyxvc().checkIn(log);
3436 if (ret == LyXVC::ErrorCommand ||
3437 ret == LyXVC::VCSuccess)
3438 reloadBuffer(*buffer);
3439 if (buffer->lyxvc().isCheckInWithConfirmation()) {
3440 frontend::Alert::error(
3441 _("Revision control error."),
3442 _("Document could not be checked in."));
3446 RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
3447 LV_VC_RENAME : LV_VC_COPY;
3448 renameBuffer(*buffer, cmd.argument(), kind);
3453 case LFUN_VC_CHECK_IN:
3454 if (!buffer || !ensureBufferClean(buffer))
3456 if (buffer->lyxvc().inUse() && !buffer->hasReadonlyFlag()) {
3458 LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log);
3460 // Only skip reloading if the checkin was cancelled or
3461 // an error occurred before the real checkin VCS command
3462 // was executed, since the VCS might have changed the
3463 // file even if it could not checkin successfully.
3464 if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess)
3465 reloadBuffer(*buffer);
3469 case LFUN_VC_CHECK_OUT:
3470 if (!buffer || !ensureBufferClean(buffer))
3472 if (buffer->lyxvc().inUse()) {
3473 dr.setMessage(buffer->lyxvc().checkOut());
3474 reloadBuffer(*buffer);
3478 case LFUN_VC_LOCKING_TOGGLE:
3479 LASSERT(buffer, return);
3480 if (!ensureBufferClean(buffer) || buffer->hasReadonlyFlag())
3482 if (buffer->lyxvc().inUse()) {
3483 string res = buffer->lyxvc().lockingToggle();
3485 frontend::Alert::error(_("Revision control error."),
3486 _("Error when setting the locking property."));
3489 reloadBuffer(*buffer);
3494 case LFUN_VC_REVERT:
3495 LASSERT(buffer, return);
3496 if (buffer->lyxvc().revert()) {
3497 reloadBuffer(*buffer);
3498 dr.clearMessageUpdate();
3502 case LFUN_VC_UNDO_LAST:
3503 LASSERT(buffer, return);
3504 buffer->lyxvc().undoLast();
3505 reloadBuffer(*buffer);
3506 dr.clearMessageUpdate();
3509 case LFUN_VC_REPO_UPDATE:
3510 LASSERT(buffer, return);
3511 if (ensureBufferClean(buffer)) {
3512 dr.setMessage(buffer->lyxvc().repoUpdate());
3513 checkExternallyModifiedBuffers();
3517 case LFUN_VC_COMMAND: {
3518 string flag = cmd.getArg(0);
3519 if (buffer && contains(flag, 'R') && !ensureBufferClean(buffer))
3522 if (contains(flag, 'M')) {
3523 if (!Alert::askForText(message, _("LyX VC: Log Message")))
3526 string path = cmd.getArg(1);
3527 if (contains(path, "$$p") && buffer)
3528 path = subst(path, "$$p", buffer->filePath());
3529 LYXERR(Debug::LYXVC, "Directory: " << path);
3531 if (!pp.isReadableDirectory()) {
3532 lyxerr << _("Directory is not accessible.") << endl;
3535 support::PathChanger p(pp);
3537 string command = cmd.getArg(2);
3538 if (command.empty())
3541 command = subst(command, "$$i", buffer->absFileName());
3542 command = subst(command, "$$p", buffer->filePath());
3544 command = subst(command, "$$m", to_utf8(message));
3545 LYXERR(Debug::LYXVC, "Command: " << command);
3547 one.startscript(Systemcall::Wait, command);
3551 if (contains(flag, 'I'))
3552 buffer->markDirty();
3553 if (contains(flag, 'R'))
3554 reloadBuffer(*buffer);
3559 case LFUN_VC_COMPARE: {
3560 if (cmd.argument().empty()) {
3561 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "comparehistory"));
3565 string rev1 = cmd.getArg(0);
3570 if (!buffer->lyxvc().prepareFileRevision(rev1, f1))
3573 if (isStrInt(rev1) && convert<int>(rev1) <= 0) {
3574 f2 = buffer->absFileName();
3576 string rev2 = cmd.getArg(1);
3580 if (!buffer->lyxvc().prepareFileRevision(rev2, f2))
3584 LYXERR(Debug::LYXVC, "Launching comparison for fetched revisions:\n" <<
3585 f1 << "\n" << f2 << "\n" );
3586 string par = "compare run " + quoteName(f1) + " " + quoteName(f2);
3587 lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, par));
3597 void GuiView::openChildDocument(string const & fname)
3599 LASSERT(documentBufferView(), return);
3600 Buffer & buffer = documentBufferView()->buffer();
3601 FileName const filename = support::makeAbsPath(fname, buffer.filePath());
3602 documentBufferView()->saveBookmark(false);
3604 if (theBufferList().exists(filename)) {
3605 child = theBufferList().getBuffer(filename);
3608 message(bformat(_("Opening child document %1$s..."),
3609 makeDisplayPath(filename.absFileName())));
3610 child = loadDocument(filename, false);
3612 // Set the parent name of the child document.
3613 // This makes insertion of citations and references in the child work,
3614 // when the target is in the parent or another child document.
3616 child->setParent(&buffer);
3620 bool GuiView::goToFileRow(string const & argument)
3624 size_t i = argument.find_last_of(' ');
3625 if (i != string::npos) {
3626 file_name = os::internal_path(trim(argument.substr(0, i)));
3627 istringstream is(argument.substr(i + 1));
3632 if (i == string::npos) {
3633 LYXERR0("Wrong argument: " << argument);
3637 string const abstmp = package().temp_dir().absFileName();
3638 string const realtmp = package().temp_dir().realPath();
3639 // We have to use os::path_prefix_is() here, instead of
3640 // simply prefixIs(), because the file name comes from
3641 // an external application and may need case adjustment.
3642 if (os::path_prefix_is(file_name, abstmp, os::CASE_ADJUSTED)
3643 || os::path_prefix_is(file_name, realtmp, os::CASE_ADJUSTED)) {
3644 // Needed by inverse dvi search. If it is a file
3645 // in tmpdir, call the apropriated function.
3646 // If tmpdir is a symlink, we may have the real
3647 // path passed back, so we correct for that.
3648 if (!prefixIs(file_name, abstmp))
3649 file_name = subst(file_name, realtmp, abstmp);
3650 buf = theBufferList().getBufferFromTmp(file_name);
3652 // Must replace extension of the file to be .lyx
3653 // and get full path
3654 FileName const s = fileSearch(string(),
3655 support::changeExtension(file_name, ".lyx"), "lyx");
3656 // Either change buffer or load the file
3657 if (theBufferList().exists(s))
3658 buf = theBufferList().getBuffer(s);
3659 else if (s.exists()) {
3660 buf = loadDocument(s);
3665 _("File does not exist: %1$s"),
3666 makeDisplayPath(file_name)));
3672 _("No buffer for file: %1$s."),
3673 makeDisplayPath(file_name))
3678 bool success = documentBufferView()->setCursorFromRow(row);
3680 LYXERR(Debug::LATEX,
3681 "setCursorFromRow: invalid position for row " << row);
3682 frontend::Alert::error(_("Inverse Search Failed"),
3683 _("Invalid position requested by inverse search.\n"
3684 "You may need to update the viewed document."));
3690 void GuiView::toolBarPopup(const QPoint & /*pos*/)
3692 QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this);
3693 menu->exec(QCursor::pos());
3698 Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func,
3699 Buffer const * orig, Buffer * clone, string const & format)
3701 Buffer::ExportStatus const status = func(format);
3703 // the cloning operation will have produced a clone of the entire set of
3704 // documents, starting from the master. so we must delete those.
3705 Buffer * mbuf = const_cast<Buffer *>(clone->masterBuffer());
3707 busyBuffers.remove(orig);
3712 Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(
3713 Buffer const * orig, Buffer * clone, string const & format)
3715 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3717 return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format);
3721 Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(
3722 Buffer const * orig, Buffer * clone, string const & format)
3724 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const =
3726 return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format);
3730 Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(
3731 Buffer const * orig, Buffer * clone, string const & format)
3733 Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const =
3735 return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format);
3739 bool GuiView::GuiViewPrivate::asyncBufferProcessing(
3740 string const & argument,
3741 Buffer const * used_buffer,
3742 docstring const & msg,
3743 Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &),
3744 Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const,
3745 Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const,
3751 string format = argument;
3753 format = used_buffer->params().getDefaultOutputFormat();
3754 processing_format = format;
3756 progress_->clearMessages();
3759 #if EXPORT_in_THREAD
3761 GuiViewPrivate::busyBuffers.insert(used_buffer);
3762 Buffer * cloned_buffer = used_buffer->cloneWithChildren();
3763 if (!cloned_buffer) {
3764 Alert::error(_("Export Error"),
3765 _("Error cloning the Buffer."));
3768 QFuture<Buffer::ExportStatus> f = QtConcurrent::run(
3773 setPreviewFuture(f);
3774 last_export_format = used_buffer->params().bufferFormat();
3777 // We are asynchronous, so we don't know here anything about the success
3780 Buffer::ExportStatus status;
3782 status = (used_buffer->*syncFunc)(format, false);
3783 } else if (previewFunc) {
3784 status = (used_buffer->*previewFunc)(format);
3787 handleExportStatus(gv_, status, format);
3789 return (status == Buffer::ExportSuccess
3790 || status == Buffer::PreviewSuccess);
3794 Buffer::ExportStatus status;
3796 status = (used_buffer->*syncFunc)(format, true);
3797 } else if (previewFunc) {
3798 status = (used_buffer->*previewFunc)(format);
3801 handleExportStatus(gv_, status, format);
3803 return (status == Buffer::ExportSuccess
3804 || status == Buffer::PreviewSuccess);
3808 void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr)
3810 BufferView * bv = currentBufferView();
3811 LASSERT(bv, return);
3813 // Let the current BufferView dispatch its own actions.
3814 bv->dispatch(cmd, dr);
3815 if (dr.dispatched()) {
3816 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3817 updateDialog("document", "");
3821 // Try with the document BufferView dispatch if any.
3822 BufferView * doc_bv = documentBufferView();
3823 if (doc_bv && doc_bv != bv) {
3824 doc_bv->dispatch(cmd, dr);
3825 if (dr.dispatched()) {
3826 if (cmd.action() == LFUN_REDO || cmd.action() == LFUN_UNDO)
3827 updateDialog("document", "");
3832 // Then let the current Cursor dispatch its own actions.
3833 bv->cursor().dispatch(cmd);
3835 // update completion. We do it here and not in
3836 // processKeySym to avoid another redraw just for a
3837 // changed inline completion
3838 if (cmd.origin() == FuncRequest::KEYBOARD) {
3839 if (cmd.action() == LFUN_SELF_INSERT
3840 || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed()))
3841 updateCompletion(bv->cursor(), true, true);
3842 else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD)
3843 updateCompletion(bv->cursor(), false, true);
3845 updateCompletion(bv->cursor(), false, false);
3848 dr = bv->cursor().result();
3852 void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
3854 BufferView * bv = currentBufferView();
3855 // By default we won't need any update.
3856 dr.screenUpdate(Update::None);
3857 // assume cmd will be dispatched
3858 dr.dispatched(true);
3860 Buffer * doc_buffer = documentBufferView()
3861 ? &(documentBufferView()->buffer()) : 0;
3863 if (cmd.origin() == FuncRequest::TOC) {
3864 GuiToc * toc = static_cast<GuiToc*>(findOrBuild("toc", false));
3865 // FIXME: do we need to pass a DispatchResult object here?
3866 toc->doDispatch(bv->cursor(), cmd);
3870 string const argument = to_utf8(cmd.argument());
3872 switch(cmd.action()) {
3873 case LFUN_BUFFER_CHILD_OPEN:
3874 openChildDocument(to_utf8(cmd.argument()));
3877 case LFUN_BUFFER_IMPORT:
3878 importDocument(to_utf8(cmd.argument()));
3881 case LFUN_MASTER_BUFFER_EXPORT:
3883 doc_buffer = const_cast<Buffer *>(doc_buffer->masterBuffer());
3885 case LFUN_BUFFER_EXPORT: {
3888 // GCC only sees strfwd.h when building merged
3889 if (::lyx::operator==(cmd.argument(), "custom")) {
3890 // LFUN_MASTER_BUFFER_EXPORT is not enabled for this case,
3891 // so the following test should not be needed.
3892 // In principle, we could try to switch to such a view...
3893 // if (cmd.action() == LFUN_BUFFER_EXPORT)
3894 dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr);
3898 string const dest = cmd.getArg(1);
3899 FileName target_dir;
3900 if (!dest.empty() && FileName::isAbsolute(dest))
3901 target_dir = FileName(support::onlyPath(dest));
3903 target_dir = doc_buffer->fileName().onlyPath();
3905 string const format = (argument.empty() || argument == "default") ?
3906 doc_buffer->params().getDefaultOutputFormat() : argument;
3908 if ((dest.empty() && doc_buffer->isUnnamed())
3909 || !target_dir.isDirWritable()) {
3910 exportBufferAs(*doc_buffer, from_utf8(format));
3913 /* TODO/Review: Is it a problem to also export the children?
3914 See the update_unincluded flag */
3915 d.asyncBufferProcessing(format,
3918 &GuiViewPrivate::exportAndDestroy,
3920 0, cmd.allowAsync());
3921 // TODO Inform user about success
3925 case LFUN_BUFFER_EXPORT_AS: {
3926 LASSERT(doc_buffer, break);
3927 docstring f = cmd.argument();
3929 f = from_ascii(doc_buffer->params().getDefaultOutputFormat());
3930 exportBufferAs(*doc_buffer, f);
3934 case LFUN_BUFFER_UPDATE: {
3935 d.asyncBufferProcessing(argument,
3938 &GuiViewPrivate::compileAndDestroy,
3940 0, cmd.allowAsync());
3943 case LFUN_BUFFER_VIEW: {
3944 d.asyncBufferProcessing(argument,
3946 _("Previewing ..."),
3947 &GuiViewPrivate::previewAndDestroy,
3949 &Buffer::preview, cmd.allowAsync());
3952 case LFUN_MASTER_BUFFER_UPDATE: {
3953 d.asyncBufferProcessing(argument,
3954 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3956 &GuiViewPrivate::compileAndDestroy,
3958 0, cmd.allowAsync());
3961 case LFUN_MASTER_BUFFER_VIEW: {
3962 d.asyncBufferProcessing(argument,
3963 (doc_buffer ? doc_buffer->masterBuffer() : 0),
3965 &GuiViewPrivate::previewAndDestroy,
3966 0, &Buffer::preview, cmd.allowAsync());
3969 case LFUN_EXPORT_CANCEL: {
3970 Systemcall::killscript();
3973 case LFUN_BUFFER_SWITCH: {
3974 string const file_name = to_utf8(cmd.argument());
3975 if (!FileName::isAbsolute(file_name)) {
3977 dr.setMessage(_("Absolute filename expected."));
3981 Buffer * buffer = theBufferList().getBuffer(FileName(file_name));
3984 dr.setMessage(_("Document not loaded"));
3988 // Do we open or switch to the buffer in this view ?
3989 if (workArea(*buffer)
3990 || lyxrc.open_buffers_in_tabs || !documentBufferView()) {
3995 // Look for the buffer in other views
3996 QList<int> const ids = guiApp->viewIds();
3998 for (; i != ids.size(); ++i) {
3999 GuiView & gv = guiApp->view(ids[i]);
4000 if (gv.workArea(*buffer)) {
4002 gv.activateWindow();
4004 gv.setBuffer(buffer);
4009 // If necessary, open a new window as a last resort
4010 if (i == ids.size()) {
4011 lyx::dispatch(FuncRequest(LFUN_WINDOW_NEW));
4017 case LFUN_BUFFER_NEXT:
4018 gotoNextOrPreviousBuffer(NEXTBUFFER, false);
4021 case LFUN_BUFFER_MOVE_NEXT:
4022 gotoNextOrPreviousBuffer(NEXTBUFFER, true);
4025 case LFUN_BUFFER_PREVIOUS:
4026 gotoNextOrPreviousBuffer(PREVBUFFER, false);
4029 case LFUN_BUFFER_MOVE_PREVIOUS:
4030 gotoNextOrPreviousBuffer(PREVBUFFER, true);
4033 case LFUN_BUFFER_CHKTEX:
4034 LASSERT(doc_buffer, break);
4035 doc_buffer->runChktex();
4038 case LFUN_COMMAND_EXECUTE: {
4039 command_execute_ = true;
4040 minibuffer_focus_ = true;
4043 case LFUN_DROP_LAYOUTS_CHOICE:
4044 d.layout_->showPopup();
4047 case LFUN_MENU_OPEN:
4048 if (QMenu * menu = guiApp->menus().menu(toqstr(cmd.argument()), *this))
4049 menu->exec(QCursor::pos());
4052 case LFUN_FILE_INSERT: {
4053 if (cmd.getArg(1) == "ignorelang")
4054 insertLyXFile(from_utf8(cmd.getArg(0)), true);
4056 insertLyXFile(cmd.argument());
4060 case LFUN_FILE_INSERT_PLAINTEXT:
4061 case LFUN_FILE_INSERT_PLAINTEXT_PARA: {
4062 string const fname = to_utf8(cmd.argument());
4063 if (!fname.empty() && !FileName::isAbsolute(fname)) {
4064 dr.setMessage(_("Absolute filename expected."));
4068 FileName filename(fname);
4069 if (fname.empty()) {
4070 FileDialog dlg(qt_("Select file to insert"));
4072 FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()),
4073 QStringList(qt_("All Files (*)")));
4075 if (result.first == FileDialog::Later || result.second.isEmpty()) {
4076 dr.setMessage(_("Canceled."));
4080 filename.set(fromqstr(result.second));
4084 FuncRequest const new_cmd(cmd, filename.absoluteFilePath());
4085 bv->dispatch(new_cmd, dr);
4090 case LFUN_BUFFER_RELOAD: {
4091 LASSERT(doc_buffer, break);
4094 bool drop = (cmd.argument() == "dump");
4097 if (!drop && !doc_buffer->isClean()) {
4098 docstring const file =
4099 makeDisplayPath(doc_buffer->absFileName(), 20);
4100 if (doc_buffer->notifiesExternalModification()) {
4101 docstring text = _("The current version will be lost. "
4102 "Are you sure you want to load the version on disk "
4103 "of the document %1$s?");
4104 ret = Alert::prompt(_("Reload saved document?"),
4105 bformat(text, file), 1, 1,
4106 _("&Reload"), _("&Cancel"));
4108 docstring text = _("Any changes will be lost. "
4109 "Are you sure you want to revert to the saved version "
4110 "of the document %1$s?");
4111 ret = Alert::prompt(_("Revert to saved document?"),
4112 bformat(text, file), 1, 1,
4113 _("&Revert"), _("&Cancel"));
4118 doc_buffer->markClean();
4119 reloadBuffer(*doc_buffer);
4120 dr.forceBufferUpdate();
4125 case LFUN_BUFFER_WRITE:
4126 LASSERT(doc_buffer, break);
4127 saveBuffer(*doc_buffer);
4130 case LFUN_BUFFER_WRITE_AS:
4131 LASSERT(doc_buffer, break);
4132 renameBuffer(*doc_buffer, cmd.argument());
4135 case LFUN_BUFFER_WRITE_AS_TEMPLATE:
4136 LASSERT(doc_buffer, break);
4137 renameBuffer(*doc_buffer, cmd.argument(),
4138 LV_WRITE_AS_TEMPLATE);
4141 case LFUN_BUFFER_WRITE_ALL: {
4142 Buffer * first = theBufferList().first();
4145 message(_("Saving all documents..."));
4146 // We cannot use a for loop as the buffer list cycles.
4149 if (!b->isClean()) {
4151 LYXERR(Debug::ACTION, "Saved " << b->absFileName());
4153 b = theBufferList().next(b);
4154 } while (b != first);
4155 dr.setMessage(_("All documents saved."));
4159 case LFUN_BUFFER_EXTERNAL_MODIFICATION_CLEAR:
4160 LASSERT(doc_buffer, break);
4161 doc_buffer->clearExternalModification();
4164 case LFUN_BUFFER_CLOSE:
4168 case LFUN_BUFFER_CLOSE_ALL:
4172 case LFUN_DEVEL_MODE_TOGGLE:
4173 devel_mode_ = !devel_mode_;
4175 dr.setMessage(_("Developer mode is now enabled."));
4177 dr.setMessage(_("Developer mode is now disabled."));
4180 case LFUN_TOOLBAR_TOGGLE: {
4181 string const name = cmd.getArg(0);
4182 if (GuiToolbar * t = toolbar(name))
4187 case LFUN_TOOLBAR_MOVABLE: {
4188 string const name = cmd.getArg(0);
4190 // toggle (all) toolbars movablility
4191 toolbarsMovable_ = !toolbarsMovable_;
4192 for (ToolbarInfo const & ti : guiApp->toolbars()) {
4193 GuiToolbar * tb = toolbar(ti.name);
4194 if (tb && tb->isMovable() != toolbarsMovable_)
4195 // toggle toolbar movablity if it does not fit lock
4196 // (all) toolbars positions state silent = true, since
4197 // status bar notifications are slow
4200 if (toolbarsMovable_)
4201 dr.setMessage(_("Toolbars unlocked."));
4203 dr.setMessage(_("Toolbars locked."));
4204 } else if (GuiToolbar * t = toolbar(name)) {
4205 // toggle current toolbar movablity
4207 // update lock (all) toolbars positions
4208 updateLockToolbars();
4213 case LFUN_ICON_SIZE: {
4214 QSize size = d.iconSize(cmd.argument());
4216 dr.setMessage(bformat(_("Icon size set to %1$dx%2$d."),
4217 size.width(), size.height()));
4221 case LFUN_DIALOG_UPDATE: {
4222 string const name = to_utf8(cmd.argument());
4223 if (name == "prefs" || name == "document")
4224 updateDialog(name, string());
4225 else if (name == "paragraph")
4226 lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_UPDATE));
4227 else if (currentBufferView()) {
4228 Inset * inset = currentBufferView()->editedInset(name);
4229 // Can only update a dialog connected to an existing inset
4231 // FIXME: get rid of this indirection; GuiView ask the inset
4232 // if he is kind enough to update itself...
4233 FuncRequest fr(LFUN_INSET_DIALOG_UPDATE, cmd.argument());
4234 //FIXME: pass DispatchResult here?
4235 inset->dispatch(currentBufferView()->cursor(), fr);
4241 case LFUN_DIALOG_TOGGLE: {
4242 FuncCode const func_code = isDialogVisible(cmd.getArg(0))
4243 ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW;
4244 dispatch(FuncRequest(func_code, cmd.argument()), dr);
4248 case LFUN_DIALOG_DISCONNECT_INSET:
4249 disconnectDialog(to_utf8(cmd.argument()));
4252 case LFUN_DIALOG_HIDE: {
4253 guiApp->hideDialogs(to_utf8(cmd.argument()), 0);
4257 case LFUN_DIALOG_SHOW: {
4258 string const name = cmd.getArg(0);
4259 string sdata = trim(to_utf8(cmd.argument()).substr(name.size()));
4261 if (name == "latexlog") {
4262 // gettatus checks that
4263 LATTEST(doc_buffer);
4264 Buffer::LogType type;
4265 string const logfile = doc_buffer->logName(&type);
4267 case Buffer::latexlog:
4270 case Buffer::buildlog:
4271 sdata = "literate ";
4274 sdata += Lexer::quoteString(logfile);
4275 showDialog("log", sdata);
4276 } else if (name == "vclog") {
4277 // getStatus checks that
4278 LATTEST(doc_buffer);
4279 string const sdata2 = "vc " +
4280 Lexer::quoteString(doc_buffer->lyxvc().getLogFile());
4281 showDialog("log", sdata2);
4282 } else if (name == "symbols") {
4283 sdata = bv->cursor().getEncoding()->name();
4285 showDialog("symbols", sdata);
4287 } else if (name == "prefs" && isFullScreen()) {
4288 lfunUiToggle("fullscreen");
4289 showDialog("prefs", sdata);
4291 showDialog(name, sdata);
4296 dr.setMessage(cmd.argument());
4299 case LFUN_UI_TOGGLE: {
4300 string arg = cmd.getArg(0);
4301 if (!lfunUiToggle(arg)) {
4302 docstring const msg = "ui-toggle " + _("%1$s unknown command!");
4303 dr.setMessage(bformat(msg, from_utf8(arg)));
4305 // Make sure the keyboard focus stays in the work area.
4310 case LFUN_VIEW_SPLIT: {
4311 LASSERT(doc_buffer, break);
4312 string const orientation = cmd.getArg(0);
4313 d.splitter_->setOrientation(orientation == "vertical"
4314 ? Qt::Vertical : Qt::Horizontal);
4315 TabWorkArea * twa = addTabWorkArea();
4316 GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this);
4317 setCurrentWorkArea(wa);
4320 case LFUN_TAB_GROUP_CLOSE:
4321 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4322 closeTabWorkArea(twa);
4323 d.current_work_area_ = 0;
4324 twa = d.currentTabWorkArea();
4325 // Switch to the next GuiWorkArea in the found TabWorkArea.
4327 // Make sure the work area is up to date.
4328 setCurrentWorkArea(twa->currentWorkArea());
4330 setCurrentWorkArea(0);
4335 case LFUN_VIEW_CLOSE:
4336 if (TabWorkArea * twa = d.currentTabWorkArea()) {
4337 closeWorkArea(twa->currentWorkArea());
4338 d.current_work_area_ = 0;
4339 twa = d.currentTabWorkArea();
4340 // Switch to the next GuiWorkArea in the found TabWorkArea.
4342 // Make sure the work area is up to date.
4343 setCurrentWorkArea(twa->currentWorkArea());
4345 setCurrentWorkArea(0);
4350 case LFUN_COMPLETION_INLINE:
4351 if (d.current_work_area_)
4352 d.current_work_area_->completer().showInline();
4355 case LFUN_COMPLETION_POPUP:
4356 if (d.current_work_area_)
4357 d.current_work_area_->completer().showPopup();
4362 if (d.current_work_area_)
4363 d.current_work_area_->completer().tab();
4366 case LFUN_COMPLETION_CANCEL:
4367 if (d.current_work_area_) {
4368 if (d.current_work_area_->completer().popupVisible())
4369 d.current_work_area_->completer().hidePopup();
4371 d.current_work_area_->completer().hideInline();
4375 case LFUN_COMPLETION_ACCEPT:
4376 if (d.current_work_area_)
4377 d.current_work_area_->completer().activate();
4380 case LFUN_BUFFER_ZOOM_IN:
4381 case LFUN_BUFFER_ZOOM_OUT:
4382 case LFUN_BUFFER_ZOOM: {
4383 if (cmd.argument().empty()) {
4384 if (cmd.action() == LFUN_BUFFER_ZOOM)
4386 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4391 if (cmd.action() == LFUN_BUFFER_ZOOM)
4392 zoom_ratio_ = convert<int>(cmd.argument()) / double(lyxrc.defaultZoom);
4393 else if (cmd.action() == LFUN_BUFFER_ZOOM_IN)
4394 zoom_ratio_ += convert<int>(cmd.argument()) / 100.0;
4396 zoom_ratio_ -= convert<int>(cmd.argument()) / 100.0;
4399 // Actual zoom value: default zoom + fractional extra value
4400 int zoom = lyxrc.defaultZoom * zoom_ratio_;
4401 if (zoom < static_cast<int>(zoom_min_))
4404 lyxrc.currentZoom = zoom;
4406 dr.setMessage(bformat(_("Zoom level is now %1$d% (default value: %2$d%)"),
4407 lyxrc.currentZoom, lyxrc.defaultZoom));
4409 guiApp->fontLoader().update();
4410 dr.screenUpdate(Update::Force | Update::FitCursor);
4414 case LFUN_VC_REGISTER:
4415 case LFUN_VC_RENAME:
4417 case LFUN_VC_CHECK_IN:
4418 case LFUN_VC_CHECK_OUT:
4419 case LFUN_VC_REPO_UPDATE:
4420 case LFUN_VC_LOCKING_TOGGLE:
4421 case LFUN_VC_REVERT:
4422 case LFUN_VC_UNDO_LAST:
4423 case LFUN_VC_COMMAND:
4424 case LFUN_VC_COMPARE:
4425 dispatchVC(cmd, dr);
4428 case LFUN_SERVER_GOTO_FILE_ROW:
4429 if(goToFileRow(to_utf8(cmd.argument())))
4430 dr.screenUpdate(Update::Force | Update::FitCursor);
4433 case LFUN_LYX_ACTIVATE:
4437 case LFUN_FORWARD_SEARCH: {
4438 // it seems safe to assume we have a document buffer, since
4439 // getStatus wants one.
4440 LATTEST(doc_buffer);
4441 Buffer const * doc_master = doc_buffer->masterBuffer();
4442 FileName const path(doc_master->temppath());
4443 string const texname = doc_master->isChild(doc_buffer)
4444 ? DocFileName(changeExtension(
4445 doc_buffer->absFileName(),
4446 "tex")).mangledFileName()
4447 : doc_buffer->latexName();
4448 string const fulltexname =
4449 support::makeAbsPath(texname, doc_master->temppath()).absFileName();
4450 string const mastername =
4451 removeExtension(doc_master->latexName());
4452 FileName const dviname(addName(path.absFileName(),
4453 addExtension(mastername, "dvi")));
4454 FileName const pdfname(addName(path.absFileName(),
4455 addExtension(mastername, "pdf")));
4456 bool const have_dvi = dviname.exists();
4457 bool const have_pdf = pdfname.exists();
4458 if (!have_dvi && !have_pdf) {
4459 dr.setMessage(_("Please, preview the document first."));
4462 string outname = dviname.onlyFileName();
4463 string command = lyxrc.forward_search_dvi;
4464 if (!have_dvi || (have_pdf &&
4465 pdfname.lastModified() > dviname.lastModified())) {
4466 outname = pdfname.onlyFileName();
4467 command = lyxrc.forward_search_pdf;
4470 DocIterator cur = bv->cursor();
4471 int row = doc_buffer->texrow().rowFromDocIterator(cur).first;
4472 LYXERR(Debug::ACTION, "Forward search: row:" << row
4474 if (row == -1 || command.empty()) {
4475 dr.setMessage(_("Couldn't proceed."));
4478 string texrow = convert<string>(row);
4480 command = subst(command, "$$n", texrow);
4481 command = subst(command, "$$f", fulltexname);
4482 command = subst(command, "$$t", texname);
4483 command = subst(command, "$$o", outname);
4485 PathChanger p(path);
4487 one.startscript(Systemcall::DontWait, command);
4491 case LFUN_SPELLING_CONTINUOUSLY:
4492 lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously;
4493 dr.screenUpdate(Update::Force);
4497 // The LFUN must be for one of BufferView, Buffer or Cursor;
4499 dispatchToBufferView(cmd, dr);
4503 // Part of automatic menu appearance feature.
4504 if (isFullScreen()) {
4505 if (menuBar()->isVisible() && lyxrc.full_screen_menubar)
4509 // Need to update bv because many LFUNs here might have destroyed it
4510 bv = currentBufferView();
4512 // Clear non-empty selections
4513 // (e.g. from a "char-forward-select" followed by "char-backward-select")
4515 Cursor & cur = bv->cursor();
4516 if ((cur.selection() && cur.selBegin() == cur.selEnd())) {
4517 cur.clearSelection();
4523 bool GuiView::lfunUiToggle(string const & ui_component)
4525 if (ui_component == "scrollbar") {
4526 // hide() is of no help
4527 if (d.current_work_area_->verticalScrollBarPolicy() ==
4528 Qt::ScrollBarAlwaysOff)
4530 d.current_work_area_->setVerticalScrollBarPolicy(
4531 Qt::ScrollBarAsNeeded);
4533 d.current_work_area_->setVerticalScrollBarPolicy(
4534 Qt::ScrollBarAlwaysOff);
4535 } else if (ui_component == "statusbar") {
4536 statusBar()->setVisible(!statusBar()->isVisible());
4537 } else if (ui_component == "menubar") {
4538 menuBar()->setVisible(!menuBar()->isVisible());
4540 if (ui_component == "frame") {
4542 getContentsMargins(&l, &t, &r, &b);
4543 //are the frames in default state?
4544 d.current_work_area_->setFrameStyle(QFrame::NoFrame);
4546 setContentsMargins(-2, -2, -2, -2);
4548 setContentsMargins(0, 0, 0, 0);
4551 if (ui_component == "fullscreen") {
4559 void GuiView::toggleFullScreen()
4561 if (isFullScreen()) {
4562 for (int i = 0; i != d.splitter_->count(); ++i)
4563 d.tabWorkArea(i)->setFullScreen(false);
4564 setContentsMargins(0, 0, 0, 0);
4565 setWindowState(windowState() ^ Qt::WindowFullScreen);
4568 statusBar()->show();
4571 hideDialogs("prefs", 0);
4572 for (int i = 0; i != d.splitter_->count(); ++i)
4573 d.tabWorkArea(i)->setFullScreen(true);
4574 setContentsMargins(-2, -2, -2, -2);
4576 setWindowState(windowState() ^ Qt::WindowFullScreen);
4577 if (lyxrc.full_screen_statusbar)
4578 statusBar()->hide();
4579 if (lyxrc.full_screen_menubar)
4581 if (lyxrc.full_screen_toolbars) {
4582 ToolbarMap::iterator end = d.toolbars_.end();
4583 for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it)
4588 // give dialogs like the TOC a chance to adapt
4593 Buffer const * GuiView::updateInset(Inset const * inset)
4598 Buffer const * inset_buffer = &(inset->buffer());
4600 for (int i = 0; i != d.splitter_->count(); ++i) {
4601 GuiWorkArea * wa = d.tabWorkArea(i)->currentWorkArea();
4604 Buffer const * buffer = &(wa->bufferView().buffer());
4605 if (inset_buffer == buffer)
4606 wa->scheduleRedraw(true);
4608 return inset_buffer;
4612 void GuiView::restartCaret()
4614 /* When we move around, or type, it's nice to be able to see
4615 * the caret immediately after the keypress.
4617 if (d.current_work_area_)
4618 d.current_work_area_->startBlinkingCaret();
4620 // Take this occasion to update the other GUI elements.
4626 void GuiView::updateCompletion(Cursor & cur, bool start, bool keep)
4628 if (d.current_work_area_)
4629 d.current_work_area_->completer().updateVisibility(cur, start, keep);
4634 // This list should be kept in sync with the list of insets in
4635 // src/insets/Inset.cpp. I.e., if a dialog goes with an inset, the
4636 // dialog should have the same name as the inset.
4637 // Changes should be also recorded in LFUN_DIALOG_SHOW doxygen
4638 // docs in LyXAction.cpp.
4640 char const * const dialognames[] = {
4642 "aboutlyx", "bibitem", "bibtex", "box", "branch", "changes", "character",
4643 "citation", "compare", "comparehistory", "document", "errorlist", "ert",
4644 "external", "file", "findreplace", "findreplaceadv", "float", "graphics",
4645 "href", "include", "index", "index_print", "info", "listings", "label", "line",
4646 "log", "lyxfiles", "mathdelimiter", "mathmatrix", "mathspace", "nomenclature",
4647 "nomencl_print", "note", "paragraph", "phantom", "prefs", "ref",
4648 "sendto", "space", "spellchecker", "symbols", "tabular", "tabularcreate",
4649 "thesaurus", "texinfo", "toc", "view-source", "vspace", "wrap", "progress"};
4651 char const * const * const end_dialognames =
4652 dialognames + (sizeof(dialognames) / sizeof(char *));
4656 cmpCStr(char const * name) : name_(name) {}
4657 bool operator()(char const * other) {
4658 return strcmp(other, name_) == 0;
4665 bool isValidName(string const & name)
4667 return find_if(dialognames, end_dialognames,
4668 cmpCStr(name.c_str())) != end_dialognames;
4674 void GuiView::resetDialogs()
4676 // Make sure that no LFUN uses any GuiView.
4677 guiApp->setCurrentView(0);
4681 constructToolbars();
4682 guiApp->menus().fillMenuBar(menuBar(), this, false);
4683 d.layout_->updateContents(true);
4684 // Now update controls with current buffer.
4685 guiApp->setCurrentView(this);
4691 Dialog * GuiView::findOrBuild(string const & name, bool hide_it)
4693 if (!isValidName(name))
4696 map<string, DialogPtr>::iterator it = d.dialogs_.find(name);
4698 if (it != d.dialogs_.end()) {
4700 it->second->hideView();
4701 return it->second.get();
4704 Dialog * dialog = build(name);
4705 d.dialogs_[name].reset(dialog);
4706 if (lyxrc.allow_geometry_session)
4707 dialog->restoreSession();
4714 void GuiView::showDialog(string const & name, string const & sdata,
4717 triggerShowDialog(toqstr(name), toqstr(sdata), inset);
4721 void GuiView::doShowDialog(QString const & qname, QString const & qdata,
4727 const string name = fromqstr(qname);
4728 const string sdata = fromqstr(qdata);
4732 Dialog * dialog = findOrBuild(name, false);
4734 bool const visible = dialog->isVisibleView();
4735 dialog->showData(sdata);
4736 if (currentBufferView())
4737 currentBufferView()->editInset(name, inset);
4738 // We only set the focus to the new dialog if it was not yet
4739 // visible in order not to change the existing previous behaviour
4741 // activateWindow is needed for floating dockviews
4742 dialog->asQWidget()->raise();
4743 dialog->asQWidget()->activateWindow();
4744 dialog->asQWidget()->setFocus();
4748 catch (ExceptionMessage const & ex) {
4756 bool GuiView::isDialogVisible(string const & name) const
4758 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4759 if (it == d.dialogs_.end())
4761 return it->second.get()->isVisibleView() && !it->second.get()->isClosing();
4765 void GuiView::hideDialog(string const & name, Inset * inset)
4767 map<string, DialogPtr>::const_iterator it = d.dialogs_.find(name);
4768 if (it == d.dialogs_.end())
4772 if (!currentBufferView())
4774 if (inset != currentBufferView()->editedInset(name))
4778 Dialog * const dialog = it->second.get();
4779 if (dialog->isVisibleView())
4781 if (currentBufferView())
4782 currentBufferView()->editInset(name, 0);
4786 void GuiView::disconnectDialog(string const & name)
4788 if (!isValidName(name))
4790 if (currentBufferView())
4791 currentBufferView()->editInset(name, 0);
4795 void GuiView::hideAll() const
4797 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4798 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4800 for(; it != end; ++it)
4801 it->second->hideView();
4805 void GuiView::updateDialogs()
4807 map<string, DialogPtr>::const_iterator it = d.dialogs_.begin();
4808 map<string, DialogPtr>::const_iterator end = d.dialogs_.end();
4810 for(; it != end; ++it) {
4811 Dialog * dialog = it->second.get();
4813 if (dialog->needBufferOpen() && !documentBufferView())
4814 hideDialog(fromqstr(dialog->name()), 0);
4815 else if (dialog->isVisibleView())
4816 dialog->checkStatus();
4823 Dialog * createDialog(GuiView & lv, string const & name);
4825 // will be replaced by a proper factory...
4826 Dialog * createGuiAbout(GuiView & lv);
4827 Dialog * createGuiBibtex(GuiView & lv);
4828 Dialog * createGuiChanges(GuiView & lv);
4829 Dialog * createGuiCharacter(GuiView & lv);
4830 Dialog * createGuiCitation(GuiView & lv);
4831 Dialog * createGuiCompare(GuiView & lv);
4832 Dialog * createGuiCompareHistory(GuiView & lv);
4833 Dialog * createGuiDelimiter(GuiView & lv);
4834 Dialog * createGuiDocument(GuiView & lv);
4835 Dialog * createGuiErrorList(GuiView & lv);
4836 Dialog * createGuiExternal(GuiView & lv);
4837 Dialog * createGuiGraphics(GuiView & lv);
4838 Dialog * createGuiInclude(GuiView & lv);
4839 Dialog * createGuiIndex(GuiView & lv);
4840 Dialog * createGuiListings(GuiView & lv);
4841 Dialog * createGuiLog(GuiView & lv);
4842 Dialog * createGuiLyXFiles(GuiView & lv);
4843 Dialog * createGuiMathMatrix(GuiView & lv);
4844 Dialog * createGuiNote(GuiView & lv);
4845 Dialog * createGuiParagraph(GuiView & lv);
4846 Dialog * createGuiPhantom(GuiView & lv);
4847 Dialog * createGuiPreferences(GuiView & lv);
4848 Dialog * createGuiPrint(GuiView & lv);
4849 Dialog * createGuiPrintindex(GuiView & lv);
4850 Dialog * createGuiRef(GuiView & lv);
4851 Dialog * createGuiSearch(GuiView & lv);
4852 Dialog * createGuiSearchAdv(GuiView & lv);
4853 Dialog * createGuiSendTo(GuiView & lv);
4854 Dialog * createGuiShowFile(GuiView & lv);
4855 Dialog * createGuiSpellchecker(GuiView & lv);
4856 Dialog * createGuiSymbols(GuiView & lv);
4857 Dialog * createGuiTabularCreate(GuiView & lv);
4858 Dialog * createGuiTexInfo(GuiView & lv);
4859 Dialog * createGuiToc(GuiView & lv);
4860 Dialog * createGuiThesaurus(GuiView & lv);
4861 Dialog * createGuiViewSource(GuiView & lv);
4862 Dialog * createGuiWrap(GuiView & lv);
4863 Dialog * createGuiProgressView(GuiView & lv);
4867 Dialog * GuiView::build(string const & name)
4869 LASSERT(isValidName(name), return 0);
4871 Dialog * dialog = createDialog(*this, name);
4875 if (name == "aboutlyx")
4876 return createGuiAbout(*this);
4877 if (name == "bibtex")
4878 return createGuiBibtex(*this);
4879 if (name == "changes")
4880 return createGuiChanges(*this);
4881 if (name == "character")
4882 return createGuiCharacter(*this);
4883 if (name == "citation")
4884 return createGuiCitation(*this);
4885 if (name == "compare")
4886 return createGuiCompare(*this);
4887 if (name == "comparehistory")
4888 return createGuiCompareHistory(*this);
4889 if (name == "document")
4890 return createGuiDocument(*this);
4891 if (name == "errorlist")
4892 return createGuiErrorList(*this);
4893 if (name == "external")
4894 return createGuiExternal(*this);
4896 return createGuiShowFile(*this);
4897 if (name == "findreplace")
4898 return createGuiSearch(*this);
4899 if (name == "findreplaceadv")
4900 return createGuiSearchAdv(*this);
4901 if (name == "graphics")
4902 return createGuiGraphics(*this);
4903 if (name == "include")
4904 return createGuiInclude(*this);
4905 if (name == "index")
4906 return createGuiIndex(*this);
4907 if (name == "index_print")
4908 return createGuiPrintindex(*this);
4909 if (name == "listings")
4910 return createGuiListings(*this);
4912 return createGuiLog(*this);
4913 if (name == "lyxfiles")
4914 return createGuiLyXFiles(*this);
4915 if (name == "mathdelimiter")
4916 return createGuiDelimiter(*this);
4917 if (name == "mathmatrix")
4918 return createGuiMathMatrix(*this);
4920 return createGuiNote(*this);
4921 if (name == "paragraph")
4922 return createGuiParagraph(*this);
4923 if (name == "phantom")
4924 return createGuiPhantom(*this);
4925 if (name == "prefs")
4926 return createGuiPreferences(*this);
4928 return createGuiRef(*this);
4929 if (name == "sendto")
4930 return createGuiSendTo(*this);
4931 if (name == "spellchecker")
4932 return createGuiSpellchecker(*this);
4933 if (name == "symbols")
4934 return createGuiSymbols(*this);
4935 if (name == "tabularcreate")
4936 return createGuiTabularCreate(*this);
4937 if (name == "texinfo")
4938 return createGuiTexInfo(*this);
4939 if (name == "thesaurus")
4940 return createGuiThesaurus(*this);
4942 return createGuiToc(*this);
4943 if (name == "view-source")
4944 return createGuiViewSource(*this);
4946 return createGuiWrap(*this);
4947 if (name == "progress")
4948 return createGuiProgressView(*this);
4954 SEMenu::SEMenu(QWidget * parent)
4956 QAction * action = addAction(qt_("Disable Shell Escape"));
4957 connect(action, SIGNAL(triggered()),
4958 parent, SLOT(disableShellEscape()));
4961 } // namespace frontend
4964 #include "moc_GuiView.cpp"