diff options
Diffstat (limited to 'src/gui')
30 files changed, 378 insertions, 114 deletions
diff --git a/src/gui/doc/src/richtext.qdoc b/src/gui/doc/src/richtext.qdoc index 90248298d6..460018a52e 100644 --- a/src/gui/doc/src/richtext.qdoc +++ b/src/gui/doc/src/richtext.qdoc @@ -27,6 +27,7 @@ /*! \group richtext-processing + \brief How to use Rich Text Processing APIs. \title Rich Text Processing APIs */ diff --git a/src/gui/image/qicon.cpp b/src/gui/image/qicon.cpp index f1a384af3a..d885729cbd 100644 --- a/src/gui/image/qicon.cpp +++ b/src/gui/image/qicon.cpp @@ -1173,8 +1173,8 @@ QIcon QIcon::fromTheme(const QString &name, const QIcon &fallback) QIconEngine * const engine = platformTheme ? platformTheme->createIconEngine(name) : new QIconLoaderEngine(name); QIcon *cachedIcon = new QIcon(engine); - qtIconCache()->insert(name, cachedIcon); icon = *cachedIcon; + qtIconCache()->insert(name, cachedIcon); } // Note the qapp check is to allow lazy loading of static icons diff --git a/src/gui/image/qimage_conversions.cpp b/src/gui/image/qimage_conversions.cpp index 6009918521..2e8fc1963d 100644 --- a/src/gui/image/qimage_conversions.cpp +++ b/src/gui/image/qimage_conversions.cpp @@ -2945,7 +2945,7 @@ void qInitImageConversions() } #endif -#ifdef __ARM_NEON__ +#if defined(__ARM_NEON__) && !defined(Q_PROCESSOR_ARM_64) extern void convert_RGB888_to_RGB32_neon(QImageData *dest, const QImageData *src, Qt::ImageConversionFlags); qimage_converter_map[QImage::Format_RGB888][QImage::Format_RGB32] = convert_RGB888_to_RGB32_neon; qimage_converter_map[QImage::Format_RGB888][QImage::Format_ARGB32] = convert_RGB888_to_RGB32_neon; diff --git a/src/gui/image/qimage_neon.cpp b/src/gui/image/qimage_neon.cpp index e3930bbb4d..88d1c87ee7 100644 --- a/src/gui/image/qimage_neon.cpp +++ b/src/gui/image/qimage_neon.cpp @@ -35,7 +35,7 @@ #include <private/qimage_p.h> #include <private/qsimd_p.h> -#ifdef __ARM_NEON__ +#if defined(__ARM_NEON__) && !defined(Q_PROCESSOR_ARM_64) QT_BEGIN_NAMESPACE @@ -103,4 +103,4 @@ void convert_RGB888_to_RGB32_neon(QImageData *dest, const QImageData *src, Qt::I QT_END_NAMESPACE -#endif // __ARM_NEON__ +#endif // defined(__ARM_NEON__) && !defined(Q_PROCESSOR_ARM_64) diff --git a/src/gui/image/qjpeghandler.cpp b/src/gui/image/qjpeghandler.cpp index fbcc0608e2..ae30de634a 100644 --- a/src/gui/image/qjpeghandler.cpp +++ b/src/gui/image/qjpeghandler.cpp @@ -729,7 +729,7 @@ public: }; QJpegHandlerPrivate(QJpegHandler *qq) - : quality(75), iod_src(0), state(Ready), q(qq) + : quality(75), exifOrientation(1), iod_src(0), state(Ready), q(qq) {} ~QJpegHandlerPrivate() @@ -744,8 +744,10 @@ public: bool readJpegHeader(QIODevice*); bool read(QImage *image); + void applyExifOrientation(QImage *image); int quality; + int exifOrientation; QVariant size; QImage::Format format; QSize scaledSize; @@ -763,6 +765,97 @@ public: QJpegHandler *q; }; +static bool readExifHeader(QDataStream &stream) +{ + char prefix[6]; + if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix)) + return false; + if (prefix[0] != 'E' || prefix[1] != 'x' || prefix[2] != 'i' || prefix[3] != 'f' || prefix[4] != 0 || prefix[5] != 0) + return false; + return true; +} + +/* + * Returns -1 on error + * Returns 0 if no Exif orientation was found + * Returns 1 orientation is horizontal (normal) + * Returns 2 mirror horizontal + * Returns 3 rotate 180 + * Returns 4 mirror vertical + * Returns 5 mirror horizontal and rotate 270 CCW + * Returns 6 rotate 90 CW + * Returns 7 mirror horizontal and rotate 90 CW + * Returns 8 rotate 270 CW + */ +static int getExifOrientation(QByteArray &exifData) +{ + QDataStream stream(&exifData, QIODevice::ReadOnly); + + if (!readExifHeader(stream)) + return -1; + + quint16 val; + quint32 offset; + + // read byte order marker + stream >> val; + if (val == 0x4949) // 'II' == Intel + stream.setByteOrder(QDataStream::LittleEndian); + else if (val == 0x4d4d) // 'MM' == Motorola + stream.setByteOrder(QDataStream::BigEndian); + else + return -1; // unknown byte order + + // read size + stream >> val; + if (val != 0x2a) + return -1; + + stream >> offset; + // we have already used 8 bytes of TIFF header + offset -= 8; + + // read IFD + while (!stream.atEnd()) { + quint16 numEntries; + + // skip offset bytes to get the next IFD + if (stream.skipRawData(offset) != (qint32)offset) + return -1; + + stream >> numEntries; + + for (;numEntries > 0; --numEntries) { + quint16 tag; + quint16 type; + quint32 components; + quint32 value; + + stream >> tag >> type >> components >> value; + + if (tag == 0x0112) { // Tag Exif.Image.Orientation + if (components !=1) + return -1; + if (type != 3) // we are expecting it to be an unsigned short + return -1; + if (value < 1 || value > 8) // check for valid range + return -1; + + // It is possible to include the orientation multiple times. + // Right now the first value is returned. + return value; + } + } + + // read offset to next IFD + stream >> offset; + if (offset == 0) // this is the last IFD + break; + } + + // No Exif orientation was found + return 0; +} /*! \internal */ @@ -782,6 +875,7 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) if (!setjmp(err.setjmp_buffer)) { jpeg_save_markers(&info, JPEG_COM, 0xFFFF); + jpeg_save_markers(&info, JPEG_APP0+1, 0xFFFF); // Exif uses APP1 marker (void) jpeg_read_header(&info, TRUE); @@ -793,6 +887,8 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) format = QImage::Format_Invalid; read_jpeg_format(format, &info); + QByteArray exifData; + for (jpeg_saved_marker_ptr marker = info.marker_list; marker != NULL; marker = marker->next) { if (marker->marker == JPEG_COM) { QString key, value; @@ -810,9 +906,18 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) description += key + QLatin1String(": ") + value.simplified(); readTexts.append(key); readTexts.append(value); + } else if (marker->marker == JPEG_APP0+1) { + exifData.append((const char*)marker->data, marker->data_length); } } + if (exifData.size()) { + // Exif data present + int orientation = getExifOrientation(exifData); + if (orientation > 0) + exifOrientation = orientation; + } + state = ReadHeader; return true; } @@ -826,6 +931,48 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device) return true; } +void QJpegHandlerPrivate::applyExifOrientation(QImage *image) +{ + // This is not an optimized implementation, but easiest to maintain + QTransform transform; + + switch (exifOrientation) { + case 1: // normal + break; + case 2: // mirror horizontal + *image = image->mirrored(true, false); + break; + case 3: // rotate 180 + transform.rotate(180); + *image = image->transformed(transform); + break; + case 4: // mirror vertical + *image = image->mirrored(false, true); + break; + case 5: // mirror horizontal and rotate 270 CCW + *image = image->mirrored(true, false); + transform.rotate(270); + *image = image->transformed(transform); + break; + case 6: // rotate 90 CW + transform.rotate(90); + *image = image->transformed(transform); + break; + case 7: // mirror horizontal and rotate 90 CW + *image = image->mirrored(true, false); + transform.rotate(90); + *image = image->transformed(transform); + break; + case 8: // rotate 270 CW + transform.rotate(-90); + *image = image->transformed(transform); + break; + default: + qWarning("This should never happen"); + } + exifOrientation = 1; +} + bool QJpegHandlerPrivate::read(QImage *image) { if(state == Ready) @@ -837,6 +984,7 @@ bool QJpegHandlerPrivate::read(QImage *image) if (success) { for (int i = 0; i < readTexts.size()-1; i+=2) image->setText(readTexts.at(i), readTexts.at(i+1)); + applyExifOrientation(image); state = Ready; return true; @@ -856,12 +1004,13 @@ extern "C" void qt_convert_rgb888_to_rgb32_mips_dspr2_asm(quint32 *dst, const uc QJpegHandler::QJpegHandler() : d(new QJpegHandlerPrivate(this)) { -#if defined(__ARM_NEON__) +#if defined(__ARM_NEON__) && !defined(Q_PROCESSOR_ARM_64) // from qimage_neon.cpp if (qCpuHasFeature(NEON)) rgb888ToRgb32ConverterPtr = qt_convert_rgb888_to_rgb32_neon; -#endif // __ARM_NEON__ +#endif + #if defined(QT_COMPILER_SUPPORTS_SSSE3) // from qimage_ssse3.cpp diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index c5e7fb523d..e421f79e91 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -163,7 +163,6 @@ QWindow *QGuiApplicationPrivate::focus_window = 0; static QBasicMutex applicationFontMutex; QFont *QGuiApplicationPrivate::app_font = 0; bool QGuiApplicationPrivate::obey_desktop_settings = true; -bool QGuiApplicationPrivate::noGrab = false; static qreal fontSmoothingGamma = 1.7; @@ -1191,20 +1190,10 @@ void QGuiApplicationPrivate::eventDispatcherReady() platform_integration->initialize(); } -#if defined(QT_DEBUG) && defined(Q_OS_LINUX) -// Find out if our parent process is gdb by looking at the 'exe' symlink under /proc. -static bool runningUnderDebugger() -{ - const QFileInfo parentProcExe(QStringLiteral("/proc/") + QString::number(getppid()) + QStringLiteral("/exe")); - return parentProcExe.isSymLink() && parentProcExe.symLinkTarget().endsWith(QLatin1String("/gdb")); -} -#endif - void QGuiApplicationPrivate::init() { QCoreApplicationPrivate::is_app_running = false; // Starting up. - bool doGrabUnderDebugger = false; bool loadTestability = false; QList<QByteArray> pluginList; // Get command line params @@ -1239,10 +1228,6 @@ void QGuiApplicationPrivate::init() QDir::setCurrent(qbundlePath.section(QLatin1Char('/'), 0, -2)); } #endif - } else if (arg == "-nograb") { - QGuiApplicationPrivate::noGrab = true; - } else if (arg == "-dograb") { - doGrabUnderDebugger = true; #ifndef QT_NO_SESSIONMANAGER } else if (arg == "-session" && i < argc-1) { ++i; @@ -1268,16 +1253,6 @@ void QGuiApplicationPrivate::init() argc = j; } -#if defined(QT_DEBUG) && defined(Q_OS_LINUX) - if (!doGrabUnderDebugger && !QGuiApplicationPrivate::noGrab && runningUnderDebugger()) { - QGuiApplicationPrivate::noGrab = true; - qDebug("Qt: gdb: -nograb added to command-line options.\n" - "\t Use the -dograb option to enforce grabbing."); - } -#else - Q_UNUSED(doGrabUnderDebugger) -#endif - // Load environment exported generic plugins foreach (const QByteArray &plugin, qgetenv("QT_QPA_GENERIC_PLUGINS").split(',')) pluginList << plugin; diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h index 8988bd461d..eed3d5c10e 100644 --- a/src/gui/kernel/qguiapplication_p.h +++ b/src/gui/kernel/qguiapplication_p.h @@ -221,7 +221,6 @@ public: QStyleHints *styleHints; static bool obey_desktop_settings; - static bool noGrab; QInputMethod *inputMethod; QString firstWindowTitle; diff --git a/src/gui/kernel/qopenglcontext_p.h b/src/gui/kernel/qopenglcontext_p.h index d5a3126176..975553e7cd 100644 --- a/src/gui/kernel/qopenglcontext_p.h +++ b/src/gui/kernel/qopenglcontext_p.h @@ -203,6 +203,7 @@ public: , workaround_brokenTexSubImage(false) , workaround_missingPrecisionQualifiers(false) , active_engine(0) + , qgl_current_fbo_invalid(false) { requestedFormat = QSurfaceFormat::defaultFormat(); } @@ -237,6 +238,8 @@ public: QPaintEngineEx *active_engine; + bool qgl_current_fbo_invalid; + QVariant nativeHandle; static QOpenGLContext *setCurrentContext(QOpenGLContext *context); diff --git a/src/gui/kernel/qshapedpixmapdndwindow.cpp b/src/gui/kernel/qshapedpixmapdndwindow.cpp index 55a8aae33c..af60b36647 100644 --- a/src/gui/kernel/qshapedpixmapdndwindow.cpp +++ b/src/gui/kernel/qshapedpixmapdndwindow.cpp @@ -52,6 +52,12 @@ QShapedPixmapWindow::QShapedPixmapWindow() m_backingStore = new QBackingStore(this); } +QShapedPixmapWindow::~QShapedPixmapWindow() +{ + delete m_backingStore; + m_backingStore = 0; +} + void QShapedPixmapWindow::render() { QRect rect(QPoint(), geometry().size()); diff --git a/src/gui/kernel/qshapedpixmapdndwindow_p.h b/src/gui/kernel/qshapedpixmapdndwindow_p.h index b59305f055..04198c83cb 100644 --- a/src/gui/kernel/qshapedpixmapdndwindow_p.h +++ b/src/gui/kernel/qshapedpixmapdndwindow_p.h @@ -56,6 +56,7 @@ class QShapedPixmapWindow : public QWindow Q_OBJECT public: QShapedPixmapWindow(); + ~QShapedPixmapWindow(); void render(); diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp index a3b7f38c80..fa99390af0 100644 --- a/src/gui/kernel/qwindow.cpp +++ b/src/gui/kernel/qwindow.cpp @@ -1127,6 +1127,10 @@ Qt::WindowState QWindow::windowState() const This is a hint to the window manager that this window is a dialog or pop-up on behalf of the given window. + In order to cause the window to be centered above its transient parent by + default, depending on the window manager, it may also be necessary to call + setFlags() with a suitable \l Qt::WindowType (such as \c Qt::Dialog). + \sa transientParent(), parent() */ void QWindow::setTransientParent(QWindow *parent) @@ -1644,8 +1648,6 @@ QPlatformSurface *QWindow::surfaceHandle() const bool QWindow::setKeyboardGrabEnabled(bool grab) { Q_D(QWindow); - if (grab && QGuiApplicationPrivate::noGrab) - return false; if (d->platformWindow) return d->platformWindow->setKeyboardGrabEnabled(grab); return false; @@ -1663,8 +1665,6 @@ bool QWindow::setKeyboardGrabEnabled(bool grab) bool QWindow::setMouseGrabEnabled(bool grab) { Q_D(QWindow); - if (grab && QGuiApplicationPrivate::noGrab) - return false; if (d->platformWindow) return d->platformWindow->setMouseGrabEnabled(grab); return false; @@ -2352,7 +2352,7 @@ QWindow *QWindow::fromWinId(WId id) /*! Causes an alert to be shown for \a msec miliseconds. If \a msec is \c 0 (the default), then the alert is shown indefinitely until the window becomes - active again. + active again. This function has no effect on an active window. In alert state, the window indicates that it demands attention, for example by flashing or bouncing the taskbar entry. @@ -2363,7 +2363,7 @@ QWindow *QWindow::fromWinId(WId id) void QWindow::alert(int msec) { Q_D(QWindow); - if (!d->platformWindow || d->platformWindow->isAlertState()) + if (!d->platformWindow || d->platformWindow->isAlertState() || isActive()) return; d->platformWindow->setAlertState(true); if (d->platformWindow->isAlertState() && msec) diff --git a/src/gui/kernel/qwindow.h b/src/gui/kernel/qwindow.h index 6d9793ae3f..2230ed8801 100644 --- a/src/gui/kernel/qwindow.h +++ b/src/gui/kernel/qwindow.h @@ -358,6 +358,19 @@ private: friend Q_GUI_EXPORT QWindowPrivate *qt_window_private(QWindow *window); }; +#ifndef Q_QDOC +template <> inline QWindow *qobject_cast<QWindow*>(QObject *o) +{ + if (!o || !o->isWindowType()) return 0; + return static_cast<QWindow*>(o); +} +template <> inline const QWindow *qobject_cast<const QWindow*>(const QObject *o) +{ + if (!o || !o->isWindowType()) return 0; + return static_cast<const QWindow*>(o); +} +#endif // !Q_QDOC + QT_END_NAMESPACE #endif // QWINDOW_H diff --git a/src/gui/opengl/qopenglframebufferobject.cpp b/src/gui/opengl/qopenglframebufferobject.cpp index b185e332e6..124d9d53f6 100644 --- a/src/gui/opengl/qopenglframebufferobject.cpp +++ b/src/gui/opengl/qopenglframebufferobject.cpp @@ -469,6 +469,8 @@ void QOpenGLFramebufferObjectPrivate::init(QOpenGLFramebufferObject *, const QSi funcs.glGenFramebuffers(1, &fbo); funcs.glBindFramebuffer(GL_FRAMEBUFFER, fbo); + QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true; + GLuint color_buffer = 0; QT_CHECK_GLERROR(); @@ -997,7 +999,11 @@ bool QOpenGLFramebufferObject::bind() if (current->shareGroup() != d->fbo_guard->group()) qWarning("QOpenGLFramebufferObject::bind() called from incompatible context"); #endif + d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo()); + + QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true; + if (d->texture_guard || d->format.samples() != 0) d->valid = d->checkFramebufferStatus(current); else @@ -1029,9 +1035,12 @@ bool QOpenGLFramebufferObject::release() qWarning("QOpenGLFramebufferObject::release() called from incompatible context"); #endif - if (current) + if (current) { d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, current->defaultFramebufferObject()); + QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true; + } + return true; } @@ -1194,9 +1203,23 @@ Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, If used together with QOpenGLPaintDevice, \a flipped should be the opposite of the value of QOpenGLPaintDevice::paintFlipped(). - Will try to return a premultiplied ARBG32 or RGB32 image. Since 5.2 it will fall back to - a premultiplied RGBA8888 or RGBx8888 image when reading to ARGB32 is not supported. Since 5.4 an - A2BGR30 image is returned if the internal format is RGB10_A2. + The returned image has a format of premultiplied ARGB32 or RGB32. The latter is used + only when internalTextureFormat() is set to \c GL_RGB. + + If the rendering in the framebuffer was not done with premultiplied alpha in mind, + create a wrapper QImage with a non-premultiplied format. This is necessary before + performing operations like QImage::save() because otherwise the image data would get + unpremultiplied, even though it was not premultiplied in the first place. To create + such a wrapper without performing a copy of the pixel data, do the following: + + \code + QImage fboImage(fbo.toImage()); + QImage image(fboImage.constBits(), fboImage.width(), fboImage.height(), QImage::Format_ARGB32); + \endcode + + Since Qt 5.2 the function will fall back to premultiplied RGBA8888 or RGBx8888 when + reading to (A)RGB32 is not supported. Since 5.4 an A2BGR30 image is returned if the + internal format is RGB10_A2. For multisampled framebuffer objects the samples are resolved using the \c{GL_EXT_framebuffer_blit} extension. If the extension is not available, the contents @@ -1272,8 +1295,10 @@ bool QOpenGLFramebufferObject::bindDefault() { QOpenGLContext *ctx = const_cast<QOpenGLContext *>(QOpenGLContext::currentContext()); - if (ctx) + if (ctx) { ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject()); + QOpenGLContextPrivate::get(ctx)->qgl_current_fbo_invalid = true; + } #ifdef QT_DEBUG else qWarning("QOpenGLFramebufferObject::bindDefault() called without current context."); @@ -1342,6 +1367,7 @@ void QOpenGLFramebufferObject::setAttachment(QOpenGLFramebufferObject::Attachmen qWarning("QOpenGLFramebufferObject::setAttachment() called from incompatible context"); #endif d->funcs.glBindFramebuffer(GL_FRAMEBUFFER, d->fbo()); + QOpenGLContextPrivate::get(current)->qgl_current_fbo_invalid = true; d->initAttachments(current, attachment); } diff --git a/src/gui/opengl/qopenglpaintdevice.cpp b/src/gui/opengl/qopenglpaintdevice.cpp index e908fd8e91..a08d26f708 100644 --- a/src/gui/opengl/qopenglpaintdevice.cpp +++ b/src/gui/opengl/qopenglpaintdevice.cpp @@ -138,8 +138,8 @@ QOpenGLPaintDevice::QOpenGLPaintDevice(int width, int height) /*! \internal */ -QOpenGLPaintDevice::QOpenGLPaintDevice(QOpenGLPaintDevicePrivate *dd) - : d_ptr(dd) +QOpenGLPaintDevice::QOpenGLPaintDevice(QOpenGLPaintDevicePrivate &dd) + : d_ptr(&dd) { } diff --git a/src/gui/opengl/qopenglpaintdevice.h b/src/gui/opengl/qopenglpaintdevice.h index dda3bfe43f..10cee842ab 100644 --- a/src/gui/opengl/qopenglpaintdevice.h +++ b/src/gui/opengl/qopenglpaintdevice.h @@ -53,7 +53,6 @@ public: QOpenGLPaintDevice(); explicit QOpenGLPaintDevice(const QSize &size); QOpenGLPaintDevice(int width, int height); - QOpenGLPaintDevice(QOpenGLPaintDevicePrivate *dd); virtual ~QOpenGLPaintDevice(); int devType() const { return QInternal::OpenGL; } @@ -76,6 +75,7 @@ public: virtual void ensureActiveTarget(); protected: + QOpenGLPaintDevice(QOpenGLPaintDevicePrivate &dd); int metric(QPaintDevice::PaintDeviceMetric metric) const; Q_DISABLE_COPY(QOpenGLPaintDevice) diff --git a/src/gui/opengl/qopengltextureglyphcache.cpp b/src/gui/opengl/qopengltextureglyphcache.cpp index 0f70a01014..cd268cd685 100644 --- a/src/gui/opengl/qopengltextureglyphcache.cpp +++ b/src/gui/opengl/qopengltextureglyphcache.cpp @@ -82,10 +82,12 @@ QOpenGLTextureGlyphCache::~QOpenGLTextureGlyphCache() clear(); } +#if !defined(QT_OPENGL_ES_2) static inline bool isCoreProfile() { return QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile; } +#endif void QOpenGLTextureGlyphCache::createTextureData(int width, int height) { diff --git a/src/gui/painting/qbrush.cpp b/src/gui/painting/qbrush.cpp index d120175108..d136f3a903 100644 --- a/src/gui/painting/qbrush.cpp +++ b/src/gui/painting/qbrush.cpp @@ -708,6 +708,9 @@ void QBrush::setStyle(Qt::BrushStyle style) void QBrush::setColor(const QColor &c) { + if (d->color == c) + return; + detach(d->style); d->color = c; } diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp index 4c6dc958b2..a991b89f48 100644 --- a/src/gui/painting/qdrawhelper.cpp +++ b/src/gui/painting/qdrawhelper.cpp @@ -6928,7 +6928,7 @@ void qInitDrawhelperAsm() qt_fetch_radial_gradient = qt_fetch_radial_gradient_neon; #endif -#ifdef Q_PROCESSOR_MIPS_32 +#if defined(Q_PROCESSOR_MIPS_32) && defined(QT_COMPILER_SUPPORTS_MIPS_DSP) qt_memfill32 = qt_memfill32_asm_mips_dsp; #endif // Q_PROCESSOR_MIPS_32 diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index 30421ea2fa..8cf45863b8 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -2506,7 +2506,8 @@ void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) QFontEngine::FaceId face_id = fe->faceId(); bool noEmbed = false; - if (face_id.filename.isEmpty() + if (!embedFonts + || face_id.filename.isEmpty() || fe->fsType & 0x200 /* bitmap embedding only */ || fe->fsType == 2 /* no embedding allowed */) { *currentPage << "Q\n"; @@ -2529,10 +2530,6 @@ void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti) qreal size = ti.fontEngine->fontDef.pixelSize; -#if defined(Q_OS_WIN) - size = (ti.fontEngine->ascent() + ti.fontEngine->descent()).toReal(); -#endif - QVarLengthArray<glyph_t> glyphs; QVarLengthArray<QFixedPoint> positions; QTransform m = QTransform::fromTranslate(p.x(), p.y()); diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp index decf2fe121..8fe6aff79f 100644 --- a/src/gui/text/qfontdatabase.cpp +++ b/src/gui/text/qfontdatabase.cpp @@ -620,7 +620,7 @@ struct QtFontDesc static int match(int script, const QFontDef &request, const QString &family_name, const QString &foundry_name, int force_encoding_id, - QtFontDesc *desc, const QList<int> &blacklisted, bool fallback); + QtFontDesc *desc, const QList<int> &blacklisted); static void initFontDef(const QtFontDesc &desc, const QFontDef &request, QFontDef *fontDef, bool multi) { @@ -1110,7 +1110,7 @@ static bool matchFamilyName(const QString &familyName, QtFontFamily *f) */ static int match(int script, const QFontDef &request, const QString &family_name, const QString &foundry_name, int force_encoding_id, - QtFontDesc *desc, const QList<int> &blacklistedFamilies, bool fallback = false) + QtFontDesc *desc, const QList<int> &blacklistedFamilies) { Q_UNUSED(force_encoding_id); int result = -1; @@ -1163,7 +1163,7 @@ static int match(int script, const QFontDef &request, load(test.family->name, script); // Check if family is supported in the script we want - if (!fallback && script != QChar::Script_Common && !(test.family->writingSystems[writingSystem] & QtFontFamily::Supported)) + if (script != QChar::Script_Common && !(test.family->writingSystems[writingSystem] & QtFontFamily::Supported)) continue; // as we know the script is supported, we can be sure @@ -2490,7 +2490,7 @@ bool QFontDatabase::supportsThreadedFontRendering() */ QFontEngine * QFontDatabase::findFont(int script, const QFontPrivate *fp, - const QFontDef &request, bool multi, bool fallback) + const QFontDef &request, bool multi) { QMutexLocker locker(fontDatabaseMutex()); @@ -2518,7 +2518,7 @@ QFontDatabase::findFont(int script, const QFontPrivate *fp, QtFontDesc desc; QList<int> blackListed; - int index = match(script, request, family_name, foundry_name, force_encoding_id, &desc, blackListed, fallback); + int index = match(script, request, family_name, foundry_name, force_encoding_id, &desc, blackListed); if (index >= 0) { engine = loadEngine(script, request, desc.family, desc.foundry, desc.style, desc.size); if (!engine) diff --git a/src/gui/text/qfontdatabase.h b/src/gui/text/qfontdatabase.h index f1c479d0bb..d7d8745f12 100644 --- a/src/gui/text/qfontdatabase.h +++ b/src/gui/text/qfontdatabase.h @@ -152,7 +152,7 @@ private: static void createDatabase(); static void parseFontName(const QString &name, QString &foundry, QString &family); static QString resolveFontFamilyAlias(const QString &family); - static QFontEngine *findFont(int script, const QFontPrivate *fp, const QFontDef &request, bool multi = false, bool fallback = false); + static QFontEngine *findFont(int script, const QFontPrivate *fp, const QFontDef &request, bool multi = false); static void load(const QFontPrivate *d, int script); friend struct QFontDef; diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index fe0f9fa943..4c6fc7c1a6 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -373,8 +373,6 @@ bool QFontEngine::supportsScript(QChar::Script script) const if (!ret && script_tag_2 != HB_OT_TAG_DEFAULT_SCRIPT) ret = hb_ot_layout_table_find_script(face, HB_OT_TAG_GSUB, HB_OT_TAG_DEFAULT_SCRIPT, &script_index); } - - hb_face_destroy(face); } return ret; } @@ -2028,7 +2026,7 @@ void QFontEngineMultiBasicImpl::loadEngine(int at) request.family = fallbackFamilies.at(at-1); engines[at] = QFontDatabase::findFont(script, /*fontprivate = */0, - request, /*multi = */false, true); + request, /*multi = */false); Q_ASSERT(engines[at]); engines[at]->ref.ref(); engines[at]->fontDef = request; @@ -2098,4 +2096,8 @@ QFontEngine* QFontEngineMultiBasicImpl::createMultiFontEngine(QFontEngine *fe, i return engine; } +QTestFontEngine::QTestFontEngine(int size) + : QFontEngineBox(TestFontEngine, size) +{} + QT_END_NAMESPACE diff --git a/src/gui/text/qfontengine_ft.cpp b/src/gui/text/qfontengine_ft.cpp index b44eb43be5..4c5bab77d6 100644 --- a/src/gui/text/qfontengine_ft.cpp +++ b/src/gui/text/qfontengine_ft.cpp @@ -1459,10 +1459,7 @@ void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_me bool QFontEngineFT::supportsTransformation(const QTransform &transform) const { - // The freetype engine falls back to QFontEngine for tranformed glyphs, - // which uses fast-tranform and produces very ugly results, so we claim - // to support just translations. - return transform.type() <= QTransform::TxTranslate; + return transform.type() <= QTransform::TxRotate; } void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) @@ -1943,17 +1940,75 @@ void QFontEngineFT::unlockAlphaMapForGlyph() currentlyLockedAlphaMap = QImage(); } -QFontEngineFT::Glyph *QFontEngineFT::loadGlyphFor(glyph_t g, QFixed subPixelPosition, GlyphFormat format) +QFontEngineFT::Glyph *QFontEngineFT::loadGlyphFor(glyph_t g, + QFixed subPixelPosition, + GlyphFormat format, + const QTransform &t) { - return defaultGlyphSet.outline_drawing ? 0 : - loadGlyph(cacheEnabled ? &defaultGlyphSet : 0, g, subPixelPosition, format); + FT_Face face = 0; + QGlyphSet *glyphSet = 0; + FT_Matrix ftMatrix = QTransformToFTMatrix(t); + if (cacheEnabled) { + if (t.type() > QTransform::TxTranslate && FT_IS_SCALABLE(freetype->face)) { + for (int i = 0; i < transformedGlyphSets.count(); ++i) { + const QGlyphSet &g = transformedGlyphSets.at(i); + if (g.transformationMatrix.xx == ftMatrix.xx + && g.transformationMatrix.xy == ftMatrix.xy + && g.transformationMatrix.yx == ftMatrix.yx + && g.transformationMatrix.yy == ftMatrix.yy) { + + // found a match, move it to the front + transformedGlyphSets.move(i, 0); + glyphSet = &transformedGlyphSets[0]; + break; + } + } + + if (!glyphSet) { + // don't cache more than 10 transformations + if (transformedGlyphSets.count() >= 10) { + transformedGlyphSets.move(transformedGlyphSets.size() - 1, 0); + } else { + transformedGlyphSets.prepend(QGlyphSet()); + } + glyphSet = &transformedGlyphSets[0]; + glyphSet->clear(); + glyphSet->transformationMatrix = ftMatrix; + } + } else { + glyphSet = &defaultGlyphSet; + } + Q_ASSERT(glyphSet != 0); + } + + if (glyphSet != 0 && glyphSet->outline_drawing) + return 0; + + Glyph *glyph = glyphSet != 0 ? glyphSet->getGlyph(g, subPixelPosition) : 0; + if (!glyph || glyph->format != format) { + face = lockFace(); + FT_Matrix m = this->matrix; + FT_Matrix_Multiply(&ftMatrix, &m); + freetype->matrix = m; + glyph = loadGlyph(glyphSet, g, subPixelPosition, format, false); + } + + if (face) + unlockFace(); + + return glyph; } QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition) { + return alphaMapForGlyph(g, subPixelPosition, QTransform()); +} + +QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition, const QTransform &t) +{ lockFace(); - QScopedPointer<Glyph> glyph(loadGlyphFor(g, subPixelPosition, antialias ? Format_A8 : Format_Mono)); + QScopedPointer<Glyph> glyph(loadGlyphFor(g, subPixelPosition, antialias ? Format_A8 : Format_Mono, t)); if (!glyph || !glyph->data) { unlockFace(); return QFontEngine::alphaMapForGlyph(g); @@ -1982,12 +2037,12 @@ QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition) QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g, QFixed subPixelPosition, const QTransform &t) { - if (t.type() > QTransform::TxTranslate) + if (t.type() > QTransform::TxRotate) return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, t); lockFace(); - QScopedPointer<Glyph> glyph(loadGlyphFor(g, subPixelPosition, Format_A32)); + QScopedPointer<Glyph> glyph(loadGlyphFor(g, subPixelPosition, Format_A32, t)); if (!glyph || !glyph->data) { unlockFace(); return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, t); diff --git a/src/gui/text/qfontengine_ft_p.h b/src/gui/text/qfontengine_ft_p.h index a73c281f1d..1894d25d70 100644 --- a/src/gui/text/qfontengine_ft_p.h +++ b/src/gui/text/qfontengine_ft_p.h @@ -233,6 +233,7 @@ private: virtual void recalcAdvances(QGlyphLayout *glyphs, ShaperFlags flags) const; virtual QImage alphaMapForGlyph(glyph_t g) { return alphaMapForGlyph(g, 0); } virtual QImage alphaMapForGlyph(glyph_t, QFixed); + QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t); virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t); virtual glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, QFixed subPixelPosition, @@ -265,7 +266,7 @@ private: inline Glyph *loadGlyph(uint glyph, QFixed subPixelPosition, GlyphFormat format = Format_None, bool fetchMetricsOnly = false) const { return loadGlyph(cacheEnabled ? &defaultGlyphSet : 0, glyph, subPixelPosition, format, fetchMetricsOnly); } Glyph *loadGlyph(QGlyphSet *set, uint glyph, QFixed subPixelPosition, GlyphFormat = Format_None, bool fetchMetricsOnly = false) const; - Glyph *loadGlyphFor(glyph_t g, QFixed subPixelPosition, GlyphFormat format); + Glyph *loadGlyphFor(glyph_t g, QFixed subPixelPosition, GlyphFormat format, const QTransform &t); QGlyphSet *loadTransformedGlyphSet(const QTransform &matrix); bool loadGlyphs(QGlyphSet *gs, const glyph_t *glyphs, int num_glyphs, diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index 50b1bb9e9d..9364b82bed 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -463,7 +463,7 @@ private: class QTestFontEngine : public QFontEngineBox { public: - inline QTestFontEngine(int size) : QFontEngineBox(TestFontEngine, size) {} + QTestFontEngine(int size); }; QT_END_NAMESPACE diff --git a/src/gui/text/qharfbuzzng.cpp b/src/gui/text/qharfbuzzng.cpp index eb7bca1974..16c45e642b 100644 --- a/src/gui/text/qharfbuzzng.cpp +++ b/src/gui/text/qharfbuzzng.cpp @@ -632,7 +632,7 @@ hb_face_t *hb_qt_face_get_for_engine(QFontEngine *fe) fe->face_destroy_func = _hb_qt_face_release; } - return hb_face_reference((hb_face_t *)fe->face_); + return (hb_face_t *)fe->face_; } @@ -645,8 +645,6 @@ _hb_qt_font_create(QFontEngine *fe) hb_font_t *font = hb_font_create(face); - hb_face_destroy(face); // ref-ed in hb_qt_face_get_for_engine() - if (Q_UNLIKELY(hb_font_is_immutable(font))) { hb_font_destroy(font); return NULL; @@ -684,7 +682,7 @@ hb_font_t *hb_qt_font_get_for_engine(QFontEngine *fe) fe->font_destroy_func = _hb_qt_font_release; } - return hb_font_reference((hb_font_t *)fe->font_); + return (hb_font_t *)fe->font_; } QT_END_NAMESPACE diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 9fe1fd26e9..d156124b98 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -1168,8 +1168,6 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st }; const int num_features = 1; shapedOk = hb_shape_full(hb_font, buffer, features, num_features, 0); - - hb_font_destroy(hb_font); } if (!shapedOk) { hb_buffer_destroy(buffer); diff --git a/src/gui/text/qtextimagehandler.cpp b/src/gui/text/qtextimagehandler.cpp index f6525448de..37c18e3624 100644 --- a/src/gui/text/qtextimagehandler.cpp +++ b/src/gui/text/qtextimagehandler.cpp @@ -44,31 +44,49 @@ QT_BEGIN_NAMESPACE -static QString resolve2xFile(const QString &fileName, qreal targetDevicePixelRatio) +static QString resolveFileName(QString fileName, QUrl *url, qreal targetDevicePixelRatio) { + // We might use the fileName for loading if url loading fails + // try to make sure it is a valid file path. + // Also, QFile{Info}::exists works only on filepaths (not urls) + + if (url->isValid()) { + if (url->scheme() == QLatin1Literal("qrc")) { + fileName = fileName.right(fileName.length() - 3); + } + else if (url->scheme() == QLatin1Literal("file")) { + fileName = url->toLocalFile(); + } + } + if (targetDevicePixelRatio <= 1.0) return fileName; - int dotIndex = fileName.lastIndexOf(QLatin1Char('.')); + // try to find a 2x version + + const int dotIndex = fileName.lastIndexOf(QLatin1Char('.')); if (dotIndex != -1) { QString at2xfileName = fileName; at2xfileName.insert(dotIndex, QStringLiteral("@2x")); - if (QFile::exists(at2xfileName)) - return at2xfileName; + if (QFile::exists(at2xfileName)) { + fileName = at2xfileName; + *url = QUrl(fileName); + } } + return fileName; } -static QPixmap getPixmap(QTextDocument *doc, const QTextImageFormat &format) + +static QPixmap getPixmap(QTextDocument *doc, const QTextImageFormat &format, const qreal devicePixelRatio = 1.0) { QPixmap pm; QString name = format.name(); - if (name.startsWith(QLatin1String(":/"))) // auto-detect resources + if (name.startsWith(QLatin1String(":/"))) // auto-detect resources and convert them to url name.prepend(QLatin1String("qrc")); - QPaintDevice *pdev = doc->documentLayout()->paintDevice(); - name = resolve2xFile(name, pdev ? pdev->devicePixelRatio() : qApp->devicePixelRatio()); QUrl url = QUrl(name); + name = resolveFileName(name, &url, devicePixelRatio); const QVariant data = doc->resource(QTextDocument::ImageResource, url); if (data.type() == QVariant::Pixmap || data.type() == QVariant::Image) { pm = qvariant_cast<QPixmap>(data); @@ -77,19 +95,18 @@ static QPixmap getPixmap(QTextDocument *doc, const QTextImageFormat &format) } if (pm.isNull()) { - QString context; #if 0 + QString context; // ### Qt5 QTextBrowser *browser = qobject_cast<QTextBrowser *>(doc->parent()); if (browser) context = browser->source().toString(); #endif + // try direct loading QImage img; - if (img.isNull()) { // try direct loading - name = format.name(); // remove qrc:/ prefix again - if (name.isEmpty() || !img.load(name)) - return QPixmap(QLatin1String(":/qt-project.org/styles/commonstyle/images/file-16.png")); - } + if (name.isEmpty() || !img.load(name)) + return QPixmap(QLatin1String(":/qt-project.org/styles/commonstyle/images/file-16.png")); + pm = QPixmap::fromImage(img); doc->addResource(QTextDocument::ImageResource, url, pm); } @@ -142,16 +159,15 @@ static QSize getPixmapSize(QTextDocument *doc, const QTextImageFormat &format) return size; } -static QImage getImage(QTextDocument *doc, const QTextImageFormat &format) +static QImage getImage(QTextDocument *doc, const QTextImageFormat &format, const qreal devicePixelRatio = 1.0) { QImage image; QString name = format.name(); if (name.startsWith(QLatin1String(":/"))) // auto-detect resources name.prepend(QLatin1String("qrc")); - QPaintDevice *pdev = doc->documentLayout()->paintDevice(); - name = resolve2xFile(name, pdev ? pdev->devicePixelRatio() : qApp->devicePixelRatio()); QUrl url = QUrl(name); + name = resolveFileName(name, &url, devicePixelRatio); const QVariant data = doc->resource(QTextDocument::ImageResource, url); if (data.type() == QVariant::Image) { image = qvariant_cast<QImage>(data); @@ -160,19 +176,18 @@ static QImage getImage(QTextDocument *doc, const QTextImageFormat &format) } if (image.isNull()) { - QString context; - #if 0 + QString context; // ### Qt5 QTextBrowser *browser = qobject_cast<QTextBrowser *>(doc->parent()); if (browser) context = browser->source().toString(); #endif - if (image.isNull()) { // try direct loading - name = format.name(); // remove qrc:/ prefix again - if (name.isEmpty() || !image.load(name)) - return QImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/file-16.png")); - } + // try direct loading + + if (name.isEmpty() || !image.load(name)) + return QImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/file-16.png")); + doc->addResource(QTextDocument::ImageResource, url, image); } @@ -241,10 +256,10 @@ void QTextImageHandler::drawObject(QPainter *p, const QRectF &rect, QTextDocumen const QTextImageFormat imageFormat = format.toImageFormat(); if (QCoreApplication::instance()->thread() != QThread::currentThread()) { - const QImage image = getImage(doc, imageFormat); + const QImage image = getImage(doc, imageFormat, p->device()->devicePixelRatio()); p->drawImage(rect, image, image.rect()); } else { - const QPixmap pixmap = getPixmap(doc, imageFormat); + const QPixmap pixmap = getPixmap(doc, imageFormat, p->device()->devicePixelRatio()); p->drawPixmap(rect, pixmap, pixmap.rect()); } } diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp index adc5663299..1ac50d3e5c 100644 --- a/src/gui/text/qtextlayout.cpp +++ b/src/gui/text/qtextlayout.cpp @@ -2256,8 +2256,10 @@ QList<QGlyphRun> QTextLine::glyphRuns(int from, int length) const logClusters, iterator.itemStart, iterator.itemLength)); - for (int i = 0; i < subLayout.numGlyphs; ++i) - pos.rx() += subLayout.advances[i].toReal(); + for (int i = 0; i < subLayout.numGlyphs; ++i) { + QFixed justification = QFixed::fromFixed(subLayout.justifications[i].space_18d6); + pos.rx() += (subLayout.advances[i] + justification).toReal(); + } if (rtl) end = start; @@ -2559,6 +2561,10 @@ qreal QTextLine::cursorToX(int *cursorPos, Edge edge) const } else itm = eng->findItem(pos); + if (itm < 0) { + *cursorPos = 0; + return x.toReal(); + } eng->shapeLine(line); const QScriptItem *si = &eng->layoutData->items[itm]; diff --git a/src/gui/util/qvalidator.cpp b/src/gui/util/qvalidator.cpp index 1b5a10f733..f879847935 100644 --- a/src/gui/util/qvalidator.cpp +++ b/src/gui/util/qvalidator.cpp @@ -316,7 +316,12 @@ void QValidator::fixup(QString &) const QIntValidator uses its locale() to interpret the number. For example, in Arabic locales, QIntValidator will accept Arabic digits. - \sa QDoubleValidator, QRegExpValidator, {Line Edits Example} + \note The QLocale::NumberOptions set on the locale() also affect the + way the number is interpreted. For example, since QLocale::RejectGroupSeparator + is not set by default, the validator will accept group separators. It is thus + recommended to use QLocale::toInt() to obtain the numeric value. + + \sa QDoubleValidator, QRegExpValidator, QLocale::toInt(), {Line Edits Example} */ /*! @@ -393,7 +398,8 @@ static qlonglong pow10(int exp) QValidator::State QIntValidator::validate(QString & input, int&) const { QByteArray buff; - if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff)) { + if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, + -1, locale().numberOptions() & QLocale::RejectGroupSeparator)) { return Invalid; } @@ -432,7 +438,8 @@ QValidator::State QIntValidator::validate(QString & input, int&) const void QIntValidator::fixup(QString &input) const { QByteArray buff; - if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff)) { + if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, + -1, locale().numberOptions() & QLocale::RejectGroupSeparator)) { return; } bool ok, overflow; @@ -548,7 +555,12 @@ public: in the German locale, "1,234" will be accepted as the fractional number 1.234. In Arabic locales, QDoubleValidator will accept Arabic digits. - \sa QIntValidator, QRegExpValidator, {Line Edits Example} + \note The QLocale::NumberOptions set on the locale() also affect the + way the number is interpreted. For example, since QLocale::RejectGroupSeparator + is not set by default, the validator will accept group separators. It is thus + recommended to use QLocale::toDouble() to obtain the numeric value. + + \sa QIntValidator, QRegExpValidator, QLocale::toDouble(), {Line Edits Example} */ /*! @@ -648,8 +660,10 @@ QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QL { Q_Q(const QDoubleValidator); QByteArray buff; - if (!locale.d->m_data->validateChars(input, numMode, &buff, q->dec)) + if (!locale.d->m_data->validateChars(input, numMode, &buff, q->dec, + locale.numberOptions() & QLocale::RejectGroupSeparator)) { return QValidator::Invalid; + } if (buff.isEmpty()) return QValidator::Intermediate; @@ -1003,7 +1017,7 @@ QValidator::State QRegularExpressionValidator::validate(QString &input, int &pos const QRegularExpressionMatch m = d->usedRe.match(input, 0, QRegularExpression::PartialPreferCompleteMatch); if (m.hasMatch()) { return Acceptable; - } else if (m.hasPartialMatch()) { + } else if (input.isEmpty() || m.hasPartialMatch()) { return Intermediate; } else { pos = input.size(); |