X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiApplication.cpp;h=35b639c9ac490e27e09ee25c123afce961eb9840;hb=c0a1893008bd13650d470afff16f56720b65c87c;hp=9c7c288faebdf4393be76eae3f51798345c7f9a7;hpb=d5fb80ed874057da0d0b31b836052b9fa35ba269;p=lyx.git diff --git a/src/frontends/qt4/GuiApplication.cpp b/src/frontends/qt4/GuiApplication.cpp index 9c7c288fae..35b639c9ac 100644 --- a/src/frontends/qt4/GuiApplication.cpp +++ b/src/frontends/qt4/GuiApplication.cpp @@ -14,6 +14,7 @@ #include "GuiApplication.h" +#include "ToolTipFormatter.h" #include "ColorCache.h" #include "ColorSet.h" #include "GuiClipboard.h" @@ -35,6 +36,7 @@ #include "BufferView.h" #include "CmdDef.h" #include "Color.h" +#include "Converter.h" #include "CutAndPaste.h" #include "ErrorList.h" #include "Font.h" @@ -63,7 +65,6 @@ #include "support/ExceptionMessage.h" #include "support/FileName.h" #include "support/filetools.h" -#include "support/foreach.h" #include "support/ForkedCalls.h" #include "support/gettext.h" #include "support/lassert.h" @@ -120,10 +121,14 @@ #ifdef Q_WS_X11 #include #include +#include #undef CursorShape #undef None #elif defined(QPA_XCB) #include +#ifdef HAVE_QT5_X11_EXTRAS +#include +#endif #endif #if (QT_VERSION < 0x050000) || (QT_VERSION >= 0x050400) @@ -146,7 +151,6 @@ #include #endif // Q_OS_MAC -#include "support/bind.h" #include #include @@ -184,6 +188,12 @@ frontend::Application * createApplication(int & argc, char * argv[]) } } #endif + +#if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN) + // On Windows, allow bringing the LyX window to top + AllowSetForegroundWindow(ASFW_ANY); +#endif + frontend::GuiApplication * guiApp = new frontend::GuiApplication(argc, argv); // I'd rather do that in the constructor, but I do not think that // the palette is accessible there. @@ -227,14 +237,35 @@ vector loadableImageFormats() if (qt_formats.empty()) LYXERR(Debug::GRAPHICS, "\nQt4 Problem: No Format available!"); + bool jpeg_found = false; + bool jpg_found = false; for (QList::const_iterator it = qt_formats.begin(); it != qt_formats.end(); ++it) { LYXERR(Debug::GRAPHICS, (const char *) *it << ", "); string ext = ascii_lowercase((const char *) *it); // special case - if (ext == "jpeg") + if (ext == "jpeg") { + jpeg_found = true; + if (jpg_found) + continue; ext = "jpg"; + } + else if (ext == "jpg") { + jpg_found = true; + if (jpeg_found) + continue; + } + else if (lyxrc.use_converter_cache && + (ext == "svg" || ext == "svgz") && + theConverters().isReachable("svg", "png")) + // Qt only supports SVG 1.2 tiny. See #9778. We prefer displaying + // the SVG as in the output. However we require that the converter + // cache is enabled since this is expensive. We also require that + // an explicit svg->png converter is defined, since the default + // converter could produce bad quality as well. This assumes that + // png can always be loaded. + continue; fmts.push_back(ext); } @@ -443,7 +474,7 @@ QString findImg(QString const & name) return img_name; } -} // namespace anon +} // namespace QString themeIconName(QString const & action) @@ -567,7 +598,7 @@ QPixmap getPixmap(QString const & path, QString const & name, QString const & ex if (getPixmap(pixmap, fpath)) { return pixmap; } - + QStringList exts = ext.split(","); fpath = ":/" + path + name + "."; for (int i = 0; i < exts.size(); ++i) { @@ -754,8 +785,8 @@ class QWindowsMimeMetafile : public QWINDOWSMIME { public: QWindowsMimeMetafile() {} - bool canConvertFromMime(FORMATETC const & formatetc, - QMimeData const * mimedata) const + bool canConvertFromMime(FORMATETC const & /*formatetc*/, + QMimeData const * /*mimedata*/) const { return false; } @@ -769,14 +800,14 @@ public: return pDataObj->QueryGetData(&formatetc) == S_OK; } - bool convertFromMime(FORMATETC const & formatetc, - const QMimeData * mimedata, STGMEDIUM * pmedium) const + bool convertFromMime(FORMATETC const & /*formatetc*/, + const QMimeData * /*mimedata*/, STGMEDIUM * /*pmedium*/) const { return false; } QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj, - QVariant::Type preferredType) const + QVariant::Type /*preferredType*/) const { QByteArray data; if (!canConvertToMime(mimetype, pDataObj)) @@ -807,7 +838,7 @@ public: QVector formatsForMime(QString const & mimetype, - QMimeData const * mimedata) const + QMimeData const * /*mimedata*/) const { QVector formats; if (mimetype == emfMimeType() || mimetype == wmfMimeType()) @@ -992,6 +1023,9 @@ GuiApplication::GuiApplication(int & argc, char ** argv) QCoreApplication::setOrganizationName(app_name); QCoreApplication::setOrganizationDomain("lyx.org"); QCoreApplication::setApplicationName(lyx_package); +#if QT_VERSION >= 0x050000 + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif qsrand(QDateTime::currentDateTime().toTime_t()); @@ -1061,6 +1095,9 @@ GuiApplication::GuiApplication(int & argc, char ** argv) // This is clearly not enough in a time where we use threads for // document preview and/or export. 20 should be OK. QThreadPool::globalInstance()->setMaxThreadCount(20); + + // make sure tooltips are formatted + installEventFilter(new ToolTipFormatter(this)); } @@ -1073,7 +1110,7 @@ GuiApplication * theGuiApp() double GuiApplication::pixelRatio() const { #if QT_VERSION >= 0x050000 - return devicePixelRatio(); + return qt_scale_factor * devicePixelRatio(); #else return 1.0; #endif @@ -1370,7 +1407,7 @@ DispatchResult const & GuiApplication::dispatch(FuncRequest const & cmd) updateCurrentView(cmd, dr); // the buffer may have been closed by one action - if (theBufferList().isLoaded(buffer)) + if (theBufferList().isLoaded(buffer) || theBufferList().isInternal(buffer)) buffer->undo().endUndoGroup(); d->dispatch_result_ = dr; @@ -1442,7 +1479,7 @@ void GuiApplication::gotoBookmark(unsigned int idx, bool openFile, // if the current buffer is not that one, switch to it. BufferView * doc_bv = current_view_ ? current_view_->documentBufferView() : 0; - Cursor const old = doc_bv->cursor(); + Cursor const * old = doc_bv ? &doc_bv->cursor() : 0; if (!doc_bv || doc_bv->buffer().fileName() != tmp.filename) { if (switchToBuffer) { dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file)); @@ -1454,13 +1491,13 @@ void GuiApplication::gotoBookmark(unsigned int idx, bool openFile, } // moveToPosition try paragraph id first and then paragraph (pit, pos). - if (!doc_bv->moveToPosition( + if (!doc_bv || !doc_bv->moveToPosition( tmp.bottom_pit, tmp.bottom_pos, tmp.top_id, tmp.top_pos)) return; Cursor & cur = doc_bv->cursor(); - if (cur != old) - notifyCursorLeavesOrEnters(old, cur); + if (old && cur != *old) + notifyCursorLeavesOrEnters(*old, cur); // bm changed if (idx == 0) @@ -1609,7 +1646,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_BUFFER_NEW: validateCurrentView(); - if (d->views_.empty() + if (!current_view_ || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) { createView(QString(), false); // keep hidden current_view_->newDocument(to_utf8(cmd.argument()), false); @@ -1622,7 +1659,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_BUFFER_NEW_TEMPLATE: validateCurrentView(); - if (d->views_.empty() + if (!current_view_ || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) { createView(); current_view_->newDocument(to_utf8(cmd.argument()), true); @@ -1634,12 +1671,14 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; case LFUN_FILE_OPEN: { + // FIXME: normally the code below is not needed, since getStatus makes sure that + // current_view_ is not null. validateCurrentView(); // FIXME: create a new method shared with LFUN_HELP_OPEN. string const fname = to_utf8(cmd.argument()); bool const is_open = FileName::isAbsolute(fname) && theBufferList().getBuffer(FileName(fname)); - if (d->views_.empty() + if (!current_view_ || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0 && !is_open)) { @@ -1649,10 +1688,21 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) crc = for_each(fname.begin(), fname.end(), crc); createView(crc.checksum()); current_view_->openDocument(fname); - if (current_view_ && !current_view_->documentBufferView()) + if (!current_view_->documentBufferView()) current_view_->close(); - } else + else if (cmd.origin() == FuncRequest::LYXSERVER) { + current_view_->raise(); + current_view_->activateWindow(); + current_view_->showNormal(); + } + } else { current_view_->openDocument(fname); + if (cmd.origin() == FuncRequest::LYXSERVER) { + current_view_->raise(); + current_view_->activateWindow(); + current_view_->showNormal(); + } + } break; } @@ -1677,13 +1727,8 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) current_view_->message(bformat(_("Opening help file %1$s..."), makeDisplayPath(fname.absFileName()))); Buffer * buf = current_view_->loadDocument(fname, false); - -#ifndef DEVEL_VERSION if (buf) - buf->setReadonly(true); -#else - (void) buf; -#endif + buf->setReadonly(!current_view_->develMode()); break; } @@ -1809,12 +1854,16 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) // repeat command string countstr; string rest = split(argument, countstr, ' '); - istringstream is(countstr); - int count = 0; - is >> count; - //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl; - for (int i = 0; i < count; ++i) - dispatch(lyxaction.lookupFunc(rest)); + int const count = convert(countstr); + // an arbitrary number to limit number of iterations + int const max_iter = 10000; + if (count > max_iter) { + dr.setMessage(bformat(_("Cannot iterate more than %1$d times"), max_iter)); + dr.setError(true); + } else { + for (int i = 0; i < count; ++i) + dispatch(lyxaction.lookupFunc(rest)); + } break; } @@ -1834,7 +1883,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) dispatch(func); } // the buffer may have been closed by one action - if (theBufferList().isLoaded(buffer)) + if (theBufferList().isLoaded(buffer) || theBufferList().isInternal(buffer)) buffer->undo().endUndoGroup(); break; } @@ -1847,11 +1896,12 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) QList allViews = d->views_.values(); - // this foreach does not modify any buffer. It just collects info on local visibility of buffers - // and on which buffer is active in each view. + // this for does not modify any buffer. It just collects info on local + // visibility of buffers and on which buffer is active in each view. Buffer * const last = theBufferList().last(); - foreach (GuiView * view, allViews) { - // all of the buffers might be locally hidden. That is, there is no active buffer. + for(GuiView * view : allViews) { + // all of the buffers might be locally hidden. That is, there is no + // active buffer. if (!view || !view->currentBufferView()) activeBuffers[view] = 0; else @@ -1898,7 +1948,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) } // put things back to how they were (if possible). - foreach (GuiView * view, allViews) { + for (GuiView * view : allViews) { Buffer * originalBuf = activeBuffers[view]; // there might not have been an active buffer in this view or it might have been closed by the LFUN. if (theBufferList().isLoaded(originalBuf)) @@ -2020,9 +2070,8 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr) if (current_view_ == 0) createView(); } - // fall through } - + // fall through default: // The LFUN must be for one of GuiView, BufferView, Buffer or Cursor; // let's try that: @@ -2071,19 +2120,43 @@ void GuiApplication::handleKeyFunc(FuncCode action) } +//Keep this in sync with GuiApplication::processKeySym below +bool GuiApplication::queryKeySym(KeySymbol const & keysym, + KeyModifier state) const +{ + // Do nothing if we have nothing + if (!keysym.isOK() || keysym.isModifier()) + return false; + // Do a one-deep top-level lookup for cancel and meta-fake keys. + KeySequence seq; + FuncRequest func = seq.addkey(keysym, state); + // When not cancel or meta-fake, do the normal lookup. + if ((func.action() != LFUN_CANCEL) && (func.action() != LFUN_META_PREFIX)) { + seq = d->keyseq; + func = seq.addkey(keysym, (state | d->meta_fake_bit)); + } + // Maybe user can only reach the key via holding down shift. + // Let's see. But only if shift is the only modifier + if (func.action() == LFUN_UNKNOWN_ACTION && state == ShiftModifier) + // If addkey looked up a command and did not find further commands then + // seq has been reset at this point + func = seq.addkey(keysym, NoModifier); + + LYXERR(Debug::KEY, " Key (queried) [action=" << func.action() << "][" + << seq.print(KeySequence::Portable) << ']'); + return func.action() != LFUN_UNKNOWN_ACTION; +} + + +//Keep this in sync with GuiApplication::queryKeySym above void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state) { LYXERR(Debug::KEY, "KeySym is " << keysym.getSymbolName()); // Do nothing if we have nothing (JMarc) - if (!keysym.isOK()) { - LYXERR(Debug::KEY, "Empty kbd action (probably composing)"); - if (current_view_) - current_view_->restartCursor(); - return; - } - - if (keysym.isModifier()) { + if (!keysym.isOK() || keysym.isModifier()) { + if (!keysym.isOK()) + LYXERR(Debug::KEY, "Empty kbd action (probably composing)"); if (current_view_) current_view_->restartCursor(); return; @@ -2129,6 +2202,8 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state) // Let's see. But only if shift is the only modifier if (func.action() == LFUN_UNKNOWN_ACTION && state == ShiftModifier) { LYXERR(Debug::KEY, "Trying without shift"); + // If addkey looked up a command and did not find further commands then + // seq has been reset at this point func = d->keyseq.addkey(keysym, NoModifier); LYXERR(Debug::KEY, "Action now " << func.action()); } @@ -2214,8 +2289,12 @@ void GuiApplication::processFuncRequestAsync(FuncRequest const & func) void GuiApplication::processFuncRequestQueue() { while (!d->func_request_queue_.empty()) { - processFuncRequest(d->func_request_queue_.front()); + // take the item from the stack _before_ processing the + // request in order to avoid race conditions from nested + // or parallel requests (see #10406) + FuncRequest const fr(d->func_request_queue_.front()); d->func_request_queue_.pop(); + processFuncRequest(fr); } } @@ -2278,6 +2357,8 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow, LYXERR(Debug::GUI, "About to create new window with ID " << id); GuiView * view = new GuiView(id); + // `view' is the new current_view_. Tell coverity that is is not 0. + LATTEST(current_view_); // register view d->views_[id] = view; @@ -2327,6 +2408,15 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow, } +bool GuiApplication::unhide(Buffer * buf) +{ + if (!currentView()) + return false; + currentView()->setBuffer(buf, false); + return true; +} + + Clipboard & GuiApplication::clipboard() { return d->clipboard_; @@ -2509,7 +2599,7 @@ void GuiApplication::restoreGuiSession() // not be added at all (help files). for (size_t i = 0; i < lastopened.size(); ++i) { FileName const & file_name = lastopened[i].file_name; - if (d->views_.empty() || (!lyxrc.open_buffers_in_tabs + if (!current_view_ || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) { boost::crc_32_type crc; string const & fname = file_name.absFileName(); @@ -2565,7 +2655,7 @@ namespace { const QFontInfo fi(font); return fi.fixedPitch(); } -} +} // namespace QFont const GuiApplication::typewriterSystemFont() @@ -2637,7 +2727,8 @@ bool GuiApplication::notify(QObject * receiver, QEvent * event) #endif // In release mode, try to exit gracefully. this->exit(1); - + // FIXME: GCC 7 thinks we can fall through here. Can we? + // fall through case BufferException: { if (!current_view_ || !current_view_->documentBufferView()) return false; @@ -2781,7 +2872,7 @@ bool GuiApplication::closeAllViews() theSession().lastOpened().clear(); QList const views = d->views_.values(); - foreach (GuiView * view, views) { + for (GuiView * view : views) { if (!view->closeScheduled()) return false; } @@ -2797,7 +2888,7 @@ bool GuiApplication::prepareAllViewsForLogout() return true; QList const views = d->views_.values(); - foreach (GuiView * view, views) { + for (GuiView * view : views) { if (!view->prepareAllBuffersForLogout()) return false; } @@ -2816,7 +2907,7 @@ GuiView & GuiApplication::view(int id) const void GuiApplication::hideDialogs(string const & name, Inset * inset) const { QList const views = d->views_.values(); - foreach (GuiView * view, views) + for (GuiView * view : views) view->hideDialog(name, inset); } @@ -2904,7 +2995,7 @@ GuiApplication::ReturnValues GuiApplication::readUIFile(FileName ui_path) QString const file = toqstr(lex.getString()); bool const success = readUIFile(file, true); if (!success) { - LYXERR0("Failed to read inlcuded file: " << fromqstr(file)); + LYXERR0("Failed to read included file: " << fromqstr(file)); return ReadError; } break; @@ -3086,8 +3177,26 @@ bool GuiApplication::x11EventFilter(XEvent * xev) BufferView * bv = current_view_->currentBufferView(); if (bv) { docstring const sel = bv->requestSelection(); - if (!sel.empty()) + if (!sel.empty()) { d->selection_.put(sel); + // Refresh the selection request timestamp. + // We have to do this by ourselves as Qt seems + // not doing that, maybe because of our + // "persistent selection" implementation + // (see comments in GuiSelection.cpp). + XSelectionEvent nev; + nev.type = SelectionNotify; + nev.display = xev->xselectionrequest.display; + nev.requestor = xev->xselectionrequest.requestor; + nev.selection = xev->xselectionrequest.selection; + nev.target = xev->xselectionrequest.target; + nev.property = 0L; // None + nev.time = CurrentTime; + XSendEvent(QX11Info::display(), + nev.requestor, False, 0, + reinterpret_cast(&nev)); + return true; + } } break; } @@ -3122,8 +3231,29 @@ bool GuiApplication::nativeEventFilter(const QByteArray & eventType, BufferView * bv = current_view_->currentBufferView(); if (bv) { docstring const sel = bv->requestSelection(); - if (!sel.empty()) + if (!sel.empty()) { d->selection_.put(sel); +#ifdef HAVE_QT5_X11_EXTRAS + // Refresh the selection request timestamp. + // We have to do this by ourselves as Qt seems + // not doing that, maybe because of our + // "persistent selection" implementation + // (see comments in GuiSelection.cpp). + xcb_selection_notify_event_t nev; + nev.response_type = XCB_SELECTION_NOTIFY; + nev.requestor = srev->requestor; + nev.selection = srev->selection; + nev.target = srev->target; + nev.property = XCB_NONE; + nev.time = XCB_CURRENT_TIME; + xcb_connection_t * con = QX11Info::connection(); + xcb_send_event(con, 0, srev->requestor, + XCB_EVENT_MASK_NO_EVENT, + reinterpret_cast(&nev)); + xcb_flush(con); +#endif + return true; + } } break; }