X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiView.cpp;h=a75b1ae2c6f056123f8586bd7ad4d37a61ef680b;hb=da548dbe30ef0d36a2fb8ec6c05facb45d7f8ee7;hp=4213f1dc6a3fe4c4f66f00ddd977894bf1d54ad6;hpb=e48942bbbb48419c3d26c3edab520b31dc5e8941;p=lyx.git diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp index 4213f1dc6a..52fadcf3ef 100644 --- a/src/frontends/qt4/GuiView.cpp +++ b/src/frontends/qt4/GuiView.cpp @@ -34,6 +34,7 @@ #include "qt_helpers.h" #include "frontends/alert.h" +#include "frontends/KeySymbol.h" #include "buffer_funcs.h" #include "Buffer.h" @@ -58,6 +59,7 @@ #include "LyXVC.h" #include "Paragraph.h" #include "SpellChecker.h" +#include "Session.h" #include "TexRow.h" #include "TextClass.h" #include "Text.h" @@ -76,7 +78,7 @@ #include "support/lstrings.h" #include "support/os.h" #include "support/Package.h" -#include "support/Path.h" +#include "support/PathChanger.h" #include "support/Systemcall.h" #include "support/Timeout.h" #include "support/ProgressInterface.h" @@ -88,35 +90,36 @@ #include #include #include +#include +#include +#include #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include -#include +// sync with GuiAlert.cpp #define EXPORT_in_THREAD 1 -// QtConcurrent was introduced in Qt 4.4 -#if (QT_VERSION >= 0x040400) -#include -#include -#include -#endif #include "support/bind.h" @@ -129,10 +132,16 @@ # include #endif + using namespace std; using namespace lyx::support; namespace lyx { + +using support::addExtension; +using support::changeExtension; +using support::removeExtension; + namespace frontend { namespace { @@ -148,27 +157,40 @@ public: /// The text to be written on top of the pixmap QString const text = lyx_version ? qt_("version ") + lyx_version : qt_("unknown version"); - splash_ = getPixmap("images/", "banner", "png"); + splash_ = getPixmap("images/", "banner", "svgz,png"); QPainter pain(&splash_); pain.setPen(QColor(0, 0, 0)); + double const multiplier = splashPixelRatio() / pixelRatio(); + int const size = static_cast(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble() * multiplier); + int const x = static_cast(190 * multiplier); + int const y = static_cast(225 * multiplier); + LYXERR(Debug::GUI, + "widget pixel ratio: " << pixelRatio() << + " splash pixel ratio: " << splashPixelRatio() << + " version text size,position: " << size << "@" << x << "+" << y); QFont font; // The font used to display the version info font.setStyleHint(QFont::SansSerif); font.setWeight(QFont::Bold); - font.setPointSize(int(toqstr(lyxrc.font_sizes[FONT_SIZE_LARGE]).toDouble())); - int width = QFontMetrics(font).width(text); + font.setPointSize(size); pain.setFont(font); - pain.drawText(397 - width, 15, text); + pain.drawText(x, y, text); setFocusPolicy(Qt::StrongFocus); } void paintEvent(QPaintEvent *) { - int x = (width() - splash_.width()) / 2; - int y = (height() - splash_.height()) / 2; + int const w = static_cast(splash_.width() / splashPixelRatio()); + int const h = static_cast(splash_.height() / splashPixelRatio()); + int const x = (width() - w) / 2; + int const y = (height() - h) / 2; + LYXERR(Debug::GUI, + "widget pixel ratio: " << pixelRatio() << + " splash pixel ratio: " << splashPixelRatio() << + " paint pixmap: " << w << "x" << h << "@" << x << "+" << y); QPainter pain(this); - pain.drawPixmap(x, y, splash_); + pain.drawPixmap(x, y, w, h, splash_); } void keyPressEvent(QKeyEvent * ev) @@ -185,6 +207,24 @@ public: private: QPixmap splash_; + + /// Current ratio between physical pixels and device-independent pixels + double pixelRatio() const { +#if QT_VERSION >= 0x050000 + return devicePixelRatio(); +#else + return 1.0; +#endif + } + + /// Ratio between physical pixels and device-independent pixels of splash image + double splashPixelRatio() const { +#if QT_VERSION >= 0x050000 + return splash_.devicePixelRatio(); +#else + return 1.0; +#endif + } }; @@ -204,9 +244,24 @@ struct GuiView::GuiViewPrivate in_show_(false) { // hardcode here the platform specific icon size - smallIconSize = 14; // scaling problems - normalIconSize = 20; // ok, default + smallIconSize = 16; // scaling problems + normalIconSize = 20; // ok, default if iconsize.png is missing bigIconSize = 26; // better for some math icons + hugeIconSize = 32; // better for hires displays + giantIconSize = 48; + + // if it exists, use width of iconsize.png as normal size + QString const dir = toqstr(addPath("images", lyxrc.icon_set)); + FileName const fn = lyx::libFileSearch(dir, "iconsize.png"); + if (!fn.empty()) { + QImage image(toqstr(fn.absFileName())); + if (image.width() < int(smallIconSize)) + normalIconSize = smallIconSize; + else if (image.width() > int(giantIconSize)) + normalIconSize = giantIconSize; + else + normalIconSize = image.width(); + } splitter_ = new QSplitter; bg_widget_ = new BackgroundWidget; @@ -218,7 +273,7 @@ struct GuiView::GuiViewPrivate // TODO cleanup, remove the singleton, handle multiple Windows? progress_ = ProgressInterface::instance(); if (!dynamic_cast(progress_)) { - progress_ = new GuiProgress(); // TODO who deletes it + progress_ = new GuiProgress; // TODO who deletes it ProgressInterface::setInstance(progress_); } QObject::connect( @@ -265,6 +320,20 @@ struct GuiView::GuiViewPrivate parent, SLOT(bigSizedIcons())); menu->addAction(bigIcons); + QAction * hugeIcons = new QAction(iconSizeGroup); + hugeIcons->setText(qt_("Huge-sized icons")); + hugeIcons->setCheckable(true); + QObject::connect(hugeIcons, SIGNAL(triggered()), + parent, SLOT(hugeSizedIcons())); + menu->addAction(hugeIcons); + + QAction * giantIcons = new QAction(iconSizeGroup); + giantIcons->setText(qt_("Giant-sized icons")); + giantIcons->setCheckable(true); + QObject::connect(giantIcons, SIGNAL(triggered()), + parent, SLOT(giantSizedIcons())); + menu->addAction(giantIcons); + unsigned int cur = parent->iconSize().width(); if ( cur == parent->d.smallIconSize) smallIcons->setChecked(true); @@ -272,6 +341,10 @@ struct GuiView::GuiViewPrivate normalIcons->setChecked(true); else if (cur == parent->d.bigIconSize) bigIcons->setChecked(true); + else if (cur == parent->d.hugeIconSize) + hugeIcons->setChecked(true); + else if (cur == parent->d.giantIconSize) + giantIcons->setChecked(true); return menu; } @@ -287,7 +360,7 @@ struct GuiView::GuiViewPrivate { return splitter_->count(); } - + TabWorkArea * tabWorkArea(int i) { return dynamic_cast(splitter_->widget(i)); @@ -310,8 +383,19 @@ struct GuiView::GuiViewPrivate return tabWorkArea(0); } -#if (QT_VERSION >= 0x040400) - void setPreviewFuture(QFuture const & f) + int countWorkAreasOf(Buffer & buf) + { + int areas = tabWorkAreaCount(); + int count = 0; + for (int i = 0; i != areas; ++i) { + TabWorkArea * twa = tabWorkArea(i); + if (twa->workArea(buf)) + ++count; + } + return count; + } + + void setPreviewFuture(QFuture const & f) { if (processing_thread_watcher_.isRunning()) { // we prefer to cancel this preview in order to keep a snappy @@ -320,7 +404,6 @@ struct GuiView::GuiViewPrivate } processing_thread_watcher_.setFuture(f); } -#endif public: GuiView * gv_; @@ -349,6 +432,8 @@ public: unsigned int smallIconSize; unsigned int normalIconSize; unsigned int bigIconSize; + unsigned int hugeIconSize; + unsigned int giantIconSize; /// QTimer statusbar_timer_; /// auto-saving of buffers @@ -359,37 +444,30 @@ public: /// TocModels toc_models_; -#if (QT_VERSION >= 0x040400) /// QFutureWatcher autosave_watcher_; - QFutureWatcher processing_thread_watcher_; + QFutureWatcher processing_thread_watcher_; /// string last_export_format; -#else - struct DummyWatcher { bool isRunning(){return false;} }; - DummyWatcher processing_thread_watcher_; -#endif + string processing_format; static QSet busyBuffers; - static docstring previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format); - static docstring exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format); - static docstring compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format); - static docstring saveAndDestroy(Buffer const * orig, Buffer * buffer, FileName const & fname); + static Buffer::ExportStatus previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format); + static Buffer::ExportStatus exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format); + static Buffer::ExportStatus compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format); + static docstring autosaveAndDestroy(Buffer const * orig, Buffer * buffer); template - static docstring runAndDestroy(const T& func, Buffer const * orig, Buffer * buffer, string const & format, string const & msg); - + static Buffer::ExportStatus runAndDestroy(const T& func, Buffer const * orig, Buffer * buffer, string const & format); + // TODO syncFunc/previewFunc: use bind bool asyncBufferProcessing(string const & argument, - Buffer const * used_buffer, - docstring const & msg, - docstring (*asyncFunc)(Buffer const *, Buffer *, string const &), - bool (Buffer::*syncFunc)(string const &, bool, bool) const, - bool (Buffer::*previewFunc)(string const &, bool) const); - - QTimer processing_cursor_timer_; - bool indicates_processing_; - QMap orig_cursors_; + Buffer const * used_buffer, + docstring const & msg, + Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &), + Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const, + Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const); + QVector guiWorkAreas(); }; @@ -397,7 +475,7 @@ QSet GuiView::GuiViewPrivate::busyBuffers; GuiView::GuiView(int id) - : d(*new GuiViewPrivate(this)), id_(id), closing_(false) + : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0) { // GuiToolbars *must* be initialised before the menu bar. normalSizedIcons(); // at least on Mac the default is 32 otherwise, which is huge @@ -425,35 +503,53 @@ GuiView::GuiView(int id) // We don't want to keep the window in memory if it is closed. setAttribute(Qt::WA_DeleteOnClose, true); -#if (!defined(Q_WS_WIN) && !defined(Q_WS_MACX)) +#if !(defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)) && !defined(Q_OS_MAC) + // QIcon::fromTheme was introduced in Qt 4.6 +#if (QT_VERSION >= 0x040600) // assign an icon to main form. We do not do it under Qt/Win or Qt/Mac, - // since the icon is provided in the application bundle. - setWindowIcon(getPixmap("images/", "lyx", "png")); + // since the icon is provided in the application bundle. We use a themed + // version when available and use the bundled one as fallback. + setWindowIcon(QIcon::fromTheme("lyx", getPixmap("images/", "lyx", "svg,png"))); +#else + setWindowIcon(getPixmap("images/", "lyx", "svg,png")); #endif -#if (QT_VERSION >= 0x040300) +#endif + resetWindowTitleAndIconText(); + // use tabbed dock area for multiple docks // (such as "source" and "messages") setDockOptions(QMainWindow::ForceTabbedDocks); -#endif // For Drag&Drop. setAcceptDrops(true); + // add busy indicator to statusbar + QLabel * busylabel = new QLabel(statusBar()); + statusBar()->addPermanentWidget(busylabel); + search_mode mode = theGuiApp()->imageSearchMode(); + QString fn = toqstr(lyx::libFileSearch("images", "busy", "gif", mode).absFileName()); + QMovie * busyanim = new QMovie(fn, QByteArray(), busylabel); + busylabel->setMovie(busyanim); + busyanim->start(); + busylabel->hide(); + + connect(&d.processing_thread_watcher_, SIGNAL(started()), + busylabel, SLOT(show())); + connect(&d.processing_thread_watcher_, SIGNAL(finished()), + busylabel, SLOT(hide())); + statusBar()->setSizeGripEnabled(true); updateStatusBar(); -#if (QT_VERSION >= 0x040400) connect(&d.autosave_watcher_, SIGNAL(finished()), this, - SLOT(processingThreadFinished())); + SLOT(autoSaveThreadFinished())); + + connect(&d.processing_thread_watcher_, SIGNAL(started()), this, + SLOT(processingThreadStarted())); connect(&d.processing_thread_watcher_, SIGNAL(finished()), this, SLOT(processingThreadFinished())); - d.processing_cursor_timer_.setInterval(1000 * 3); - connect(&d.processing_cursor_timer_, SIGNAL(timeout()), this, - SLOT(indicateProcessing())); -#endif - connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)), SLOT(doShowDialog(QString const &, QString const &, Inset *))); @@ -495,63 +591,64 @@ QVector GuiView::GuiViewPrivate::guiWorkAreas() return areas; } -void GuiView::setCursorShapes(Qt::CursorShape shape) +static void handleExportStatus(GuiView * view, Buffer::ExportStatus status, + string const & format) { - QVector areas = d.guiWorkAreas(); - Q_FOREACH(GuiWorkArea* wa, areas) { - wa->setCursorShape(shape); + docstring const fmt = formats.prettyName(format); + docstring msg; + switch (status) { + case Buffer::ExportSuccess: + msg = bformat(_("Successful export to format: %1$s"), fmt); + break; + case Buffer::ExportCancel: + msg = _("Document export cancelled."); + break; + case Buffer::ExportError: + case Buffer::ExportNoPathToFormat: + case Buffer::ExportTexPathHasSpaces: + case Buffer::ExportConverterError: + msg = bformat(_("Error while exporting format: %1$s"), fmt); + break; + case Buffer::PreviewSuccess: + msg = bformat(_("Successful preview of format: %1$s"), fmt); + break; + case Buffer::PreviewError: + msg = bformat(_("Error while previewing format: %1$s"), fmt); + break; } + view->message(msg); } -void GuiView::restoreCursorShapes() -{ - QVector areas = d.guiWorkAreas(); - Q_FOREACH(GuiWorkArea* wa, areas) { - if (d.orig_cursors_.contains(wa)) { - wa->setCursorShape(d.orig_cursors_[wa]); - } - } -} -void GuiView::saveCursorShapes() +void GuiView::processingThreadStarted() { - d.orig_cursors_.clear(); - QVector areas = d.guiWorkAreas(); - Q_FOREACH(GuiWorkArea* wa, areas) { - d.orig_cursors_[wa] = wa->cursorShape(); - } } -void GuiView::indicateProcessing() + +void GuiView::processingThreadFinished() { - if (d.indicates_processing_) { - restoreCursorShapes(); - } else { - setCursorShapes(Qt::BusyCursor); + QFutureWatcher const * watcher = + static_cast const *>(sender()); + + Buffer::ExportStatus const status = watcher->result(); + handleExportStatus(this, status, d.processing_format); + + updateToolbars(); + BufferView const * const bv = currentBufferView(); + if (bv && !bv->buffer().errorList("Export").empty()) { + errors("Export"); + return; } - d.indicates_processing_ = !d.indicates_processing_; + errors(d.last_export_format); } -void GuiView::processingThreadStarted() -{ - saveCursorShapes(); - d.indicates_processing_ = false; - indicateProcessing(); - d.processing_cursor_timer_.start(); -} -void GuiView::processingThreadFinished() +void GuiView::autoSaveThreadFinished() { -#if (QT_VERSION >= 0x040400) QFutureWatcher const * watcher = static_cast const *>(sender()); message(watcher->result()); updateToolbars(); - errors(d.last_export_format); - d.processing_cursor_timer_.stop(); - restoreCursorShapes(); - d.indicates_processing_ = false; -#endif } @@ -560,7 +657,7 @@ void GuiView::saveLayout() const QSettings settings; settings.beginGroup("views"); settings.beginGroup(QString::number(id_)); -#ifdef Q_WS_X11 +#if defined(Q_WS_X11) || defined(QPA_XCB) settings.setValue("pos", pos()); settings.setValue("size", size()); #else @@ -571,6 +668,19 @@ void GuiView::saveLayout() const } +void GuiView::saveUISettings() const +{ + // Save the toolbar private states + ToolbarMap::iterator end = d.toolbars_.end(); + for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it) + it->second->saveSession(); + // Now take care of all other dialogs + map::const_iterator it = d.dialogs_.begin(); + for (; it!= d.dialogs_.end(); ++it) + it->second->saveSession(); +} + + bool GuiView::restoreLayout() { QSettings settings; @@ -581,8 +691,19 @@ bool GuiView::restoreLayout() return false; //code below is skipped when when ~/.config/LyX is (re)created - setIconSize(settings.value(icon_key).toSize()); -#ifdef Q_WS_X11 + QSize icon_size = settings.value(icon_key).toSize(); + // Check whether session size changed. + if (icon_size.width() != int(d.smallIconSize) && + icon_size.width() != int(d.normalIconSize) && + icon_size.width() != int(d.bigIconSize) && + icon_size.width() != int(d.hugeIconSize) && + icon_size.width() != int(d.giantIconSize)) { + icon_size.setWidth(d.normalIconSize); + icon_size.setHeight(d.normalIconSize); + } + setIconSize(icon_size); + +#if defined(Q_WS_X11) || defined(QPA_XCB) QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint(); QSize size = settings.value("size", QSize(690, 510)).toSize(); resize(size); @@ -611,6 +732,16 @@ bool GuiView::restoreLayout() if (!restoreState(settings.value("layout").toByteArray(), 0)) initToolbars(); + + // init the toolbars that have not been restored + Toolbars::Infos::iterator cit = guiApp->toolbars().begin(); + Toolbars::Infos::iterator end = guiApp->toolbars().end(); + for (; cit != end; ++cit) { + GuiToolbar * tb = toolbar(cit->name); + if (tb && !tb->isRestored()) + initToolbar(cit->name); + } + updateDialogs(); return true; } @@ -653,51 +784,47 @@ void GuiView::initToolbars() // extracts the toolbars from the backend Toolbars::Infos::iterator cit = guiApp->toolbars().begin(); Toolbars::Infos::iterator end = guiApp->toolbars().end(); - for (; cit != end; ++cit) { - GuiToolbar * tb = toolbar(cit->name); - if (!tb) - continue; - int const visibility = guiApp->toolbars().defaultVisibility(cit->name); - bool newline = !(visibility & Toolbars::SAMEROW); - tb->setVisible(false); - tb->setVisibility(visibility); - - if (visibility & Toolbars::TOP) { - if (newline) - addToolBarBreak(Qt::TopToolBarArea); - addToolBar(Qt::TopToolBarArea, tb); - } - - if (visibility & Toolbars::BOTTOM) { - // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock. -#if (QT_VERSION >= 0x040202) - if (newline) - addToolBarBreak(Qt::BottomToolBarArea); -#endif - addToolBar(Qt::BottomToolBarArea, tb); - } + for (; cit != end; ++cit) + initToolbar(cit->name); +} - if (visibility & Toolbars::LEFT) { - // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock. -#if (QT_VERSION >= 0x040202) - if (newline) - addToolBarBreak(Qt::LeftToolBarArea); -#endif - addToolBar(Qt::LeftToolBarArea, tb); - } - if (visibility & Toolbars::RIGHT) { - // Qt < 4.2.2 cannot handle ToolBarBreak on non-TOP dock. -#if (QT_VERSION >= 0x040202) - if (newline) - addToolBarBreak(Qt::RightToolBarArea); -#endif - addToolBar(Qt::RightToolBarArea, tb); - } +void GuiView::initToolbar(string const & name) +{ + GuiToolbar * tb = toolbar(name); + if (!tb) + return; + int const visibility = guiApp->toolbars().defaultVisibility(name); + bool newline = !(visibility & Toolbars::SAMEROW); + tb->setVisible(false); + tb->setVisibility(visibility); + + if (visibility & Toolbars::TOP) { + if (newline) + addToolBarBreak(Qt::TopToolBarArea); + addToolBar(Qt::TopToolBarArea, tb); + } + + if (visibility & Toolbars::BOTTOM) { + if (newline) + addToolBarBreak(Qt::BottomToolBarArea); + addToolBar(Qt::BottomToolBarArea, tb); + } + + if (visibility & Toolbars::LEFT) { + if (newline) + addToolBarBreak(Qt::LeftToolBarArea); + addToolBar(Qt::LeftToolBarArea, tb); + } - if (visibility & Toolbars::ON) - tb->setVisible(true); + if (visibility & Toolbars::RIGHT) { + if (newline) + addToolBarBreak(Qt::RightToolBarArea); + addToolBar(Qt::RightToolBarArea, tb); } + + if (visibility & Toolbars::ON) + tb->setVisible(true); } @@ -714,16 +841,26 @@ void GuiView::setFocus() } +bool GuiView::hasFocus() const +{ + if (currentWorkArea()) + return currentWorkArea()->hasFocus(); + if (currentMainWorkArea()) + return currentMainWorkArea()->hasFocus(); + return d.bg_widget_->hasFocus(); +} + + void GuiView::focusInEvent(QFocusEvent * e) { LYXERR(Debug::DEBUG, "GuiView::focusInEvent()" << this); QMainWindow::focusInEvent(e); // Make sure guiApp points to the correct view. guiApp->setCurrentView(this); - if (currentMainWorkArea()) - currentMainWorkArea()->setFocus(); - else if (currentWorkArea()) + if (currentWorkArea()) currentWorkArea()->setFocus(); + else if (currentMainWorkArea()) + currentMainWorkArea()->setFocus(); else d.bg_widget_->setFocus(); } @@ -745,6 +882,7 @@ void GuiView::showEvent(QShowEvent * e) // No work area, switch to the background widget. d.setBackground(); + updateToolbars(); QMainWindow::showEvent(e); } @@ -756,6 +894,32 @@ bool GuiView::closeScheduled() } +bool GuiView::prepareAllBuffersForLogout() +{ + Buffer * first = theBufferList().first(); + if (!first) + return true; + + // First, iterate over all buffers and ask the users if unsaved + // changes should be saved. + // We cannot use a for loop as the buffer list cycles. + Buffer * b = first; + do { + if (!saveBufferIfNeeded(const_cast(*b), false)) + return false; + b = theBufferList().next(b); + } while (b != first); + + // Next, save session state + // When a view/window was closed before without quitting LyX, there + // are already entries in the lastOpened list. + theSession().lastOpened().clear(); + writeSession(); + + return true; +} + + /** Destroy only all tabbed WorkAreas. Destruction of other WorkAreas ** is responsibility of the container (e.g., dialog) **/ @@ -764,7 +928,8 @@ void GuiView::closeEvent(QCloseEvent * close_event) LYXERR(Debug::DEBUG, "GuiView::closeEvent()"); if (!GuiViewPrivate::busyBuffers.isEmpty()) { - Alert::warning(_("Exit LyX"), _("LyX could not be closed because documents are processed by LyX.")); + Alert::warning(_("Exit LyX"), + _("LyX could not be closed because documents are being processed by LyX.")); close_event->setAccepted(false); return; } @@ -801,16 +966,8 @@ void GuiView::closeEvent(QCloseEvent * close_event) // Saving fullscreen requires additional tweaks in the toolbar code. // It wouldn't also work under linux natively. if (lyxrc.allow_geometry_session) { - // Save this window geometry and layout. saveLayout(); - // Then the toolbar private states. - ToolbarMap::iterator end = d.toolbars_.end(); - for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it) - it->second->saveSession(); - // Now take care of all other dialogs: - map::const_iterator it = d.dialogs_.begin(); - for (; it!= d.dialogs_.end(); ++it) - it->second->saveSession(); + saveUISettings(); } close_event->accept(); @@ -848,7 +1005,7 @@ void GuiView::dropEvent(QDropEvent * event) = theConverters().importableFormats(); vector::const_iterator it = import_formats.begin(); for (; it != import_formats.end(); ++it) - if ((*it)->extension() == ext) + if ((*it)->hasExtension(ext)) found_formats.push_back(*it); FuncRequest cmd; @@ -920,6 +1077,18 @@ void GuiView::bigSizedIcons() } +void GuiView::hugeSizedIcons() +{ + setIconSize(QSize(d.hugeIconSize, d.hugeIconSize)); +} + + +void GuiView::giantSizedIcons() +{ + setIconSize(QSize(d.giantIconSize, d.giantIconSize)); +} + + void GuiView::clearMessage() { // FIXME: This code was introduced in r19643 to fix bug #4123. However, @@ -939,11 +1108,20 @@ void GuiView::updateWindowTitle(GuiWorkArea * wa) return; setWindowTitle(qt_("LyX: ") + wa->windowTitle()); setWindowIconText(wa->windowIconText()); +#if (QT_VERSION >= 0x040400) + // Sets the path for the window: this is used by OSX to + // allow a context click on the title bar showing a menu + // with the path up to the file + setWindowFilePath(toqstr(wa->bufferView().buffer().absFileName())); +#endif } void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa) { + if (d.current_work_area_) + QObject::disconnect(d.current_work_area_, SIGNAL(busy(bool)), + this, SLOT(setBusy(bool))); disconnectBuffer(); disconnectBufferView(); connectBufferView(wa->bufferView()); @@ -951,6 +1129,7 @@ void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa) d.current_work_area_ = wa; QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)), this, SLOT(updateWindowTitle(GuiWorkArea *))); + QObject::connect(wa, SIGNAL(busy(bool)), this, SLOT(setBusy(bool))); updateWindowTitle(wa); structureChanged(); @@ -991,7 +1170,7 @@ void GuiView::on_lastWorkAreaRemoved() return; } -#ifdef Q_WS_MACX +#ifdef Q_OS_MAC // On Mac we also close the last window because the application stay // resident in memory. On other platforms we don't close the last // window because this would quit the application. @@ -1012,6 +1191,8 @@ void GuiView::updateStatusBar() void GuiView::showMessage() { + if (busy_) + return; QString msg = toqstr(theGuiApp()->viewStatusMessage()); if (msg.isEmpty()) { BufferView const * bv = currentBufferView(); @@ -1073,9 +1254,7 @@ bool GuiView::event(QEvent * e) } case QEvent::ShortcutOverride: { - -// See bug 4888 -#if (!defined Q_WS_X11) || (QT_VERSION >= 0x040500) + // See bug 4888 if (isFullScreen() && menuBar()->isHidden()) { QKeyEvent * ke = static_cast(e); // FIXME: we should also try to detect special LyX shortcut such as @@ -1087,7 +1266,6 @@ bool GuiView::event(QEvent * e) return QMainWindow::event(e); } } -#endif return QMainWindow::event(e); } @@ -1111,25 +1289,43 @@ bool GuiView::focusNextPrevChild(bool /*next*/) bool GuiView::busy() const { - return busy_; + return busy_ > 0; } void GuiView::setBusy(bool busy) { - busy_ = busy; - if (d.current_work_area_) { - d.current_work_area_->setUpdatesEnabled(!busy); - if (busy) - d.current_work_area_->stopBlinkingCursor(); - else - d.current_work_area_->startBlinkingCursor(); - } + bool const busy_before = busy_ > 0; + busy ? ++busy_ : --busy_; + if ((busy_ > 0) == busy_before) + // busy state didn't change + return; - if (busy) + if (busy) { QApplication::setOverrideCursor(Qt::WaitCursor); - else - QApplication::restoreOverrideCursor(); + return; + } + QApplication::restoreOverrideCursor(); + updateLayoutList(); +} + + +double GuiView::pixelRatio() const +{ +#if QT_VERSION >= 0x050000 + return devicePixelRatio(); +#else + return 1.0; +#endif +} + + +GuiWorkArea * GuiView::workArea(int index) +{ + if (TabWorkArea * twa = d.currentTabWorkArea()) + if (index < twa->count()) + return dynamic_cast(twa->widget(index)); + return 0; } @@ -1216,17 +1412,23 @@ void GuiView::setCurrentWorkArea(GuiWorkArea * wa) theGuiApp()->setCurrentView(this); d.current_work_area_ = wa; + + // We need to reset this now, because it will need to be + // right if the tabWorkArea gets reset in the for loop. We + // will change it back if we aren't in that case. + GuiWorkArea * const old_cmwa = d.current_main_work_area_; + d.current_main_work_area_ = wa; + for (int i = 0; i != d.splitter_->count(); ++i) { if (d.tabWorkArea(i)->setCurrentWorkArea(wa)) { - //if (d.current_main_work_area_) - // d.current_main_work_area_->setFrameStyle(QFrame::NoFrame); - d.current_main_work_area_ = wa; - //d.current_main_work_area_->setFrameStyle(QFrame::Box | QFrame::Plain); - //d.current_main_work_area_->setLineWidth(2); - LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() << ", Current main wa: " << currentMainWorkArea()); + LYXERR(Debug::DEBUG, "Current wa: " << currentWorkArea() + << ", Current main wa: " << currentMainWorkArea()); return; } } + + d.current_main_work_area_ = old_cmwa; + LYXERR(Debug::DEBUG, "This is not a tabbed wa"); on_currentWorkAreaChanged(wa); BufferView & bv = wa->bufferView(); @@ -1268,7 +1470,7 @@ void GuiView::removeWorkArea(GuiWorkArea * wa) // It is not a tabbed work area (i.e., the search work area), so it // should be deleted by other means. - LASSERT(found_twa, /* */); + LASSERT(found_twa, return); if (d.current_work_area_ == 0) { if (d.splitter_->count() != 0) { @@ -1309,12 +1511,14 @@ void GuiView::updateToolbars() lyx::getStatus(FuncRequest(LFUN_CHANGES_TRACK)).onOff(true); bool const mathmacrotemplate = lyx::getStatus(FuncRequest(LFUN_IN_MATHMACROTEMPLATE)).enabled(); + bool const ipa = + lyx::getStatus(FuncRequest(LFUN_IN_IPA)).enabled(); for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it) - it->second->update(math, table, review, mathmacrotemplate); + it->second->update(math, table, review, mathmacrotemplate, ipa); } else for (ToolbarMap::iterator it = d.toolbars_.begin(); it != end; ++it) - it->second->update(false, false, false, false); + it->second->update(false, false, false, false, false); } @@ -1322,13 +1526,14 @@ void GuiView::setBuffer(Buffer * newBuffer) { LYXERR(Debug::DEBUG, "Setting buffer: " << newBuffer << endl); LASSERT(newBuffer, return); - setBusy(true); - + GuiWorkArea * wa = workArea(*newBuffer); if (wa == 0) { + setBusy(true); newBuffer->masterBuffer()->updateBuffer(); + setBusy(false); wa = addWorkArea(*newBuffer); - // scroll to the position when the file was last closed + // scroll to the position when the BufferView was last closed if (lyxrc.use_lastfilepos) { LastFilePosSection::FilePos filepos = theSession().lastFilePos().load(newBuffer->fileName()); @@ -1341,8 +1546,6 @@ void GuiView::setBuffer(Buffer * newBuffer) connectBuffer(*newBuffer); connectBufferView(wa->bufferView()); setCurrentWorkArea(wa); - - setBusy(false); } @@ -1374,14 +1577,31 @@ void GuiView::disconnectBufferView() void GuiView::errors(string const & error_type, bool from_master) { - ErrorList & el = from_master ? - currentBufferView()->buffer().masterBuffer()->errorList(error_type) - : currentBufferView()->buffer().errorList(error_type); + BufferView const * const bv = currentBufferView(); + if (!bv) + return; + +#if EXPORT_in_THREAD + // We are called with from_master == false by default, so we + // have to figure out whether that is the case or not. + ErrorList & el = bv->buffer().errorList(error_type); + if (el.empty()) { + el = bv->buffer().masterBuffer()->errorList(error_type); + from_master = true; + } +#else + ErrorList const & el = from_master ? + bv->buffer().masterBuffer()->errorList(error_type) : + bv->buffer().errorList(error_type); +#endif + + if (el.empty()) + return; + string data = error_type; if (from_master) data = "from_master|" + error_type; - if (!el.empty()) - showDialog("errorlist", data); + showDialog("errorlist", data); } @@ -1443,26 +1663,16 @@ BufferView const * GuiView::currentBufferView() const } -#if (QT_VERSION >= 0x040400) -docstring GuiView::GuiViewPrivate::saveAndDestroy(Buffer const * orig, Buffer * buffer, FileName const & fname) +docstring GuiView::GuiViewPrivate::autosaveAndDestroy( + Buffer const * orig, Buffer * clone) { - bool failed = true; - FileName const tmp_ret = FileName::tempName("lyxauto"); - if (!tmp_ret.empty()) { - if (buffer->writeFile(tmp_ret)) - failed = !tmp_ret.moveTo(fname); - } - if (failed) { - // failed to write/rename tmp_ret so try writing direct - failed = buffer->writeFile(fname); - } - delete buffer; + bool const success = clone->autoSave(); + delete clone; busyBuffers.remove(orig); - return failed - ? _("Automatic save failed!") - : _("Automatic save done."); + return success + ? _("Automatic save done.") + : _("Automatic save failed!"); } -#endif void GuiView::autoSave() @@ -1471,17 +1681,16 @@ void GuiView::autoSave() Buffer * buffer = documentBufferView() ? &documentBufferView()->buffer() : 0; - if (!buffer) + if (!buffer) { + resetAutosaveTimers(); return; + } -#if (QT_VERSION >= 0x040400) GuiViewPrivate::busyBuffers.insert(buffer); - QFuture f = QtConcurrent::run(GuiViewPrivate::saveAndDestroy, buffer, buffer->clone(), - buffer->getAutosaveFileName()); + QFuture f = QtConcurrent::run(GuiViewPrivate::autosaveAndDestroy, + buffer, buffer->cloneBufferOnly()); d.autosave_watcher_.setFuture(f); -#else - buffer->autoSave(); -#endif + resetAutosaveTimers(); } @@ -1500,6 +1709,16 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) Buffer * doc_buffer = documentBufferView() ? &(documentBufferView()->buffer()) : 0; + /* In LyX/Mac, when a dialog is open, the menus of the + application can still be accessed without giving focus to + the main window. In this case, we want to disable the menu + entries that are buffer-related. + */ + if (cmd.origin() == FuncRequest::MENU && !hasFocus()) { + buf = 0; + doc_buffer = 0; + } + // Check whether we need a buffer if (!lyxaction.funcHasFlag(cmd.action(), LyXAction::NoBuffer) && !buf) { // no, exit directly @@ -1522,7 +1741,9 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_MASTER_BUFFER_UPDATE: case LFUN_MASTER_BUFFER_VIEW: - enable = doc_buffer && doc_buffer->parent() != 0 + enable = doc_buffer + && (doc_buffer->parent() != 0 + || doc_buffer->hasChildren()) && !d.processing_thread_watcher_.isRunning(); break; @@ -1534,8 +1755,8 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) } string format = to_utf8(cmd.argument()); if (cmd.argument().empty()) - format = doc_buffer->getDefaultOutputFormat(); - enable = doc_buffer->isExportableFormat(format); + format = doc_buffer->params().getDefaultOutputFormat(); + enable = doc_buffer->params().isExportableFormat(format); break; } @@ -1574,10 +1795,12 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) } case LFUN_BUFFER_WRITE_AS: + case LFUN_BUFFER_EXPORT_AS: enable = doc_buffer; break; case LFUN_BUFFER_CLOSE: + case LFUN_VIEW_CLOSE: enable = doc_buffer; break; @@ -1585,7 +1808,7 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) enable = theBufferList().last() != theBufferList().first(); break; - case LFUN_SPLIT_VIEW: + case LFUN_VIEW_SPLIT: if (cmd.getArg(0) == "vertical") enable = doc_buffer && (d.splitter_->count() == 1 || d.splitter_->orientation() == Qt::Vertical); @@ -1594,8 +1817,8 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) d.splitter_->orientation() == Qt::Horizontal); break; - case LFUN_CLOSE_TAB_GROUP: - enable = d.currentTabWorkArea(); + case LFUN_TAB_GROUP_CLOSE: + enable = d.tabWorkAreaCount() > 1; break; case LFUN_TOOLBAR_TOGGLE: { @@ -1639,22 +1862,23 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) || name == "progress" || name == "compare"; else if (name == "print") - enable = doc_buffer->isExportable("dvi") + enable = doc_buffer->params().isExportable("dvi") && lyxrc.print_command != "none"; - else if (name == "character" || name == "symbols") { + else if (name == "character" || name == "symbols" + || name == "mathdelimiter" || name == "mathmatrix") { if (!buf || buf->isReadonly()) enable = false; else { - // FIXME we should consider passthru - // paragraphs too. - Inset const & in = currentBufferView()->cursor().inset(); - enable = !in.getLayout().isPassThru(); + Cursor const & cur = currentBufferView()->cursor(); + enable = !(cur.inTexted() && cur.paragraph().isPassThru()); } } else if (name == "latexlog") enable = FileName(doc_buffer->logName()).isReadableFile(); else if (name == "spellchecker") - enable = theSpellChecker() && !doc_buffer->isReadonly(); + enable = theSpellChecker() + && !doc_buffer->isReadonly() + && !doc_buffer->text().empty(); else if (name == "vclog") enable = doc_buffer->lyxvc().inUse(); break; @@ -1687,7 +1911,7 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) enable = false; break; - case LFUN_COMPLETION_COMPLETE: + case LFUN_COMPLETE: if (!d.current_work_area_ || !d.current_work_area_->completer().inlinePossible( currentBufferView()->cursor())) @@ -1717,9 +1941,13 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) enable = doc_buffer; break; + case LFUN_BUFFER_MOVE_NEXT: + case LFUN_BUFFER_MOVE_PREVIOUS: + // we do not cycle when moving case LFUN_BUFFER_NEXT: case LFUN_BUFFER_PREVIOUS: - // FIXME: should we check is there is an previous or next buffer? + // because we cycle, it doesn't matter whether on first or last + enable = (d.currentTabWorkArea()->count() > 1); break; case LFUN_BUFFER_SWITCH: // toggle on the current buffer, but do not toggle off @@ -1732,6 +1960,12 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_VC_REGISTER: enable = doc_buffer && !doc_buffer->lyxvc().inUse(); break; + case LFUN_VC_RENAME: + enable = doc_buffer && doc_buffer->lyxvc().renameEnabled(); + break; + case LFUN_VC_COPY: + enable = doc_buffer && doc_buffer->lyxvc().copyEnabled(); + break; case LFUN_VC_CHECK_IN: enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled(); break; @@ -1769,6 +2003,15 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()); break; + case LFUN_FILE_INSERT_PLAINTEXT: + case LFUN_FILE_INSERT_PLAINTEXT_PARA: + enable = documentBufferView() && documentBufferView()->cursor().inTexted(); + break; + + case LFUN_SPELLING_CONTINUOUSLY: + flag.setOnOff(lyxrc.spellcheck_continuously); + break; + default: return false; } @@ -1808,19 +2051,19 @@ Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles) setBusy(false); throw(e); } + setBusy(false); if (!newBuffer) { message(_("Document not loaded.")); - setBusy(false); return 0; } setBuffer(newBuffer); + newBuffer->errors("Parse"); if (tolastfiles) theSession().lastFiles().add(filename); - setBusy(false); return newBuffer; } @@ -1839,16 +2082,12 @@ void GuiView::openDocument(string const & fname) string filename; if (fname.empty()) { - FileDialog dlg(qt_("Select document to open"), LFUN_FILE_OPEN); + FileDialog dlg(qt_("Select document to open")); dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path)); dlg.setButton2(qt_("Examples|#E#e"), toqstr(addPath(package().system_support().absFileName(), "examples"))); - QStringList filter(qt_("LyX Documents (*.lyx)")); - filter << qt_("LyX-1.3.x Documents (*.lyx13)") - << qt_("LyX-1.4.x Documents (*.lyx14)") - << qt_("LyX-1.5.x Documents (*.lyx15)") - << qt_("LyX-1.6.x Documents (*.lyx16)"); + QStringList const filter(qt_("LyX Documents (*.lyx)")); FileDialog::Result result = dlg.open(toqstr(initpath), filter); @@ -1881,7 +2120,8 @@ void GuiView::openDocument(string const & fname) // if the file doesn't exist and isn't already open (bug 6645), // let the user create one - if (!fullname.exists() && !theBufferList().exists(fullname)) { + if (!fullname.exists() && !theBufferList().exists(fullname) && + !LyXVC::file_not_found_hook(fullname)) { // the user specifically chose this name. Believe him. Buffer * const b = newFile(filename, string(), true); if (b) @@ -1895,10 +2135,6 @@ void GuiView::openDocument(string const & fname) docstring str2; Buffer * buf = loadDocument(fullname); if (buf) { - // I don't think this is needed, since it will be done in setBuffer(). - // buf->updateBuffer(); - setBuffer(buf); - buf->errors("Parse"); str2 = bformat(_("Document %1$s opened."), disp_fn); if (buf->lyxvc().inUse()) str2 += " " + from_utf8(buf->lyxvc().versionString()) + @@ -1918,8 +2154,9 @@ static bool import(GuiView * lv, FileName const & filename, string loader_format; vector loaders = theConverters().loaders(); if (find(loaders.begin(), loaders.end(), format) == loaders.end()) { - for (vector::const_iterator it = loaders.begin(); - it != loaders.end(); ++it) { + vector::const_iterator it = loaders.begin(); + vector::const_iterator en = loaders.end(); + for (; it != en; ++it) { if (!theConverters().isReachable(format, *it)) continue; @@ -1945,10 +2182,6 @@ static bool import(GuiView * lv, FileName const & filename, Buffer * buf = lv->loadDocument(lyxfile); if (!buf) return false; - // I don't think this is needed, since it will be done in setBuffer(). - // buf->updateBuffer(); - lv->setBuffer(buf); - buf->errors("Parse"); } else { Buffer * const b = newFile(lyxfile.absFileName(), string(), true); if (!b) @@ -1988,16 +2221,16 @@ void GuiView::importDocument(string const & argument) docstring const text = bformat(_("Select %1$s file to import"), formats.prettyName(format)); - FileDialog dlg(toqstr(text), LFUN_BUFFER_IMPORT); + FileDialog dlg(toqstr(text)); dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path)); dlg.setButton2(qt_("Examples|#E#e"), toqstr(addPath(package().system_support().absFileName(), "examples"))); docstring filter = formats.prettyName(format); - filter += " (*."; + filter += " (*.{"; // FIXME UNICODE - filter += from_utf8(formats.extension(format)); - filter += ')'; + filter += from_utf8(formats.extensions(format)); + filter += "})"; FileDialog::Result result = dlg.open(toqstr(initpath), fileFilters(toqstr(filter))); @@ -2018,6 +2251,18 @@ void GuiView::importDocument(string const & argument) // get absolute path of file FileName const fullname(support::makeAbsPath(filename)); + // Can happen if the user entered a path into the dialog + // (see bug #7437) + if (fullname.onlyFileName().empty()) { + docstring msg = bformat(_("The file name '%1$s' is invalid!\n" + "Aborting import."), + from_utf8(fullname.absFileName())); + frontend::Alert::error(_("File name error"), msg); + message(_("Canceled.")); + return; + } + + FileName const lyxfile(support::changeExtension(fullname.absFileName(), ".lyx")); // Check if the document already is open @@ -2100,91 +2345,45 @@ void GuiView::insertLyXFile(docstring const & fname) // FIXME UNICODE FileName filename(to_utf8(fname)); - - if (!filename.empty()) { - bv->insertLyXFile(filename); - return; - } - - // Launch a file browser - // FIXME UNICODE - string initpath = lyxrc.document_path; - string const trypath = bv->buffer().filePath(); - // If directory is writeable, use this as default. - if (FileName(trypath).isDirWritable()) - initpath = trypath; - - // FIXME UNICODE - FileDialog dlg(qt_("Select LyX document to insert"), LFUN_FILE_INSERT); - dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path)); - dlg.setButton2(qt_("Examples|#E#e"), - toqstr(addPath(package().system_support().absFileName(), - "examples"))); - - FileDialog::Result result = dlg.open(toqstr(initpath), - QStringList(qt_("LyX Documents (*.lyx)"))); - - if (result.first == FileDialog::Later) - return; - - // FIXME UNICODE - filename.set(fromqstr(result.second)); - - // check selected filename if (filename.empty()) { - // emit message signal. - message(_("Canceled.")); - return; - } - - bv->insertLyXFile(filename); -} - - -void GuiView::insertPlaintextFile(docstring const & fname, - bool asParagraph) -{ - BufferView * bv = documentBufferView(); - if (!bv) - return; - - if (!fname.empty() && !FileName::isAbsolute(to_utf8(fname))) { - message(_("Absolute filename expected.")); - return; - } - - // FIXME UNICODE - FileName filename(to_utf8(fname)); - - if (!filename.empty()) { - bv->insertPlaintextFile(filename, asParagraph); - return; - } + // Launch a file browser + // FIXME UNICODE + string initpath = lyxrc.document_path; + string const trypath = bv->buffer().filePath(); + // If directory is writeable, use this as default. + if (FileName(trypath).isDirWritable()) + initpath = trypath; - FileDialog dlg(qt_("Select file to insert"), (asParagraph ? - LFUN_FILE_INSERT_PLAINTEXT_PARA : LFUN_FILE_INSERT_PLAINTEXT)); + // FIXME UNICODE + FileDialog dlg(qt_("Select LyX document to insert")); + dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path)); + dlg.setButton2(qt_("Examples|#E#e"), + toqstr(addPath(package().system_support().absFileName(), + "examples"))); - FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()), - QStringList(qt_("All Files (*)"))); + FileDialog::Result result = dlg.open(toqstr(initpath), + QStringList(qt_("LyX Documents (*.lyx)"))); - if (result.first == FileDialog::Later) - return; + if (result.first == FileDialog::Later) + return; - // FIXME UNICODE - filename.set(fromqstr(result.second)); + // FIXME UNICODE + filename.set(fromqstr(result.second)); - // check selected filename - if (filename.empty()) { - // emit message signal. - message(_("Canceled.")); - return; + // check selected filename + if (filename.empty()) { + // emit message signal. + message(_("Canceled.")); + return; + } } - bv->insertPlaintextFile(filename, asParagraph); + bv->insertLyXFile(filename); + bv->buffer().errors("Parse"); } -bool GuiView::renameBuffer(Buffer & b, docstring const & newname) +bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind) { FileName fname = b.fileName(); FileName const oldname = fname; @@ -2198,8 +2397,7 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname) // No argument? Ask user through dialog. // FIXME UNICODE - FileDialog dlg(qt_("Choose a filename to save document as"), - LFUN_BUFFER_WRITE_AS); + FileDialog dlg(qt_("Choose a filename to save document as")); dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path)); dlg.setButton2(qt_("Templates|#T#t"), toqstr(lyxrc.template_path)); @@ -2223,6 +2421,162 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname) fname.changeExtension(".lyx"); } + // fname is now the new Buffer location. + + // if there is already a Buffer open with this name, we do not want + // to have another one. (the second test makes sure we're not just + // trying to overwrite ourselves, which is fine.) + if (theBufferList().exists(fname) && fname != oldname + && theBufferList().getBuffer(fname) != &b) { + docstring const text = + bformat(_("The file\n%1$s\nis already open in your current session.\n" + "Please close it before attempting to overwrite it.\n" + "Do you want to choose a new filename?"), + from_utf8(fname.absFileName())); + int const ret = Alert::prompt(_("Chosen File Already Open"), + text, 0, 1, _("&Rename"), _("&Cancel")); + switch (ret) { + case 0: return renameBuffer(b, docstring(), kind); + case 1: return false; + } + //return false; + } + + bool const existsLocal = fname.exists(); + bool const existsInVC = LyXVC::fileInVC(fname); + if (existsLocal || existsInVC) { + docstring const file = makeDisplayPath(fname.absFileName(), 30); + if (kind != LV_WRITE_AS && existsInVC) { + // renaming to a name that is already in VC + // would not work + docstring text = bformat(_("The document %1$s " + "is already registered.\n\n" + "Do you want to choose a new name?"), + file); + docstring const title = (kind == LV_VC_RENAME) ? + _("Rename document?") : _("Copy document?"); + docstring const button = (kind == LV_VC_RENAME) ? + _("&Rename") : _("&Copy"); + int const ret = Alert::prompt(title, text, 0, 1, + button, _("&Cancel")); + switch (ret) { + case 0: return renameBuffer(b, docstring(), kind); + case 1: return false; + } + } + + if (existsLocal) { + docstring text = bformat(_("The document %1$s " + "already exists.\n\n" + "Do you want to overwrite that document?"), + file); + int const ret = Alert::prompt(_("Overwrite document?"), + text, 0, 2, _("&Overwrite"), + _("&Rename"), _("&Cancel")); + switch (ret) { + case 0: break; + case 1: return renameBuffer(b, docstring(), kind); + case 2: return false; + } + } + } + + switch (kind) { + case LV_VC_RENAME: { + string msg = b.lyxvc().rename(fname); + if (msg.empty()) + return false; + message(from_utf8(msg)); + break; + } + case LV_VC_COPY: { + string msg = b.lyxvc().copy(fname); + if (msg.empty()) + return false; + message(from_utf8(msg)); + break; + } + case LV_WRITE_AS: + break; + } + // LyXVC created the file already in case of LV_VC_RENAME or + // LV_VC_COPY, but call saveBuffer() nevertheless to get + // relative paths of included stuff right if we moved e.g. from + // /a/b.lyx to /a/c/b.lyx. + + bool const saved = saveBuffer(b, fname); + if (saved) + b.reload(); + return saved; +} + + +struct PrettyNameComparator +{ + bool operator()(Format const *first, Format const *second) const { + return compare_no_case(translateIfPossible(from_ascii(first->prettyname())), + translateIfPossible(from_ascii(second->prettyname()))) <= 0; + } +}; + + +bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat) +{ + FileName fname = b.fileName(); + + FileDialog dlg(qt_("Choose a filename to export the document as")); + dlg.setButton1(qt_("Documents|#o#O"), toqstr(lyxrc.document_path)); + + QStringList types; + QString const anyformat = qt_("Guess from extension (*.*)"); + types << anyformat; + Formats::const_iterator it = formats.begin(); + vector export_formats; + for (; it != formats.end(); ++it) + if (it->documentFormat()) + export_formats.push_back(&(*it)); + PrettyNameComparator cmp; + sort(export_formats.begin(), export_formats.end(), cmp); + vector::const_iterator fit = export_formats.begin(); + map fmap; + QString filter; + string ext; + for (; fit != export_formats.end(); ++fit) { + docstring const loc_prettyname = + translateIfPossible(from_utf8((*fit)->prettyname())); + QString const loc_filter = toqstr(bformat(from_ascii("%1$s (*.%2$s)"), + loc_prettyname, + from_ascii((*fit)->extension()))); + types << loc_filter; + fmap[loc_filter] = (*fit)->name(); + if (from_ascii((*fit)->name()) == iformat) { + filter = loc_filter; + ext = (*fit)->extension(); + } + } + string ofname = fname.onlyFileName(); + if (!ext.empty()) + ofname = support::changeExtension(ofname, ext); + FileDialog::Result result = + dlg.save(toqstr(fname.onlyPath().absFileName()), + types, + toqstr(ofname), + &filter); + if (result.first != FileDialog::Chosen) + return false; + + string fmt_name; + fname.set(fromqstr(result.second)); + if (filter == anyformat) + fmt_name = formats.getFormatFromExtension(fname.extension()); + else + fmt_name = fmap[filter]; + LYXERR(Debug::FILES, "filter=" << fromqstr(filter) + << ", fmt_name=" << fmt_name << ", fname=" << fname.absFileName()); + + if (fmt_name.empty() || fname.empty()) + return false; + // fname is now the new Buffer location. if (FileName(fname).exists()) { docstring const file = makeDisplayPath(fname.absFileName(), 30); @@ -2234,55 +2588,34 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname) text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel")); switch (ret) { case 0: break; - case 1: return renameBuffer(b, docstring()); + case 1: return exportBufferAs(b, from_ascii(fmt_name)); case 2: return false; } } - FileName oldauto = b.getAutosaveFileName(); - - // Ok, change the name of the buffer - b.setFileName(fname.absFileName()); - b.markDirty(); - bool unnamed = b.isUnnamed(); - b.setUnnamed(false); - b.saveCheckSum(fname); - - // bring the autosave file with us, just in case. - b.moveAutosaveFile(oldauto); - - if (!saveBuffer(b)) { - oldauto = b.getAutosaveFileName(); - b.setFileName(oldname.absFileName()); - b.setUnnamed(unnamed); - b.saveCheckSum(oldname); - b.moveAutosaveFile(oldauto); - return false; - } - - // validate version control data and - // correct buffer title - b.lyxvc().file_found_hook(b.fileName()); - b.updateTitles(); + FuncRequest cmd(LFUN_BUFFER_EXPORT, fmt_name + " " + fname.absFileName()); + DispatchResult dr; + dispatch(cmd, dr); + return dr.dispatched(); +} - // the file has now been saved to the new location. - // we need to check that the locations of child buffers - // are still valid. - b.checkChildBuffers(); - return true; +bool GuiView::saveBuffer(Buffer & b) +{ + return saveBuffer(b, FileName()); } -bool GuiView::saveBuffer(Buffer & b) +bool GuiView::saveBuffer(Buffer & b, FileName const & fn) { if (workArea(b) && workArea(b)->inDialogMode()) return true; - if (b.isUnnamed()) + if (fn.empty() && b.isUnnamed()) return renameBuffer(b, docstring()); - if (b.save()) { + bool const success = (fn.empty() ? b.save() : b.saveAs(fn)); + if (success) { theSession().lastFiles().add(b.fileName()); return true; } @@ -2308,7 +2641,7 @@ bool GuiView::saveBuffer(Buffer & b) return false; } - return saveBuffer(b); + return saveBuffer(b, fn); } @@ -2318,10 +2651,45 @@ bool GuiView::hideWorkArea(GuiWorkArea * wa) } +// We only want to close the buffer if it is not visible in other workareas +// of the same view, nor in other views, and if this is not a child bool GuiView::closeWorkArea(GuiWorkArea * wa) { Buffer & buf = wa->bufferView().buffer(); - return closeWorkArea(wa, !buf.parent()); + + bool last_wa = d.countWorkAreasOf(buf) == 1 + && !inOtherView(buf) && !buf.parent(); + + bool close_buffer = last_wa; + + if (last_wa) { + if (lyxrc.close_buffer_with_last_view == "yes") + ; // Nothing to do + else if (lyxrc.close_buffer_with_last_view == "no") + close_buffer = false; + else { + docstring file; + if (buf.isUnnamed()) + file = from_utf8(buf.fileName().onlyFileName()); + else + file = buf.fileName().displayName(30); + docstring const text = bformat( + _("Last view on document %1$s is being closed.\n" + "Would you like to close or hide the document?\n" + "\n" + "Hidden documents can be displayed back through\n" + "the menu: View->Hidden->...\n" + "\n" + "To remove this question, set your preference in:\n" + " Tools->Preferences->Look&Feel->UserInterface\n" + ), file); + int ret = Alert::prompt(_("Close or hide document?"), + text, 0, 1, _("&Close"), _("&Hide")); + close_buffer = (ret == 0); + } + } + + return closeWorkArea(wa, close_buffer); } @@ -2402,7 +2770,8 @@ bool GuiView::closeWorkArea(GuiWorkArea * wa, bool close_buffer) Buffer & buf = wa->bufferView().buffer(); if (close_buffer && GuiViewPrivate::busyBuffers.contains(&buf)) { - Alert::warning(_("Close document "), _("Document could not be closed because it is processed by LyX.")); + Alert::warning(_("Close document"), + _("Document could not be closed because it is being processed by LyX.")); return false; } @@ -2424,6 +2793,7 @@ bool GuiView::closeBuffer(Buffer & buf) // so no need to do it here. This will ensure that the children end up // in the session file in the correct order. If we close the master // buffer, we can close or release the child buffers here too. + bool success = true; if (!closing_) { ListOfBuffers clist = buf.getChildren(); ListOfBuffers::const_iterator it = clist.begin(); @@ -2435,23 +2805,30 @@ bool GuiView::closeBuffer(Buffer & buf) Buffer * child_buf = *it; GuiWorkArea * child_wa = workArea(*child_buf); if (child_wa) { - if (!closeWorkArea(child_wa, true)) - return false; + if (!closeWorkArea(child_wa, true)) { + success = false; + break; + } } else theBufferList().releaseChild(&buf, child_buf); } } - // goto bookmark to update bookmark pit. - //FIXME: we should update only the bookmarks related to this buffer! - LYXERR(Debug::DEBUG, "GuiView::closeBuffer()"); - for (size_t i = 0; i < theSession().bookmarks().size(); ++i) - guiApp->gotoBookmark(i+1, false, false); - - if (saveBufferIfNeeded(buf, false)) { - buf.removeAutosaveFile(); - theBufferList().release(&buf); - return true; + if (success) { + // goto bookmark to update bookmark pit. + //FIXME: we should update only the bookmarks related to this buffer! + LYXERR(Debug::DEBUG, "GuiView::closeBuffer()"); + for (size_t i = 0; i < theSession().bookmarks().size(); ++i) + guiApp->gotoBookmark(i+1, false, false); + + if (saveBufferIfNeeded(buf, false)) { + buf.removeAutosaveFile(); + theBufferList().release(&buf); + return true; + } } + // open all children again to avoid a crash because of dangling + // pointers (bug 6603) + buf.updateBuffer(); return false; } @@ -2468,7 +2845,7 @@ bool GuiView::closeTabWorkArea(TabWorkArea * twa) // in another view, and if this is not a child and if we are closing // a view (not a tabgroup). bool const close_buffer = - !inMultiViews(wa) && !b.parent() && closing_; + !inOtherView(b) && !b.parent() && closing_; if (!closeWorkArea(wa, close_buffer)) return false; @@ -2518,14 +2895,14 @@ bool GuiView::saveBufferIfNeeded(Buffer & buf, bool hiding) return false; break; case 1: - // if we crash after this we could - // have no autosave file but I guess - // this is really improbable (Jug) - // Sometime improbable things happen, bug 6857 (ps) + // If we crash after this we could have no autosave file + // but I guess this is really improbable (Jug). + // Sometimes improbable things happen: + // - see bug http://www.lyx.org/trac/ticket/6587 (ps) // buf.removeAutosaveFile(); if (hiding) // revert all changes - buf.reload(); + reloadBuffer(buf); buf.markClean(); break; case 2: @@ -2544,17 +2921,15 @@ bool GuiView::inMultiTabs(GuiWorkArea * wa) if (wa_ && wa_ != wa) return true; } - return inMultiViews(wa); + return inOtherView(buf); } -bool GuiView::inMultiViews(GuiWorkArea * wa) +bool GuiView::inOtherView(Buffer & buf) { QList const ids = guiApp->viewIds(); - Buffer & buf = wa->bufferView().buffer(); - int found_twa = 0; - for (int i = 0; i != ids.size() && found_twa <= 1; ++i) { + for (int i = 0; i != ids.size(); ++i) { if (id_ == ids[i]) continue; @@ -2565,26 +2940,29 @@ bool GuiView::inMultiViews(GuiWorkArea * wa) } -void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np) +void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move) { - Buffer * const curbuf = documentBufferView() - ? &documentBufferView()->buffer() : 0; - Buffer * nextbuf = curbuf; - while (true) { - if (np == NEXTBUFFER) - nextbuf = theBufferList().next(nextbuf); - else - nextbuf = theBufferList().previous(nextbuf); - if (nextbuf == curbuf) - break; - if (nextbuf == 0) { - nextbuf = curbuf; - break; + if (!documentBufferView()) + return; + + if (TabWorkArea * twa = d.currentTabWorkArea()) { + Buffer * const curbuf = &documentBufferView()->buffer(); + int nwa = twa->count(); + for (int i = 0; i < nwa; ++i) { + if (&workArea(i)->bufferView().buffer() == curbuf) { + int next_index; + if (np == NEXTBUFFER) + next_index = (i == nwa - 1 ? 0 : i + 1); + else + next_index = (i == 0 ? nwa - 1 : i - 1); + if (move) + twa->moveTab(i, next_index); + else + setBuffer(&workArea(next_index)->bufferView().buffer()); + break; + } } - if (workArea(*nextbuf)) - break; } - setBuffer(nextbuf); } @@ -2619,13 +2997,10 @@ static bool ensureBufferClean(Buffer * buffer) } -bool GuiView::reloadBuffer() +bool GuiView::reloadBuffer(Buffer & buf) { - Buffer * buffer = documentBufferView() - ? &(documentBufferView()->buffer()) : 0; - if (buffer) - return buffer->reload(); - return false; + Buffer::ReadStatus status = buf.reload(); + return status == Buffer::ReadSuccess; } @@ -2634,15 +3009,16 @@ void GuiView::checkExternallyModifiedBuffers() BufferList::iterator bit = theBufferList().begin(); BufferList::iterator const bend = theBufferList().end(); for (; bit != bend; ++bit) { - if ((*bit)->fileName().exists() - && (*bit)->isExternallyModified(Buffer::checksum_method)) { + Buffer * buf = *bit; + if (buf->fileName().exists() + && buf->isExternallyModified(Buffer::checksum_method)) { docstring text = bformat(_("Document \n%1$s\n has been externally modified." " Reload now? Any local changes will be lost."), - from_utf8((*bit)->absFileName())); + from_utf8(buf->absFileName())); int const ret = Alert::prompt(_("Reload externally changed document?"), text, 0, 1, _("&Reload"), _("&Cancel")); if (!ret) - (*bit)->reload(); + reloadBuffer(*buf); } } } @@ -2659,19 +3035,55 @@ void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr) break; if (!buffer->lyxvc().inUse()) { if (buffer->lyxvc().registrer()) { - reloadBuffer(); - dr.suppressMessageUpdate(); + reloadBuffer(*buffer); + dr.clearMessageUpdate(); } } break; + case LFUN_VC_RENAME: + case LFUN_VC_COPY: { + if (!buffer || !ensureBufferClean(buffer)) + break; + if (buffer->lyxvc().inUse() && !buffer->isReadonly()) { + if (buffer->lyxvc().isCheckInWithConfirmation()) { + // Some changes are not yet committed. + // We test here and not in getStatus(), since + // this test is expensive. + string log; + LyXVC::CommandResult ret = + buffer->lyxvc().checkIn(log); + dr.setMessage(log); + if (ret == LyXVC::ErrorCommand || + ret == LyXVC::VCSuccess) + reloadBuffer(*buffer); + if (buffer->lyxvc().isCheckInWithConfirmation()) { + frontend::Alert::error( + _("Revision control error."), + _("Document could not be checked in.")); + break; + } + } + RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ? + LV_VC_RENAME : LV_VC_COPY; + renameBuffer(*buffer, cmd.argument(), kind); + } + break; + } + case LFUN_VC_CHECK_IN: if (!buffer || !ensureBufferClean(buffer)) break; if (buffer->lyxvc().inUse() && !buffer->isReadonly()) { - dr.setMessage(buffer->lyxvc().checkIn()); - if (!dr.message().empty()) - reloadBuffer(); + string log; + LyXVC::CommandResult ret = buffer->lyxvc().checkIn(log); + dr.setMessage(log); + // Only skip reloading if the checkin was cancelled or + // an error occurred before the real checkin VCS command + // was executed, since the VCS might have changed the + // file even if it could not checkin successfully. + if (ret == LyXVC::ErrorCommand || ret == LyXVC::VCSuccess) + reloadBuffer(*buffer); } break; @@ -2680,7 +3092,7 @@ void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr) break; if (buffer->lyxvc().inUse()) { dr.setMessage(buffer->lyxvc().checkOut()); - reloadBuffer(); + reloadBuffer(*buffer); } break; @@ -2695,23 +3107,24 @@ void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr) _("Error when setting the locking property.")); } else { dr.setMessage(res); - reloadBuffer(); + reloadBuffer(*buffer); } } break; case LFUN_VC_REVERT: LASSERT(buffer, return); - buffer->lyxvc().revert(); - reloadBuffer(); - dr.suppressMessageUpdate(); + if (buffer->lyxvc().revert()) { + reloadBuffer(*buffer); + dr.clearMessageUpdate(); + } break; case LFUN_VC_UNDO_LAST: LASSERT(buffer, return); buffer->lyxvc().undoLast(); - reloadBuffer(); - dr.suppressMessageUpdate(); + reloadBuffer(*buffer); + dr.clearMessageUpdate(); break; case LFUN_VC_REPO_UPDATE: @@ -2759,7 +3172,7 @@ void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr) if (contains(flag, 'I')) buffer->markDirty(); if (contains(flag, 'R')) - reloadBuffer(); + reloadBuffer(*buffer); break; } @@ -2809,29 +3222,19 @@ void GuiView::openChildDocument(string const & fname) FileName const filename = support::makeAbsPath(fname, buffer.filePath()); documentBufferView()->saveBookmark(false); Buffer * child = 0; - bool parsed = false; if (theBufferList().exists(filename)) { child = theBufferList().getBuffer(filename); + setBuffer(child); } else { message(bformat(_("Opening child document %1$s..."), - makeDisplayPath(filename.absFileName()))); + makeDisplayPath(filename.absFileName()))); child = loadDocument(filename, false); - parsed = true; } - if (!child) - return; - // Set the parent name of the child document. // This makes insertion of citations and references in the child work, // when the target is in the parent or another child document. - child->setParent(&buffer); - - // I don't think this is needed, since it will be called in - // setBuffer(). - // child->masterBuffer()->updateBuffer(); - setBuffer(child); - if (parsed) - child->errors("Parse"); + if (child) + child->setParent(&buffer); } @@ -2878,10 +3281,6 @@ bool GuiView::goToFileRow(string const & argument) buf = loadDocument(s); if (!buf) return false; - // I don't think this is needed. loadDocument() calls - // setBuffer(), which calls updateBuffer(). - // buf->updateBuffer(); - buf->errors("Parse"); } else { message(bformat( _("File does not exist: %1$s"), @@ -2889,95 +3288,146 @@ bool GuiView::goToFileRow(string const & argument) return false; } } + if (!buf) { + message(bformat( + _("No buffer for file: %1$s."), + makeDisplayPath(file_name)) + ); + return false; + } setBuffer(buf); documentBufferView()->setCursorFromRow(row); return true; } -#if (QT_VERSION >= 0x040400) template -docstring GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * buffer, string const & format, string const & msg) +Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format) { - bool const update_unincluded = - buffer->params().maintain_unincluded_children - && !buffer->params().getIncludedChildren().empty(); - bool const success = func(format, update_unincluded); - delete buffer; + Buffer::ExportStatus const status = func(format); + + // the cloning operation will have produced a clone of the entire set of + // documents, starting from the master. so we must delete those. + Buffer * mbuf = const_cast(clone->masterBuffer()); + delete mbuf; busyBuffers.remove(orig); - return success - ? bformat(_("Successful " + msg + " to format: %1$s"), from_utf8(format)) - : bformat(_("Error " + msg + " format: %1$s"), from_utf8(format)); + return status; } -docstring GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * buffer, string const & format) + +Buffer::ExportStatus GuiView::GuiViewPrivate::compileAndDestroy(Buffer const * orig, Buffer * clone, string const & format) { - bool (Buffer::* mem_func)(std::string const &, bool, bool) const = &Buffer::doExport; - return runAndDestroy(bind(mem_func, buffer, _1, true, _2), orig, buffer, format, "export"); + Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport; + return runAndDestroy(lyx::bind(mem_func, clone, _1, true), orig, clone, format); } -docstring GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * buffer, string const & format) -{ - bool (Buffer::* mem_func)(std::string const &, bool, bool) const = &Buffer::doExport; - return runAndDestroy(bind(mem_func, buffer, _1, false, _2), orig, buffer, format, "export"); +Buffer::ExportStatus GuiView::GuiViewPrivate::exportAndDestroy(Buffer const * orig, Buffer * clone, string const & format) +{ + Buffer::ExportStatus (Buffer::* mem_func)(std::string const &, bool) const = &Buffer::doExport; + return runAndDestroy(lyx::bind(mem_func, clone, _1, false), orig, clone, format); } -docstring GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * buffer, string const & format) + +Buffer::ExportStatus GuiView::GuiViewPrivate::previewAndDestroy(Buffer const * orig, Buffer * clone, string const & format) { - bool(Buffer::* mem_func)(std::string const &, bool) const = &Buffer::preview; - return runAndDestroy(bind(mem_func, buffer, _1, _2), orig, buffer, format, "preview"); + Buffer::ExportStatus (Buffer::* mem_func)(std::string const &) const = &Buffer::preview; + return runAndDestroy(lyx::bind(mem_func, clone, _1), orig, clone, format); } -#endif bool GuiView::GuiViewPrivate::asyncBufferProcessing( - string const & argument, - Buffer const * used_buffer, - docstring const & msg, - docstring (*asyncFunc)(Buffer const *, Buffer *, string const &), - bool (Buffer::*syncFunc)(string const &, bool, bool) const, - bool (Buffer::*previewFunc)(string const &, bool) const) -{ - if (!used_buffer) { + string const & argument, + Buffer const * used_buffer, + docstring const & msg, + Buffer::ExportStatus (*asyncFunc)(Buffer const *, Buffer *, string const &), + Buffer::ExportStatus (Buffer::*syncFunc)(string const &, bool) const, + Buffer::ExportStatus (Buffer::*previewFunc)(string const &) const) +{ + if (!used_buffer) return false; - } - gv_->processingThreadStarted(); + string format = argument; - if (format.empty()) { - format = used_buffer->getDefaultOutputFormat(); - } -#if EXPORT_in_THREAD && (QT_VERSION >= 0x040400) + if (format.empty()) + format = used_buffer->params().getDefaultOutputFormat(); + processing_format = format; if (!msg.empty()) { progress_->clearMessages(); gv_->message(msg); } +#if EXPORT_in_THREAD GuiViewPrivate::busyBuffers.insert(used_buffer); - QFuture f = QtConcurrent::run( + Buffer * cloned_buffer = used_buffer->cloneFromMaster(); + if (!cloned_buffer) { + Alert::error(_("Export Error"), + _("Error cloning the Buffer.")); + return false; + } + QFuture f = QtConcurrent::run( asyncFunc, used_buffer, - used_buffer->clone(), + cloned_buffer, format); setPreviewFuture(f); - last_export_format = used_buffer->bufferFormat(); + last_export_format = used_buffer->params().bufferFormat(); (void) syncFunc; (void) previewFunc; // We are asynchronous, so we don't know here anything about the success return true; #else - bool const update_unincluded = - used_buffer->params().maintain_unincluded_children && - !used_buffer->params().getIncludedChildren().empty(); + Buffer::ExportStatus status; if (syncFunc) { - return (used_buffer->*syncFunc)(format, true, update_unincluded); + // TODO check here if it breaks exporting with Qt < 4.4 + status = (used_buffer->*syncFunc)(format, true); } else if (previewFunc) { - return (used_buffer->*previewFunc)(format, update_unincluded); - } + status = (used_buffer->*previewFunc)(format); + } else + return false; + handleExportStatus(gv_, status, format); (void) asyncFunc; - return false; + return (status == Buffer::ExportSuccess + || status == Buffer::PreviewSuccess); #endif } +void GuiView::dispatchToBufferView(FuncRequest const & cmd, DispatchResult & dr) +{ + BufferView * bv = currentBufferView(); + LASSERT(bv, return); + + // Let the current BufferView dispatch its own actions. + bv->dispatch(cmd, dr); + if (dr.dispatched()) + return; + + // Try with the document BufferView dispatch if any. + BufferView * doc_bv = documentBufferView(); + if (doc_bv && doc_bv != bv) { + doc_bv->dispatch(cmd, dr); + if (dr.dispatched()) + return; + } + + // Then let the current Cursor dispatch its own actions. + bv->cursor().dispatch(cmd); + + // update completion. We do it here and not in + // processKeySym to avoid another redraw just for a + // changed inline completion + if (cmd.origin() == FuncRequest::KEYBOARD) { + if (cmd.action() == LFUN_SELF_INSERT + || (cmd.action() == LFUN_ERT_INSERT && bv->cursor().inMathed())) + updateCompletion(bv->cursor(), true, true); + else if (cmd.action() == LFUN_CHAR_DELETE_BACKWARD) + updateCompletion(bv->cursor(), false, true); + else + updateCompletion(bv->cursor(), false, false); + } + + dr = bv->cursor().result(); +} + + void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) { BufferView * bv = currentBufferView(); @@ -3010,65 +3460,73 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_BUFFER_EXPORT: { if (!doc_buffer) break; + FileName target_dir = doc_buffer->fileName().onlyPath(); + string const dest = cmd.getArg(1); + if (!dest.empty() && FileName::isAbsolute(dest)) + target_dir = FileName(support::onlyPath(dest)); // GCC only sees strfwd.h when building merged if (::lyx::operator==(cmd.argument(), "custom")) { dispatch(FuncRequest(LFUN_DIALOG_SHOW, "sendto"), dr); break; } -#if 0 - // TODO Remove if we could export asynchronous - if (!doc_buffer->doExport(argument, false)) { - dr.setError(true); - dr.setMessage(bformat(_("Error exporting to format: %1$s."), - cmd.argument())); + if (!target_dir.isDirWritable()) { + exportBufferAs(*doc_buffer, cmd.argument()); + break; } -#else /* TODO/Review: Is it a problem to also export the children? - See the update_unincluded flag */ + See the update_unincluded flag */ d.asyncBufferProcessing(argument, - doc_buffer, - _("Exporting ..."), - &GuiViewPrivate::exportAndDestroy, - &Buffer::doExport, - 0); + doc_buffer, + _("Exporting ..."), + &GuiViewPrivate::exportAndDestroy, + &Buffer::doExport, + 0); // TODO Inform user about success -#endif + break; + } + + case LFUN_BUFFER_EXPORT_AS: { + LASSERT(doc_buffer, break); + docstring f = cmd.argument(); + if (f.empty()) + f = from_ascii(doc_buffer->params().getDefaultOutputFormat()); + exportBufferAs(*doc_buffer, f); break; } case LFUN_BUFFER_UPDATE: { d.asyncBufferProcessing(argument, - doc_buffer, - _("Exporting ..."), - &GuiViewPrivate::compileAndDestroy, - &Buffer::doExport, - 0); + doc_buffer, + _("Exporting ..."), + &GuiViewPrivate::compileAndDestroy, + &Buffer::doExport, + 0); break; } case LFUN_BUFFER_VIEW: { d.asyncBufferProcessing(argument, - doc_buffer, - _("Previewing ..."), - &GuiViewPrivate::previewAndDestroy, - 0, - &Buffer::preview); + doc_buffer, + _("Previewing ..."), + &GuiViewPrivate::previewAndDestroy, + 0, + &Buffer::preview); break; } case LFUN_MASTER_BUFFER_UPDATE: { d.asyncBufferProcessing(argument, - (doc_buffer ? doc_buffer->masterBuffer() : 0), - docstring(), - &GuiViewPrivate::compileAndDestroy, - &Buffer::doExport, - 0); + (doc_buffer ? doc_buffer->masterBuffer() : 0), + docstring(), + &GuiViewPrivate::compileAndDestroy, + &Buffer::doExport, + 0); break; } case LFUN_MASTER_BUFFER_VIEW: { d.asyncBufferProcessing(argument, - (doc_buffer ? doc_buffer->masterBuffer() : 0), - docstring(), - &GuiViewPrivate::previewAndDestroy, - 0, &Buffer::preview); + (doc_buffer ? doc_buffer->masterBuffer() : 0), + docstring(), + &GuiViewPrivate::previewAndDestroy, + 0, &Buffer::preview); break; } case LFUN_BUFFER_SWITCH: { @@ -3114,11 +3572,19 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_BUFFER_NEXT: - gotoNextOrPreviousBuffer(NEXTBUFFER); + gotoNextOrPreviousBuffer(NEXTBUFFER, false); + break; + + case LFUN_BUFFER_MOVE_NEXT: + gotoNextOrPreviousBuffer(NEXTBUFFER, true); break; case LFUN_BUFFER_PREVIOUS: - gotoNextOrPreviousBuffer(PREVBUFFER); + gotoNextOrPreviousBuffer(PREVBUFFER, false); + break; + + case LFUN_BUFFER_MOVE_PREVIOUS: + gotoNextOrPreviousBuffer(PREVBUFFER, true); break; case LFUN_COMMAND_EXECUTE: { @@ -3145,20 +3611,42 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) insertLyXFile(cmd.argument()); break; - case LFUN_FILE_INSERT_PLAINTEXT_PARA: - insertPlaintextFile(cmd.argument(), true); - break; - case LFUN_FILE_INSERT_PLAINTEXT: - insertPlaintextFile(cmd.argument(), false); + case LFUN_FILE_INSERT_PLAINTEXT_PARA: { + string const fname = to_utf8(cmd.argument()); + if (!fname.empty() && !FileName::isAbsolute(fname)) { + dr.setMessage(_("Absolute filename expected.")); + break; + } + + FileName filename(fname); + if (fname.empty()) { + FileDialog dlg(qt_("Select file to insert")); + + FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()), + QStringList(qt_("All Files (*)"))); + + if (result.first == FileDialog::Later || result.second.isEmpty()) { + dr.setMessage(_("Canceled.")); + break; + } + + filename.set(fromqstr(result.second)); + } + + if (bv) { + FuncRequest const new_cmd(cmd, filename.absoluteFilePath()); + bv->dispatch(new_cmd, dr); + } break; + } case LFUN_BUFFER_RELOAD: { LASSERT(doc_buffer, break); int ret = 0; if (!doc_buffer->isClean()) { - docstring const file = + docstring const file = makeDisplayPath(doc_buffer->absFileName(), 20); docstring text = bformat(_("Any changes will be lost. " "Are you sure you want to revert to the saved version " @@ -3169,7 +3657,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) if (ret == 0) { doc_buffer->markClean(); - reloadBuffer(); + reloadBuffer(*doc_buffer); dr.forceBufferUpdate(); } break; @@ -3234,15 +3722,14 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) //FIXME: pass DispatchResult here? inset->dispatch(currentBufferView()->cursor(), fr); } - } + } break; } case LFUN_DIALOG_TOGGLE: { - if (isDialogVisible(cmd.getArg(0))) - dispatch(FuncRequest(LFUN_DIALOG_HIDE, cmd.argument()), dr); - else - dispatch(FuncRequest(LFUN_DIALOG_SHOW, cmd.argument()), dr); + FuncCode const func_code = isDialogVisible(cmd.getArg(0)) + ? LFUN_DIALOG_HIDE : LFUN_DIALOG_SHOW; + dispatch(FuncRequest(func_code, cmd.argument()), dr); break; } @@ -3308,7 +3795,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } - case LFUN_SPLIT_VIEW: { + case LFUN_VIEW_SPLIT: { LASSERT(doc_buffer, break); string const orientation = cmd.getArg(0); d.splitter_->setOrientation(orientation == "vertical" @@ -3318,7 +3805,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) setCurrentWorkArea(wa); break; } - case LFUN_CLOSE_TAB_GROUP: + case LFUN_TAB_GROUP_CLOSE: if (TabWorkArea * twa = d.currentTabWorkArea()) { closeTabWorkArea(twa); d.current_work_area_ = 0; @@ -3333,6 +3820,21 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } break; + case LFUN_VIEW_CLOSE: + if (TabWorkArea * twa = d.currentTabWorkArea()) { + closeWorkArea(twa->currentWorkArea()); + d.current_work_area_ = 0; + twa = d.currentTabWorkArea(); + // Switch to the next GuiWorkArea in the found TabWorkArea. + if (twa) { + // Make sure the work area is up to date. + setCurrentWorkArea(twa->currentWorkArea()); + } else { + setCurrentWorkArea(0); + } + } + break; + case LFUN_COMPLETION_INLINE: if (d.current_work_area_) d.current_work_area_->completer().showInline(); @@ -3344,7 +3846,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; - case LFUN_COMPLETION_COMPLETE: + case LFUN_COMPLETE: if (d.current_work_area_) d.current_work_area_->completer().tab(); break; @@ -3384,6 +3886,8 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; case LFUN_VC_REGISTER: + case LFUN_VC_RENAME: + case LFUN_VC_COPY: case LFUN_VC_CHECK_IN: case LFUN_VC_CHECK_OUT: case LFUN_VC_REPO_UPDATE: @@ -3400,27 +3904,43 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; case LFUN_FORWARD_SEARCH: { - FileName const path(doc_buffer->temppath()); - string const texname = doc_buffer->latexName(); + Buffer const * doc_master = doc_buffer->masterBuffer(); + FileName const path(doc_master->temppath()); + string const texname = doc_master->isChild(doc_buffer) + ? DocFileName(changeExtension( + doc_buffer->absFileName(), + "tex")).mangledFileName() + : doc_buffer->latexName(); + string const fulltexname = + support::makeAbsPath(texname, doc_master->temppath()).absFileName(); + string const mastername = + removeExtension(doc_master->latexName()); FileName const dviname(addName(path.absFileName(), - support::changeExtension(texname, "dvi"))); + addExtension(mastername, "dvi"))); FileName const pdfname(addName(path.absFileName(), - support::changeExtension(texname, "pdf"))); - if (!dviname.exists() && !pdfname.exists()) { + addExtension(mastername, "pdf"))); + bool const have_dvi = dviname.exists(); + bool const have_pdf = pdfname.exists(); + if (!have_dvi && !have_pdf) { dr.setMessage(_("Please, preview the document first.")); break; } string outname = dviname.onlyFileName(); string command = lyxrc.forward_search_dvi; - if (!dviname.exists() || - pdfname.lastModified() > dviname.lastModified()) { + if (!have_dvi || (have_pdf && + pdfname.lastModified() > dviname.lastModified())) { outname = pdfname.onlyFileName(); command = lyxrc.forward_search_pdf; } - int row = doc_buffer->texrow().getRowFromIdPos(bv->cursor().paragraph().id(), bv->cursor().pos()); + DocIterator tmpcur = bv->cursor(); + // Leave math first + while (tmpcur.inMathed()) + tmpcur.pop_back(); + int row = tmpcur.inMathed() ? 0 : doc_buffer->texrow().getRowFromIdPos( + tmpcur.paragraph().id(), tmpcur.pos()); LYXERR(Debug::ACTION, "Forward search: row:" << row - << " id:" << bv->cursor().paragraph().id()); + << " id:" << tmpcur.paragraph().id()); if (!row || command.empty()) { dr.setMessage(_("Couldn't proceed.")); break; @@ -3428,6 +3948,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) string texrow = convert(row); command = subst(command, "$$n", texrow); + command = subst(command, "$$f", fulltexname); command = subst(command, "$$t", texname); command = subst(command, "$$o", outname); @@ -3436,8 +3957,16 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) one.startscript(Systemcall::DontWait, command); break; } + + case LFUN_SPELLING_CONTINUOUSLY: + lyxrc.spellcheck_continuously = !lyxrc.spellcheck_continuously; + dr.screenUpdate(Update::Force | Update::FitCursor); + break; + default: - dr.dispatched(false); + // The LFUN must be for one of BufferView, Buffer or Cursor; + // let's try that: + dispatchToBufferView(cmd, dr); break; } @@ -3445,11 +3974,19 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) if (isFullScreen()) { if (menuBar()->isVisible() && lyxrc.full_screen_menubar) menuBar()->hide(); - if (statusBar()->isVisible()) - statusBar()->hide(); } - return; + // Need to update bv because many LFUNs here might have destroyed it + bv = currentBufferView(); + + // Clear non-empty selections + // (e.g. from a "char-forward-select" followed by "char-backward-select") + if (bv) { + Cursor & cur = bv->cursor(); + if ((cur.selection() && cur.selBegin() == cur.selEnd())) { + cur.clearSelection(); + } + } } @@ -3470,7 +4007,6 @@ bool GuiView::lfunUiToggle(string const & ui_component) } else if (ui_component == "menubar") { menuBar()->setVisible(!menuBar()->isVisible()); } else -#if QT_VERSION >= 0x040300 if (ui_component == "frame") { int l, t, r, b; getContentsMargins(&l, &t, &r, &b); @@ -3482,7 +4018,6 @@ bool GuiView::lfunUiToggle(string const & ui_component) setContentsMargins(0, 0, 0, 0); } } else -#endif if (ui_component == "fullscreen") { toggleFullScreen(); } else @@ -3496,9 +4031,7 @@ void GuiView::toggleFullScreen() if (isFullScreen()) { for (int i = 0; i != d.splitter_->count(); ++i) d.tabWorkArea(i)->setFullScreen(false); -#if QT_VERSION >= 0x040300 setContentsMargins(0, 0, 0, 0); -#endif setWindowState(windowState() ^ Qt::WindowFullScreen); restoreLayout(); menuBar()->show(); @@ -3508,12 +4041,11 @@ void GuiView::toggleFullScreen() hideDialogs("prefs", 0); for (int i = 0; i != d.splitter_->count(); ++i) d.tabWorkArea(i)->setFullScreen(true); -#if QT_VERSION >= 0x040300 setContentsMargins(-2, -2, -2, -2); -#endif saveLayout(); setWindowState(windowState() ^ Qt::WindowFullScreen); - statusBar()->hide(); + if (lyxrc.full_screen_statusbar) + statusBar()->hide(); if (lyxrc.full_screen_menubar) menuBar()->hide(); if (lyxrc.full_screen_toolbars) { @@ -3614,6 +4146,7 @@ void GuiView::resetDialogs() // Make sure that no LFUN uses any GuiView. guiApp->setCurrentView(0); saveLayout(); + saveUISettings(); menuBar()->clear(); constructToolbars(); guiApp->menus().fillMenuBar(menuBar(), this, false); @@ -3705,9 +4238,12 @@ void GuiView::hideDialog(string const & name, Inset * inset) if (it == d.dialogs_.end()) return; - if (inset && currentBufferView() - && inset != currentBufferView()->editedInset(name)) - return; + if (inset) { + if (!currentBufferView()) + return; + if (inset != currentBufferView()->editedInset(name)) + return; + } Dialog * const dialog = it->second.get(); if (dialog->isVisibleView()) @@ -3771,18 +4307,15 @@ Dialog * createGuiExternal(GuiView & lv); Dialog * createGuiGraphics(GuiView & lv); Dialog * createGuiInclude(GuiView & lv); Dialog * createGuiIndex(GuiView & lv); -Dialog * createGuiLabel(GuiView & lv); Dialog * createGuiListings(GuiView & lv); Dialog * createGuiLog(GuiView & lv); Dialog * createGuiMathMatrix(GuiView & lv); -Dialog * createGuiNomenclature(GuiView & lv); Dialog * createGuiNote(GuiView & lv); Dialog * createGuiParagraph(GuiView & lv); Dialog * createGuiPhantom(GuiView & lv); Dialog * createGuiPreferences(GuiView & lv); Dialog * createGuiPrint(GuiView & lv); Dialog * createGuiPrintindex(GuiView & lv); -Dialog * createGuiPrintNomencl(GuiView & lv); Dialog * createGuiRef(GuiView & lv); Dialog * createGuiSearch(GuiView & lv); Dialog * createGuiSearchAdv(GuiView & lv); @@ -3794,7 +4327,6 @@ Dialog * createGuiTabularCreate(GuiView & lv); Dialog * createGuiTexInfo(GuiView & lv); Dialog * createGuiToc(GuiView & lv); Dialog * createGuiThesaurus(GuiView & lv); -Dialog * createGuiHyperlink(GuiView & lv); Dialog * createGuiViewSource(GuiView & lv); Dialog * createGuiWrap(GuiView & lv); Dialog * createGuiProgressView(GuiView & lv); @@ -3837,16 +4369,12 @@ Dialog * GuiView::build(string const & name) return createGuiSearchAdv(*this); if (name == "graphics") return createGuiGraphics(*this); - if (name == "href") - return createGuiHyperlink(*this); if (name == "include") return createGuiInclude(*this); if (name == "index") return createGuiIndex(*this); if (name == "index_print") return createGuiPrintindex(*this); - if (name == "label") - return createGuiLabel(*this); if (name == "listings") return createGuiListings(*this); if (name == "log") @@ -3855,10 +4383,6 @@ Dialog * GuiView::build(string const & name) return createGuiDelimiter(*this); if (name == "mathmatrix") return createGuiMathMatrix(*this); - if (name == "nomenclature") - return createGuiNomenclature(*this); - if (name == "nomencl_print") - return createGuiPrintNomencl(*this); if (name == "note") return createGuiNote(*this); if (name == "paragraph")