X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt%2FGuiView.cpp;h=5a5c1dac8d8026f33d73120eed6d3405125e1a96;hb=refs%2Fheads%2Fbugs%2F13017;hp=24969c21129e36316880fe600efe077909f26944;hpb=ae528715d336562a3d1e65fdd144797caeae25e9;p=lyx.git diff --git a/src/frontends/qt/GuiView.cpp b/src/frontends/qt/GuiView.cpp index 24969c2112..5a5c1dac8d 100644 --- a/src/frontends/qt/GuiView.cpp +++ b/src/frontends/qt/GuiView.cpp @@ -69,6 +69,8 @@ #include "Toolbars.h" #include "version.h" +#include "graphics/PreviewLoader.h" + #include "support/convert.h" #include "support/debug.h" #include "support/ExceptionMessage.h" @@ -155,11 +157,10 @@ public: return; /// The text to be written on top of the pixmap QString const htext = qt_("The Document\nProcessor[[welcome banner]]"); - QString const htextsize = qt_("1.0[[possibly scale the welcome banner text size]]"); + QString const htextsize = qt_("1.0[[translating this to different value scales the welcome banner text size for your language]]"); /// The text to be written on top of the pixmap QString const text = lyx_version ? qt_("version ") + lyx_version : qt_("unknown version"); -#if QT_VERSION >= 0x050000 QString imagedir = "images/"; FileName fname = imageLibFileSearch(imagedir, "banner", "svgz"); QSvgRenderer svgRenderer(toqstr(fname.absFileName())); @@ -171,9 +172,6 @@ public: } else { splash_ = getPixmap("images/", "banner", "png"); } -#else - splash_ = getPixmap("images/", "banner", "svgz,png"); -#endif QPainter pain(&splash_); pain.setPen(QColor(0, 0, 0)); @@ -262,11 +260,7 @@ private: /// Current ratio between physical pixels and device-independent pixels double pixelRatio() const { -#if QT_VERSION >= 0x050000 return qt_scale_factor * devicePixelRatio(); -#else - return 1.0; -#endif } qreal fontSize() const { @@ -286,11 +280,7 @@ private: /// 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 } }; @@ -523,6 +513,7 @@ public: /// QTimer statusbar_timer_; + QTimer statusbar_stats_timer_; /// auto-saving of buffers Timeout autosave_timeout_; @@ -536,6 +527,7 @@ public: string last_export_format; string processing_format; + // Buffers that are being exported static QSet busyBuffers; unsigned int smallIconSize; @@ -546,6 +538,20 @@ public: /// flag against a race condition due to multiclicks, see bug #1119 bool in_show_; + + // Timers for statistic updates in buffer + /// Current time left to the nearest info update + int time_to_update = 1000; + ///Basic step for timer in ms. Basically reaction time for short selections + int const timer_rate = 500; + /// Real stats updates infrequently. First they take long time for big buffers, second + /// they are visible for fast-repeat keyboards even for mid documents. + int const default_stats_rate = 5000; + /// Detection of new selection, so we can react fast + bool already_in_selection_ = false; + /// Maximum size of "short" selection for which we can update with faster timer_rate + int const max_sel_chars = 5000; + }; QSet GuiView::GuiViewPrivate::busyBuffers; @@ -553,8 +559,9 @@ QSet GuiView::GuiViewPrivate::busyBuffers; GuiView::GuiView(int id) : d(*new GuiViewPrivate(this)), id_(id), closing_(false), busy_(0), - command_execute_(false), minibuffer_focus_(false), toolbarsMovable_(true), - devel_mode_(false) + command_execute_(false), minibuffer_focus_(false), word_count_enabled_(true), + char_count_enabled_(true), char_nb_count_enabled_(false), + toolbarsMovable_(true), devel_mode_(false) { connect(this, SIGNAL(bufferViewChanged()), this, SLOT(onBufferViewChanged())); @@ -582,20 +589,18 @@ GuiView::GuiView(int id) } connect(&d.statusbar_timer_, SIGNAL(timeout()), this, SLOT(clearMessage())); + connect(&d.statusbar_stats_timer_, SIGNAL(timeout()), + this, SLOT(showStats())); + d.statusbar_stats_timer_.start(d.timer_rate); // We don't want to keep the window in memory if it is closed. setAttribute(Qt::WA_DeleteOnClose, true); #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. 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 #endif resetWindowTitle(); @@ -612,6 +617,10 @@ GuiView::GuiView(int id) // For Drag&Drop. setAcceptDrops(true); + QFontMetrics const fm(statusBar()->fontMetrics()); + int const iconheight = max(int(d.normalIconSize), fm.height()); + QSize const iconsize(iconheight, iconheight); + // add busy indicator to statusbar search_mode mode = theGuiApp()->imageSearchMode(); QString fn = toqstr(lyx::libFileSearch("images", "busy", "svgz", mode).absFileName()); @@ -620,18 +629,36 @@ GuiView::GuiView(int id) // make busy indicator square with 5px margins busySVG->setMaximumSize(busySVG->height() - 5, busySVG->height() - 5); busySVG->hide(); + // Add cancel button + QPixmap ps = QIcon(getPixmap("images/", "process-stop", "svgz")).pixmap(iconsize); + GuiClickableLabel * processStop = new GuiClickableLabel(statusBar()); + processStop->setPixmap(ps); + processStop->setToolTip(qt_("Click here to stop export/output process")); + processStop->hide(); + statusBar()->addPermanentWidget(processStop); connect(&d.processing_thread_watcher_, SIGNAL(started()), busySVG, SLOT(show())); connect(&d.processing_thread_watcher_, SIGNAL(finished()), busySVG, SLOT(hide())); - connect(busySVG, SIGNAL(pressed()), this, SLOT(checkCancelBackground())); + connect(&d.processing_thread_watcher_, SIGNAL(started()), + processStop, SLOT(show())); + connect(&d.processing_thread_watcher_, SIGNAL(finished()), + processStop, SLOT(hide())); + connect(processStop, SIGNAL(clicked()), this, SLOT(checkCancelBackground())); - QFontMetrics const fm(statusBar()->fontMetrics()); + connect(this, SIGNAL(scriptKilled()), busySVG, SLOT(hide())); + connect(this, SIGNAL(scriptKilled()), processStop, SLOT(hide())); + + stat_counts_ = new GuiClickableLabel(statusBar()); + stat_counts_->setAlignment(Qt::AlignCenter); + stat_counts_->setStyleSheet("padding-left: 5px; padding-right: 5px;"); + stat_counts_->hide(); + statusBar()->addPermanentWidget(stat_counts_); + + connect(stat_counts_, SIGNAL(clicked()), this, SLOT(statsPressed())); zoom_slider_ = new QSlider(Qt::Horizontal, statusBar()); - // Small size slider for macOS to prevent the status bar from enlarging - zoom_slider_->setAttribute(Qt::WA_MacSmallSize); #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) zoom_slider_->setFixedWidth(fm.horizontalAdvance('x') * 15); #else @@ -663,12 +690,22 @@ GuiView::GuiView(int id) zoom_out_->setFixedSize(s); zoom_out_->setAlignment(Qt::AlignCenter); - statusBar()->addPermanentWidget(zoom_out_); - zoom_out_->setEnabled(currentBufferView()); - statusBar()->addPermanentWidget(zoom_slider_); + + zoom_widget_ = new QWidget(statusBar()); + zoom_widget_->setAttribute(Qt::WA_MacSmallSize); + zoom_widget_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + zoom_widget_->setLayout(new QHBoxLayout()); + zoom_widget_->layout()->setSpacing(5); + zoom_widget_->layout()->setContentsMargins(0,0,0,0); + zoom_widget_->layout()->addWidget(zoom_out_); + zoom_widget_->layout()->addWidget(zoom_slider_); + zoom_widget_->layout()->addWidget(zoom_in_); + statusBar()->addPermanentWidget(zoom_widget_); + zoom_out_->setEnabled(currentBufferView() + && zoom_slider_->value() > zoom_slider_->minimum()); zoom_slider_->setEnabled(currentBufferView()); - zoom_in_->setEnabled(currentBufferView()); - statusBar()->addPermanentWidget(zoom_in_); + zoom_in_->setEnabled(currentBufferView() + && zoom_slider_->value() < zoom_slider_->maximum()); connect(zoom_slider_, SIGNAL(sliderMoved(int)), this, SLOT(zoomSliderMoved(int))); connect(zoom_slider_, SIGNAL(valueChanged(int)), this, SLOT(zoomValueChanged(int))); @@ -678,7 +715,8 @@ GuiView::GuiView(int id) // QPalette palette = statusBar()->palette(); - zoom_value_ = new QLabel(statusBar()); + zoom_value_ = new GuiClickableLabel(statusBar()); + connect(zoom_value_, SIGNAL(pressed()), this, SLOT(showZoomContextMenu())); // zoom_value_->setPalette(palette); zoom_value_->setForegroundRole(statusBar()->foregroundRole()); zoom_value_->setFixedHeight(fm.height()); @@ -694,14 +732,11 @@ GuiView::GuiView(int id) statusBar()->setContextMenuPolicy(Qt::CustomContextMenu); connect(statusBar(), SIGNAL(customContextMenuRequested(QPoint)), - this, SLOT(showZoomContextMenu())); + this, SLOT(showStatusBarContextMenu())); // enable pinch to zoom grabGesture(Qt::PinchGesture); - int const iconheight = max(int(d.normalIconSize), fm.height()); - QSize const iconsize(iconheight, iconheight); - QPixmap shellescape = QIcon(getPixmap("images/", "emblem-shellescape", "svgz,png")).pixmap(iconsize); shell_escape_ = new QLabel(statusBar()); shell_escape_->setPixmap(shellescape); @@ -743,11 +778,6 @@ GuiView::GuiView(int id) connect(this, SIGNAL(triggerShowDialog(QString const &, QString const &, Inset *)), SLOT(doShowDialog(QString const &, QString const &, Inset *))); - // set custom application bars context menu, e.g. tool bar and menu bar - setContextMenuPolicy(Qt::CustomContextMenu); - connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), - SLOT(toolBarPopup(const QPoint &))); - // Forbid too small unresizable window because it can happen // with some window manager under X11. setMinimumSize(300, 200); @@ -791,10 +821,16 @@ void GuiView::checkCancelBackground() int const ret = Alert::prompt(ttl, msg, 1, 1, _("&Cancel export"), _("Co&ntinue")); - if (ret == 0) - Systemcall::killscript(); + if (ret == 0) { + cancelExport(); + } } +void GuiView::statsPressed() +{ + DispatchResult dr; + dispatch(FuncRequest(LFUN_STATISTICS), dr); +} void GuiView::zoomSliderMoved(int value) { @@ -802,6 +838,10 @@ void GuiView::zoomSliderMoved(int value) dispatch(FuncRequest(LFUN_BUFFER_ZOOM, convert(value)), dr); scheduleRedrawWorkAreas(); zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), value))); + zoom_in_->setEnabled(currentBufferView() + && value < zoom_slider_->maximum()); + zoom_out_->setEnabled(currentBufferView() + && value > zoom_slider_->minimum()); } @@ -829,6 +869,15 @@ void GuiView::zoomOutPressed() void GuiView::showZoomContextMenu() +{ + QMenu * menu = guiApp->menus().menu(toqstr("context-zoom"), * this); + if (!menu) + return; + menu->exec(QCursor::pos()); +} + + +void GuiView::showStatusBarContextMenu() { QMenu * menu = guiApp->menus().menu(toqstr("context-statusbar"), * this); if (!menu) @@ -943,7 +992,7 @@ void GuiView::saveLayout() const settings.setValue("devel_mode", devel_mode_); settings.beginGroup("views"); settings.beginGroup(QString::number(id_)); - if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") { + if (guiApp->platformName() == "xcb") { settings.setValue("pos", pos()); settings.setValue("size", size()); } else @@ -951,7 +1000,10 @@ void GuiView::saveLayout() const settings.setValue("layout", saveState(0)); settings.setValue("icon_size", toqstr(d.iconSize(iconSize()))); settings.setValue("zoom_value_visible", zoom_value_->isVisible()); - settings.setValue("zoom_slider_visible", zoom_slider_->isVisible()); + settings.setValue("zoom_slider_visible", zoom_widget_->isVisible()); + settings.setValue("word_count_enabled", word_count_enabled_); + settings.setValue("char_count_enabled", char_count_enabled_); + settings.setValue("char_nb_count_enabled", char_nb_count_enabled_); } @@ -970,9 +1022,11 @@ void GuiView::saveUISettings() const void GuiView::setCurrentZoom(const int v) { + Q_EMIT currentZoomChanged(v); lyxrc.currentZoom = v; zoom_value_->setText(toqstr(bformat(_("[[ZOOM]]%1$d%"), v))); - Q_EMIT currentZoomChanged(v); + zoom_in_->setEnabled(currentBufferView() && v < zoom_slider_->maximum()); + zoom_out_->setEnabled(currentBufferView() && v > zoom_slider_->minimum()); } @@ -997,11 +1051,14 @@ bool GuiView::restoreLayout() zoom_value_->setVisible(settings.value("zoom_value_visible", true).toBool()); bool const show_zoom_slider = settings.value("zoom_slider_visible", true).toBool(); - zoom_slider_->setVisible(show_zoom_slider); - zoom_in_->setVisible(show_zoom_slider); - zoom_out_->setVisible(show_zoom_slider); + zoom_widget_->setVisible(show_zoom_slider); + + word_count_enabled_ = settings.value("word_count_enabled", true).toBool(); + char_count_enabled_ = settings.value("char_count_enabled", true).toBool(); + char_nb_count_enabled_ = settings.value("char_nb_count_enabled", true).toBool(); + stat_counts_->setVisible(word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_); - if (guiApp->platformName() == "qt4x11" || guiApp->platformName() == "xcb") { + if (guiApp->platformName() == "xcb") { QPoint pos = settings.value("pos", QPoint(50, 50)).toPoint(); QSize size = settings.value("size", QSize(690, 510)).toSize(); resize(size); @@ -1066,11 +1123,9 @@ void GuiView::updateLockToolbars() if (tb && tb->isMovable()) toolbarsMovable_ = true; } -#if QT_VERSION >= 0x050200 // set unified mac toolbars only when not movable as recommended: // https://doc.qt.io/qt-5/qmainwindow.html#unifiedTitleAndToolBarOnMac-prop setUnifiedTitleAndToolBarOnMac(!toolbarsMovable_); -#endif } @@ -1236,6 +1291,9 @@ void GuiView::closeEvent(QCloseEvent * close_event) { LYXERR(Debug::DEBUG, "GuiView::closeEvent()"); + // FIXME Bug #12828 bites here. If there is some other View open, then + // we really should only refuse to close if one of the Buffers open here + // is being processed. if (!GuiViewPrivate::busyBuffers.isEmpty()) { Alert::warning(_("Exit LyX"), _("LyX could not be closed because documents are being processed by LyX.")); @@ -1254,6 +1312,7 @@ void GuiView::closeEvent(QCloseEvent * close_event) // it can happen that this event arrives without selecting the view, // e.g. when clicking the close button on a background window. setFocus(); + Q_EMIT closing(id_); if (!closeWorkAreaAll()) { closing_ = false; close_event->ignore(); @@ -1271,6 +1330,7 @@ void GuiView::closeEvent(QCloseEvent * close_event) // Make sure the timer time out will not trigger a statusbar update. d.statusbar_timer_.stop(); + d.statusbar_stats_timer_.stop(); // Saving fullscreen requires additional tweaks in the toolbar code. // It wouldn't also work under linux natively. @@ -1376,6 +1436,73 @@ void GuiView::clearMessage() d.statusbar_timer_.stop(); } +void GuiView::showStats() +{ + if (!statsEnabled()) + return; + + d.time_to_update -= d.timer_rate; + + BufferView * bv = currentBufferView(); + Buffer * buf = bv ? &bv->buffer() : nullptr; + if (!buf) { + stat_counts_->hide(); + return; + } + + Cursor const & cur = bv->cursor(); + + // we start new selection and need faster update + if (!d.already_in_selection_ && cur.selection()) + d.time_to_update = 0; + + if (d.time_to_update > 0) + return; + + DocIterator from, to; + if (cur.selection()) { + from = cur.selectionBegin(); + to = cur.selectionEnd(); + d.already_in_selection_ = true; + } else { + from = doc_iterator_begin(buf); + to = doc_iterator_end(buf); + d.already_in_selection_ = false; + } + + buf->updateStatistics(from, to); + + QStringList stats; + if (word_count_enabled_) { + int const words = buf->wordCount(); + if (words == 1) + stats << toqstr(bformat(_("%1$d Word"), words)); + else + stats << toqstr(bformat(_("%1$d Words"), words)); + } + int const chars_with_blanks = buf->charCount(true); + if (char_count_enabled_) { + if (chars_with_blanks == 1) + stats << toqstr(bformat(_("%1$d Character"), chars_with_blanks)); + else + stats << toqstr(bformat(_("%1$d Characters"), chars_with_blanks)); + } + if (char_nb_count_enabled_) { + int const chars = buf->charCount(false); + if (chars == 1) + stats << toqstr(bformat(_("%1$d Character (no Blanks)"), chars)); + else + stats << toqstr(bformat(_("%1$d Characters (no Blanks)"), chars)); + } + stat_counts_->setText(stats.join(qt_(", [[stats separator]]"))); + stat_counts_->show(); + + d.time_to_update = d.default_stats_rate; + // fast updates for small selections + if (chars_with_blanks < d.max_sel_chars && cur.selection()) + d.time_to_update = d.timer_rate; +} + void GuiView::updateWindowTitle(GuiWorkArea * wa) { @@ -1391,9 +1518,7 @@ void GuiView::updateWindowTitle(GuiWorkArea * wa) // buffer-save has changed too. updateToolbars(); } -#ifndef Q_WS_MAC title += from_ascii(" - LyX"); -#endif setWindowTitle(toqstr(title)); // Sets the path for the window: this is used by OSX to // allow a context click on the title bar showing a menu @@ -1450,8 +1575,10 @@ void GuiView::onBufferViewChanged() updateDialogs(); zoom_slider_->setEnabled(currentBufferView()); zoom_value_->setEnabled(currentBufferView()); - zoom_in_->setEnabled(currentBufferView()); - zoom_out_->setEnabled(currentBufferView()); + zoom_in_->setEnabled(currentBufferView() + && zoom_slider_->value() < zoom_slider_->maximum()); + zoom_out_->setEnabled(currentBufferView() + && zoom_slider_->value() > zoom_slider_->minimum()); } @@ -1515,6 +1642,12 @@ void GuiView::showMessage() } +bool GuiView::statsEnabled() const +{ + return word_count_enabled_ || char_count_enabled_ || char_nb_count_enabled_; +} + + bool GuiView::event(QEvent * e) { switch (e->type()) @@ -1619,12 +1752,6 @@ bool GuiView::event(QEvent * e) return QMainWindow::event(e); } - case QEvent::ApplicationPaletteChange: { - // runtime switch from/to dark mode - refillToolbars(); - return QMainWindow::event(e); - } - case QEvent::Gesture: { QGestureEvent *ge = static_cast(e); QGesture *gp = ge->gesture(Qt::PinchGesture); @@ -1646,6 +1773,25 @@ bool GuiView::event(QEvent * e) return QMainWindow::event(e); } + // dark/light mode runtime switch support, OS-dependent. + // 1. Mac OS X + // Limit to Q_OS_MAC as this unnecessarily would also + // trigger on Linux with grave performance issues +#ifdef Q_OS_MAC + case QEvent::ApplicationPaletteChange: { + // We need to update metrics here to avoid a crash (#12786) + theBufferList().changed(true); + refillToolbars(); + return QMainWindow::event(e); + } +#endif + // 2. Linux + case QEvent::StyleChange: { + // We need to update metrics here to avoid a crash (#12786) + theBufferList().changed(true); + return QMainWindow::event(e); + } + default: return QMainWindow::event(e); } @@ -1695,11 +1841,7 @@ void GuiView::resetCommandExecute() double GuiView::pixelRatio() const { -#if QT_VERSION >= 0x050000 return qt_scale_factor * devicePixelRatio(); -#else - return 1.0; -#endif } @@ -1740,7 +1882,8 @@ TabWorkArea * GuiView::addTabWorkArea() QObject::connect(twa, SIGNAL(lastWorkAreaRemoved()), this, SLOT(on_lastWorkAreaRemoved())); - d.splitter_->addWidget(twa); + d.splitter_->insertWidget(d.splitter_->indexOf(d.currentTabWorkArea()) + 1, + twa); d.stack_widget_->setCurrentWidget(d.splitter_); return twa; } @@ -2324,6 +2467,14 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; } + case LFUN_CHANGES_TRACK: { + if (!doc_buffer) { + enable = false; + break; + } + return doc_buffer->getStatus(cmd, flag); + } + case LFUN_VIEW_SPLIT: if (cmd.getArg(0) == "vertical") enable = doc_buffer && (d.splitter_->count() == 1 || @@ -2333,6 +2484,11 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) d.splitter_->orientation() == Qt::Horizontal); break; + case LFUN_TAB_GROUP_NEXT: + case LFUN_TAB_GROUP_PREVIOUS: + enable = (d.splitter_->count() > 1); + break; + case LFUN_TAB_GROUP_CLOSE: enable = d.tabWorkAreaCount() > 1; break; @@ -2418,7 +2574,13 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) if (cmd.argument() == "zoomlevel") { flag.setOnOff(zoom_value_ ? zoom_value_->isVisible() : false); } else if (cmd.argument() == "zoomslider") { - flag.setOnOff(zoom_slider_ ? zoom_slider_->isVisible() : false); + flag.setOnOff(zoom_widget_ ? zoom_widget_->isVisible() : false); + } else if (cmd.argument() == "statistics-w") { + flag.setOnOff(word_count_enabled_); + } else if (cmd.argument() == "statistics-cb") { + flag.setOnOff(char_count_enabled_); + } else if (cmd.argument() == "statistics-c") { + flag.setOnOff(char_nb_count_enabled_); } else flag.setOnOff(isFullScreen()); break; @@ -2594,7 +2756,8 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_WINDOW_RAISE: break; case LFUN_FORWARD_SEARCH: - enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()); + enable = !(lyxrc.forward_search_dvi.empty() && lyxrc.forward_search_pdf.empty()) && + doc_buffer && doc_buffer->isSyncTeXenabled(); break; case LFUN_FILE_INSERT_PLAINTEXT: @@ -2668,7 +2831,7 @@ Buffer * GuiView::loadDocument(FileName const & filename, bool tolastfiles) } -void GuiView::openDocument(string const & fname) +void GuiView::openDocuments(string const & fname, int origin) { string initpath = lyxrc.document_path; @@ -2679,73 +2842,91 @@ void GuiView::openDocument(string const & fname) initpath = trypath; } - string filename; + QStringList files; if (fname.empty()) { - FileDialog dlg(qt_("Select document to open")); + FileDialog dlg(qt_("Select documents to open")); dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path)); dlg.setButton2(qt_("&Examples"), toqstr(lyxrc.example_path)); QStringList const filter({ qt_("LyX Documents (*.lyx)"), qt_("LyX Document Backups (*.lyx~)"), - qt_("All Files (*.*)") + qt_("All Files") + " " + wildcardAllFiles() }); - FileDialog::Result result = - dlg.open(toqstr(initpath), filter); + FileDialog::Results results = + dlg.openMulti(toqstr(initpath), filter); - if (result.first == FileDialog::Later) + if (results.first == FileDialog::Later) return; - filename = fromqstr(result.second); + files = results.second; // check selected filename - if (filename.empty()) { + if (files.isEmpty()) { message(_("Canceled.")); return; } } else - filename = fname; - - // get absolute path of file and add ".lyx" to the filename if - // necessary. - FileName const fullname = - fileSearch(string(), filename, "lyx", support::may_not_exist); - if (!fullname.empty()) - filename = fullname.absFileName(); - - if (!fullname.onlyPath().isDirectory()) { - Alert::warning(_("Invalid filename"), - bformat(_("The directory in the given path\n%1$s\ndoes not exist."), - from_utf8(fullname.absFileName()))); - return; - } + files << toqstr(fname); + + // iterate over all selected files + for (auto const & file : files) { + string filename = fromqstr(file); + + // get absolute path of file and add ".lyx" to the filename if + // necessary. + FileName const fullname = + fileSearch(string(), filename, "lyx", support::may_not_exist); + if (!fullname.empty()) + filename = fullname.absFileName(); + + if (!fullname.onlyPath().isDirectory()) { + Alert::warning(_("Invalid filename"), + bformat(_("The directory in the given path\n%1$s\ndoes not exist."), + from_utf8(fullname.absFileName()))); + continue; + } - // if the file doesn't exist and isn't already open (bug 6645), - // let the user create one - 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) - setBuffer(b); - return; - } + // if the file doesn't exist and isn't already open (bug 6645), + // let the user create one + if (!fullname.exists() && !theBufferList().exists(fullname) && + !LyXVC::file_not_found_hook(fullname)) { + // see bug #12609 + if (origin == FuncRequest::MENU) { + docstring const & msg = + bformat(_("File\n" + "%1$s\n" + "does not exist. Create empty file?"), + from_utf8(filename)); + int ret = Alert::prompt(_("File does not exist"), + msg, 0, 1, + _("Create &File"), + _("&Cancel")); + if (ret == 1) + continue; + } + Buffer * const b = newFile(filename, string(), true); + if (b) + setBuffer(b); + continue; + } - docstring const disp_fn = makeDisplayPath(filename); - message(bformat(_("Opening document %1$s..."), disp_fn)); + docstring const disp_fn = makeDisplayPath(filename); + message(bformat(_("Opening document %1$s..."), disp_fn)); - docstring str2; - Buffer * buf = loadDocument(fullname); - if (buf) { - str2 = bformat(_("Document %1$s opened."), disp_fn); - if (buf->lyxvc().inUse()) - str2 += " " + from_utf8(buf->lyxvc().versionString()) + - " " + _("Version control detected."); - } else { - str2 = bformat(_("Could not open document %1$s"), disp_fn); + docstring str2; + Buffer * buf = loadDocument(fullname); + if (buf) { + str2 = bformat(_("Document %1$s opened."), disp_fn); + if (buf->lyxvc().inUse()) + str2 += " " + from_utf8(buf->lyxvc().versionString()) + + " " + _("Version control detected."); + } else { + str2 = bformat(_("Could not open document %1$s"), disp_fn); + } + message(str2); } - message(str2); } // FIXME: clean that @@ -3080,8 +3261,7 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kin dlg.setButton1(qt_("D&ocuments"), toqstr(lyxrc.document_path)); dlg.setButton2(qt_("&Templates"), toqstr(lyxrc.template_path)); - if (!isLyXFileName(fname.absFileName())) - fname.changeExtension(".lyx"); + fname.ensureExtension(".lyx"); string const path = as_template ? getTemplatesPath(b) @@ -3099,8 +3279,7 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kin if (fname.empty()) return false; - if (!isLyXFileName(fname.absFileName())) - fname.changeExtension(".lyx"); + fname.ensureExtension(".lyx"); } // fname is now the new Buffer location. @@ -3248,6 +3427,8 @@ bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat) if (fmt_name.empty() || fname.empty()) return false; + fname.ensureExtension(theFormats().extension(fmt_name)); + // fname is now the new Buffer location. if (fname.exists()) { docstring const file = makeDisplayPath(fname.absFileName(), 30); @@ -3271,6 +3452,12 @@ bool GuiView::exportBufferAs(Buffer & b, docstring const & iformat) } +bool GuiView::isBufferBusy(Buffer const * b) +{ + return GuiViewPrivate::busyBuffers.contains(b); +} + + bool GuiView::saveBuffer(Buffer & b) { return saveBuffer(b, FileName()); @@ -3669,7 +3856,7 @@ void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move) for (int i = 0; i < nwa; ++i) { if (&workArea(i)->bufferView().buffer() == curbuf) { int next_index; - if (np == NEXTBUFFER) + if (np == NEXT) next_index = (i == nwa - 1 ? 0 : i + 1); else next_index = (i == 0 ? nwa - 1 : i - 1); @@ -3684,6 +3871,23 @@ void GuiView::gotoNextOrPreviousBuffer(NextOrPrevious np, bool const move) } +void GuiView::gotoNextTabWorkArea(NextOrPrevious np) +{ + int count = d.splitter_->count(); + for (int i = 0; i < count; ++i) { + if (d.tabWorkArea(i) == d.currentTabWorkArea()) { + int new_index; + if (np == NEXT) + new_index = (i == count - 1 ? 0 : i + 1); + else + new_index = (i == 0 ? count - 1 : i - 1); + setCurrentWorkArea(d.tabWorkArea(new_index)->currentWorkArea()); + break; + } + } +} + + /// make sure the document is saved static bool ensureBufferClean(Buffer * buffer) { @@ -4020,13 +4224,6 @@ bool GuiView::goToFileRow(string const & argument) } -void GuiView::toolBarPopup(const QPoint & /*pos*/) -{ - QMenu * menu = guiApp->menus().menu(toqstr("context-toolbars"), * this); - menu->exec(QCursor::pos()); -} - - template Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, Buffer const * orig, Buffer * clone, string const & format) @@ -4037,6 +4234,8 @@ Buffer::ExportStatus GuiView::GuiViewPrivate::runAndDestroy(const T& func, // documents, starting from the master. so we must delete those. Buffer * mbuf = const_cast(clone->masterBuffer()); delete mbuf; + if (orig->needToRemoveBiblioTemps()) + orig->removeBiblioTempFiles(); busyBuffers.remove(orig); return status; } @@ -4298,7 +4497,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } case LFUN_EXPORT_CANCEL: { - Systemcall::killscript(); + cancelExport(); break; } case LFUN_BUFFER_SWITCH: { @@ -4346,19 +4545,19 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_BUFFER_NEXT: - gotoNextOrPreviousBuffer(NEXTBUFFER, false); + gotoNextOrPreviousBuffer(NEXT, false); break; case LFUN_BUFFER_MOVE_NEXT: - gotoNextOrPreviousBuffer(NEXTBUFFER, true); + gotoNextOrPreviousBuffer(NEXT, true); break; case LFUN_BUFFER_PREVIOUS: - gotoNextOrPreviousBuffer(PREVBUFFER, false); + gotoNextOrPreviousBuffer(PREV, false); break; case LFUN_BUFFER_MOVE_PREVIOUS: - gotoNextOrPreviousBuffer(PREVBUFFER, true); + gotoNextOrPreviousBuffer(PREV, true); break; case LFUN_BUFFER_CHKTEX: @@ -4366,6 +4565,15 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) doc_buffer->runChktex(); break; + case LFUN_CHANGES_TRACK: { + // the actual dispatch is done in Buffer + dispatchToBufferView(cmd, dr); + // but we inform the GUI (document settings) if this is toggled + LASSERT(doc_buffer, break); + Q_EMIT changeTrackingToggled(doc_buffer->params().track_changes); + break; + } + case LFUN_COMMAND_EXECUTE: { command_execute_ = true; minibuffer_focus_ = true; @@ -4402,7 +4610,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) FileDialog dlg(qt_("Select file to insert")); FileDialog::Result result = dlg.open(toqstr(bv->buffer().filePath()), - QStringList(qt_("All Files (*)"))); + QStringList(qt_("All Files")+ " " + wildcardAllFiles())); if (result.first == FileDialog::Later || result.second.isEmpty()) { dr.setMessage(_("Canceled.")); @@ -4681,9 +4889,21 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) ? Qt::Vertical : Qt::Horizontal); TabWorkArea * twa = addTabWorkArea(); GuiWorkArea * wa = twa->addWorkArea(*doc_buffer, *this); + + wa->bufferView().copySettingsFrom(*bv); + dr.screenUpdate(Update::ForceAll); setCurrentWorkArea(wa); break; } + + case LFUN_TAB_GROUP_NEXT: + gotoNextTabWorkArea(NEXT); + break; + + case LFUN_TAB_GROUP_PREVIOUS: + gotoNextTabWorkArea(PREV); + break; + case LFUN_TAB_GROUP_CLOSE: if (TabWorkArea * twa = d.currentTabWorkArea()) { closeTabWorkArea(twa); @@ -4759,6 +4979,10 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) lyxrc.currentZoom, lyxrc.defaultZoom)); guiApp->fontLoader().update(); + // Regenerate instant previews + if (lyxrc.preview != LyXRC::PREVIEW_OFF + && doc_buffer && doc_buffer->loader()) + doc_buffer->loader()->refreshPreviews(); dr.screenUpdate(Update::ForceAll | Update::FitCursor); break; } @@ -4817,10 +5041,12 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) dr.setMessage(_("Please, preview the document first.")); break; } + bool const goto_dvi = have_dvi && !lyxrc.forward_search_dvi.empty(); + bool const goto_pdf = have_pdf && !lyxrc.forward_search_pdf.empty(); string outname = dviname.onlyFileName(); string command = lyxrc.forward_search_dvi; - if (!have_dvi || (have_pdf && - pdfname.lastModified() > dviname.lastModified())) { + if ((!goto_dvi || goto_pdf) && + pdfname.lastModified() > dviname.lastModified()) { outname = pdfname.onlyFileName(); command = lyxrc.forward_search_pdf; } @@ -4852,12 +5078,8 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; case LFUN_CITATION_OPEN: { - string pdfv, psv; - if (theFormats().getFormat("pdf")) - pdfv = theFormats().getFormat("pdf")->viewer(); - if (theFormats().getFormat("ps")) - psv = theFormats().getFormat("ps")->viewer(); - frontend::showTarget(argument, pdfv, psv); + LASSERT(doc_buffer, break); + frontend::showTarget(argument, *doc_buffer); break; } @@ -4901,9 +5123,19 @@ bool GuiView::lfunUiToggle(string const & ui_component) } else if (ui_component == "zoomlevel") { zoom_value_->setVisible(!zoom_value_->isVisible()); } else if (ui_component == "zoomslider") { - zoom_slider_->setVisible(!zoom_slider_->isVisible()); - zoom_in_->setVisible(zoom_slider_->isVisible()); - zoom_out_->setVisible(zoom_slider_->isVisible()); + zoom_widget_->setVisible(!zoom_widget_->isVisible()); + } else if (ui_component == "statistics-w") { + word_count_enabled_ = !word_count_enabled_; + if (statsEnabled()) + showStats(); + } else if (ui_component == "statistics-cb") { + char_count_enabled_ = !char_count_enabled_; + if (statsEnabled()) + showStats(); + } else if (ui_component == "statistics-c") { + char_nb_count_enabled_ = !char_nb_count_enabled_; + if (statsEnabled()) + showStats(); } else if (ui_component == "frame") { int const l = contentsMargins().left(); @@ -4925,10 +5157,20 @@ bool GuiView::lfunUiToggle(string const & ui_component) toggleFullScreen(); } else return false; + stat_counts_->setVisible(statsEnabled()); return true; } +void GuiView::cancelExport() +{ + Systemcall::killscript(); + // stop busy signal immediately so that in the subsequent + // "Export canceled" prompt the status bar icons are accurate. + Q_EMIT scriptKilled(); +} + + void GuiView::toggleFullScreen() { setWindowState(windowState() ^ Qt::WindowFullScreen); @@ -5069,16 +5311,17 @@ Dialog * GuiView::findOrBuild(string const & name, bool hide_it) return dialog; dialog = build(name); - d.dialogs_[name].reset(dialog); -#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) - // Force a uniform style for group boxes - // On Mac non-flat works better, on Linux flat is standard - flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa"); -#endif - if (lyxrc.allow_geometry_session) - dialog->restoreSession(); - if (hide_it) - dialog->hideView(); + if (dialog) { + + d.dialogs_[name].reset(dialog); + // Force a uniform style for group boxes + // On Mac non-flat works better, on Linux flat is standard + flatGroupBoxes(dialog->asQWidget(), guiApp->platformName() != "cocoa"); + if (lyxrc.allow_geometry_session) + dialog->restoreSession(); + if (hide_it) + dialog->hideView(); + } return dialog; }