I have made the following changes intended for : CE:MW:Shared / nemo-qml-plugins
Please review and accept or decline. BOSS has already run some checks on this request. See the "Messages from BOSS" section below. https://build.pub.meego.com//request/show/7392 Thank You, Marko Saukko [This message was auto-generated] --- Request # 7392: Messages from BOSS: State: review at 2012-11-16T10:49:36 by bossbot Reviews: accepted by bossbot : Prechecks succeeded. new for CE-maintainers : Please replace this text with a review and approve/reject the review (not the SR). BOSS will take care of the rest Changes: submit: Project:MTF:MW / nemo-qml-plugins -> CE:MW:Shared / nemo-qml-plugins changes files: -------------- --- nemo-qml-plugins.changes +++ nemo-qml-plugins.changes @@ -0,0 +1,6 @@ +* Wed Nov 16 2012 Johan Paul <[email protected]> - 0.1.6 +- Add a dedicated Thumbnail item. (by Andrew den Exter) +- Move the gstreamer video thumbnailer into a separate binary. (by Andrew den Exter) +- Add 'count' property to SeasideProxyModel. (by Johan Paul) +- Add address detail types as enums. (by Johan Paul) + old: ---- nemo-qml-plugins-0.1.5.tar.bz2 new: ---- nemo-qml-plugins-0.1.6.tar.bz2 spec files: ----------- --- nemo-qml-plugins.spec +++ nemo-qml-plugins.spec @@ -9,7 +9,7 @@ # << macros Summary: Nemo QML plugins source package. -Version: 0.1.5 +Version: 0.1.6 Release: 1 Group: System/Libraries License: BSD other changes: -------------- ++++++ nemo-qml-plugins-0.1.5.tar.bz2 -> nemo-qml-plugins-0.1.6.tar.bz2 --- contacts/src/seasideperson.h +++ contacts/src/seasideperson.h @@ -74,6 +74,12 @@ AddressHomeType, AddressWorkType, AddressOtherType, + AddressStreetType, + AddressLocalityType, + AddressRegionType, + AddressPostcodeType, + AddressCountryType, + AddressPOBoxType, // Website WebsiteHomeType, WebsiteWorkType, --- contacts/src/seasideproxymodel.cpp +++ contacts/src/seasideproxymodel.cpp @@ -32,6 +32,16 @@ setDynamicSortFilter(true); setFilterKeyColumn(-1); + + connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), + SIGNAL(countChanged())); + + connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), + SIGNAL(countChanged())); + + connect(this, SIGNAL(layoutChanged()), + SIGNAL(countChanged())); + setSourceModel(SeasidePeopleModel::instance()); sort(0, Qt::AscendingOrder); } @@ -147,4 +157,7 @@ return m; } - +int SeasideProxyModel::count() const +{ + return rowCount(QModelIndex()); +} --- contacts/src/seasideproxymodel.h +++ contacts/src/seasideproxymodel.h @@ -34,7 +34,6 @@ }; Q_INVOKABLE virtual void setFilter(FilterType filter); - Q_INVOKABLE virtual void search(const QString &pattern); // for SectionScroller support @@ -77,10 +76,12 @@ SeasidePeopleModel *model = static_cast<SeasidePeopleModel *>(sourceModel()); return model->exportContacts(); } - Q_INVOKABLE int contactCount() - { - rowCount(QModelIndex()); - } + + Q_PROPERTY(int count READ count NOTIFY countChanged) + int count() const; + +signals: + void countChanged(); protected: virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const; @@ -91,7 +92,7 @@ friend class tst_SeasideProxyModel; SeasideProxyModelPriv *priv; - Q_DISABLE_COPY(SeasideProxyModel); + Q_DISABLE_COPY(SeasideProxyModel) }; #endif // SEASIDEPROXYMODEL_H --- thumbnailer/gstvideothumbnailer +++ thumbnailer/gstvideothumbnailer +(directory) --- thumbnailer/gstvideothumbnailer/gstvideothumbnailer.cpp +++ thumbnailer/gstvideothumbnailer/gstvideothumbnailer.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 Jolla Ltd + * Contact: Andrew den Exter <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + + +#include <gst/gst.h> +#include <gst/app/gstappsink.h> + +#include <QImage> + +namespace { + +struct Thumbnailer +{ + Thumbnailer() + : pipeline(0) + , decodebin(0) + , transform(0) + , appsink(0) + { + } + + ~Thumbnailer() + { + if (pipeline) { + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(pipeline)); + } else { + if (decodebin) + gst_object_unref(GST_OBJECT(decodebin)); + if (transform) + gst_object_unref(GST_OBJECT(transform)); + if (appsink) + gst_object_unref(GST_OBJECT(appsink)); + } + } + + GstElement *pipeline; + GstElement *decodebin; + GstElement *transform; + GstElement *appsink; +}; + +static gboolean decodebin_autoplug_continue(GstElement *, GstPad *, GstCaps *caps, gpointer) +{ + // Short cut audio streams as soon as possible so they're not decoded. + return qstrncmp(gst_structure_get_name(gst_caps_get_structure(caps, 0)), "audio/", 6) != 0; +} + +static void decodebin_new_pad(GstElement *element, GstPad *pad, gpointer data) +{ + Q_UNUSED(element); + Thumbnailer *thumbnailer = static_cast<Thumbnailer *>(data); + + GstCaps *caps = gst_pad_get_caps(pad); + GstStructure *structure = gst_caps_get_structure(caps, 0); + + GstPad *sinkPad = gst_element_get_static_pad(thumbnailer->transform, "sink"); + + bool isFakeSink = false; + if (gst_pad_is_linked(sinkPad) || qstrncmp(gst_structure_get_name(structure), "video/x-raw-", 12) != 0) { + // Create a fake sink for any non video streams so they don't stall the pipeline. + GstElement *sink = gst_element_factory_make("fakesink", NULL); + sinkPad = gst_element_get_static_pad(sink, "sink"); + + gst_bin_add(GST_BIN(thumbnailer->pipeline), sink); + gst_element_set_state(sink, GST_STATE_PAUSED); + + isFakeSink = true; + } + + for (;;) { + switch (gst_pad_link(pad, sinkPad)) { + case GST_PAD_LINK_OK: + return; + default: + break; + } + if (isFakeSink) + return; + + GstElement *sink = gst_element_factory_make("fakesink", NULL); + sinkPad = gst_element_get_static_pad(sink, "sink"); + + gst_bin_add(GST_BIN(thumbnailer->pipeline), sink); + gst_element_set_state(sink, GST_STATE_PAUSED); + + isFakeSink = true; + } +} + +} + +extern "C" Q_DECL_EXPORT QImage createThumbnail(const QString &fileName, const QSize &requestedSize, bool crop) +{ + static bool initialized = false; + if (!initialized) { + gst_init(0, 0); + initialized = true; + } + + QImage image; + Thumbnailer thumbnailer; + + thumbnailer.decodebin = gst_element_factory_make("uridecodebin", "decodebin"); + thumbnailer.transform = gst_element_factory_make("ffmpegcolorspace", "transform"); + thumbnailer.appsink = gst_element_factory_make("appsink", "sink"); + + if (!thumbnailer.decodebin || !thumbnailer.transform || !thumbnailer.appsink) + return image; + + thumbnailer.pipeline = gst_pipeline_new(NULL); + if (!thumbnailer.pipeline) + return image; + + gst_bin_add_many(GST_BIN(thumbnailer.pipeline), thumbnailer.decodebin, thumbnailer.transform, thumbnailer.appsink, NULL); + + GstCaps *sinkCaps = gst_caps_new_simple( + "video/x-raw-rgb", + "bpp", G_TYPE_INT , 32, + "depth", G_TYPE_INT , 24, + "endianness", G_TYPE_INT, 4321, + "red_mask", G_TYPE_INT , 0x0000FF00, + "green_mask", G_TYPE_INT, 0x00FF0000, + "blue_mask", G_TYPE_INT , 0xFF000000, + NULL); + gst_app_sink_set_caps(GST_APP_SINK(thumbnailer.appsink), sinkCaps); + + gst_element_link_pads(thumbnailer.transform, "src", thumbnailer.appsink, "sink"); + g_signal_connect(thumbnailer.decodebin, "autoplug-continue", G_CALLBACK(decodebin_autoplug_continue), &thumbnailer); + g_signal_connect(thumbnailer.decodebin, "pad-added", G_CALLBACK(decodebin_new_pad), &thumbnailer); + + g_object_set(G_OBJECT(thumbnailer.decodebin), "uri", (QLatin1String("file://") + fileName).toLocal8Bit().constData(), NULL); + + gst_element_set_state(thumbnailer.pipeline, GST_STATE_PAUSED); + + GstState currentState; + GstState pendingState; + GstStateChangeReturn result = gst_element_get_state( + thumbnailer.pipeline, + ¤tState, + &pendingState, + 5 * GST_SECOND); + if (result == GST_STATE_CHANGE_SUCCESS) { + // Seek a little to hopefully capture something a little more meaningful than a fade + // from black. + gst_element_seek_simple( + thumbnailer.pipeline, + GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_FLUSH), + 2 * GST_SECOND); + if (GstBuffer *buffer = gst_app_sink_pull_preroll(GST_APP_SINK(thumbnailer.appsink))) { + GstStructure *structure = gst_caps_get_structure(GST_BUFFER_CAPS(buffer), 0); + + int width = 0; + int height = 0; + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + + if (width > 0 && height > 0) { + const int croppedWidth = crop ? height * requestedSize.width() / requestedSize.height() : width; + const int croppedHeight = crop ? width * requestedSize.height() / requestedSize.width() : height; + const int bytesPerLine = width * 4; + QImage frame; + + if (croppedWidth < width) { + const uchar *data = GST_BUFFER_DATA(buffer) + (width - croppedWidth) * 2; + frame = QImage(data, croppedWidth, height, bytesPerLine, QImage::Format_RGB32); + } else if (croppedHeight < height) { + const uchar *data = GST_BUFFER_DATA(buffer) + bytesPerLine * ((height - croppedHeight) / 2); + frame = QImage(data, width, croppedHeight, bytesPerLine, QImage::Format_RGB32); + } else { + frame = QImage(GST_BUFFER_DATA(buffer), width, height, bytesPerLine, QImage::Format_RGB32); + } + + image = frame.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + image.detach(); // Ensure a deep copy is made in the instance the image isn't scaled. + } + + gst_buffer_unref(buffer); + } + } + + return image; +} --- thumbnailer/gstvideothumbnailer/gstvideothumbnailer.pro +++ thumbnailer/gstvideothumbnailer/gstvideothumbnailer.pro @@ -0,0 +1,13 @@ +TEMPLATE = lib +CONFIG += hide_symbols plugin +TARGET = videothumbnailer + +target.path = $$[QT_INSTALL_IMPORTS]/org/nemomobile/thumbnailer/thumbnailers +INSTALLS += target + +SOURCES += gstvideothumbnailer.cpp + +CONFIG += link_pkgconfig +PKGCONFIG += \ + gstreamer-0.10 \ + gstreamer-app-0.10 --- thumbnailer/nemothumbnailitem.cpp +++ thumbnailer/nemothumbnailitem.cpp @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2012 Jolla Ltd + * Contact: Andrew den Exter <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#include "nemothumbnailitem.h" + +#include "nemothumbnailprovider.h" +#include "nemovideothumbnailer.h" + +#include <QCoreApplication> +#include <QPainter> +#include <QPixmapCache> + +struct ThumbnailRequest +{ + ThumbnailRequest(NemoThumbnailItem *item, const QString &fileName, const QByteArray &cacheKey); + ~ThumbnailRequest(); + + static void enqueue(ThumbnailRequest *&queue, ThumbnailRequest *request); + static ThumbnailRequest *dequeue(ThumbnailRequest *&queue); + + ThumbnailRequest **previous; + ThumbnailRequest *next; + NemoThumbnailItem *item; + QByteArray cacheKey; + QString fileName; + QString mimeType; + QSize size; + QImage image; + NemoThumbnailItem::FillMode fillMode; +}; + +ThumbnailRequest::ThumbnailRequest( + NemoThumbnailItem *item, const QString &fileName, const QByteArray &cacheKey) + : previous(0) + , next(0) + , item(item) + , cacheKey(cacheKey) + , fileName(fileName) + , mimeType(item->m_mimeType) + , size(item->m_sourceSize) + , fillMode(item->m_fillMode) +{ +} + +ThumbnailRequest::~ThumbnailRequest() +{ + if (next) + next->previous = previous; + if (previous) + *previous = next; +} + +void ThumbnailRequest::enqueue(ThumbnailRequest *&queue, ThumbnailRequest *request) +{ + // Remove from previous list. + if (request->next) + request->next->previous = request->previous; + if (request->previous) + *request->previous = request->next; + + request->next = queue; + if (request->next) + request->next->previous = &request->next; + request->previous = &queue; + queue = request; +} + +ThumbnailRequest *ThumbnailRequest::dequeue(ThumbnailRequest *&queue) +{ + ThumbnailRequest *request = queue; + if (request) { + if (request->next) + request->next->previous = &queue; + queue = request->next; + request->previous = 0; + request->next = 0; + } + return request; +} + +NemoThumbnailItem::NemoThumbnailItem(QDeclarativeItem *parent) + : QDeclarativeItem(parent) + , m_request(0) + , m_priority(NormalPriority) + , m_status(Null) + , m_fillMode(PreserveAspectCrop) +{ + setFlag(ItemHasNoContents, false); +} + +NemoThumbnailItem::~NemoThumbnailItem() +{ + if (m_request) + NemoThumbnailLoader::instance->cancelRequest(this); +} + +void NemoThumbnailItem::componentComplete() +{ + QDeclarativeItem::componentComplete(); + + updateThumbnail(true); +} + +QUrl NemoThumbnailItem::source() const +{ + return m_source; +} + +void NemoThumbnailItem::setSource(const QUrl &source) +{ + if (m_source != source) { + m_source = source; + emit sourceChanged(); + updateThumbnail(true); + } +} + +QString NemoThumbnailItem::mimeType() const +{ + return m_mimeType; +} + +void NemoThumbnailItem::setMimeType(const QString &mimeType) +{ + if (m_mimeType != mimeType) { + m_mimeType = mimeType; + emit mimeTypeChanged(); + updateThumbnail(false); + } +} + +NemoThumbnailItem::Priority NemoThumbnailItem::priority() const +{ + return m_priority; +} + +void NemoThumbnailItem::setPriority(Priority priority) +{ + if (m_priority != priority) { + m_priority = priority; + emit priorityChanged(); + if (m_request) + NemoThumbnailLoader::instance->updateRequest(this, false); + } +} + +QSize NemoThumbnailItem::sourceSize() const +{ + return m_sourceSize; +} + +void NemoThumbnailItem::setSourceSize(const QSize &size) +{ + if (m_sourceSize != size) { + m_sourceSize = size; + emit sourceSizeChanged(); + updateThumbnail(true); + } +} + +NemoThumbnailItem::FillMode NemoThumbnailItem::fillMode() const +{ + return m_fillMode; +} + +void NemoThumbnailItem::setFillMode(FillMode mode) +{ + if (m_fillMode != mode) { + m_fillMode = mode; + emit fillModeChanged(); + updateThumbnail(true); + } +} + +NemoThumbnailItem::Status NemoThumbnailItem::status() const +{ + return m_status; +} + +void NemoThumbnailItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) +{ + if (m_pixmap.isNull()) + return; + + painter->drawPixmap(QRect(0, 0, width(), height()), m_pixmap); +} + +void NemoThumbnailItem::updateThumbnail(bool identityChanged) +{ + if (!isComponentComplete()) + return; + + if (m_source.isLocalFile() && !m_sourceSize.isEmpty()) + NemoThumbnailLoader::instance->updateRequest(this, identityChanged); + else if (m_request) + NemoThumbnailLoader::instance->cancelRequest(this); + + if (m_request && m_status != Loading) { + m_status = Loading; + emit statusChanged(); + } else if (!m_request && !m_pixmap.isNull()) { + if (m_status != Ready) { + m_status = Ready; + emit statusChanged(); + } + update(); + } else if ((!m_source.isValid() || m_source.isEmpty()) && m_status != Null) { + m_status = Null; + emit statusChanged(); + } +} + +NemoThumbnailLoader *NemoThumbnailLoader::instance = 0; + +NemoThumbnailLoader::NemoThumbnailLoader(QObject *parent) + : QThread(parent) + , m_thumbnailHighPriority(0) + , m_thumbnailNormalPriority(0) + , m_thumbnailLowPriority(0) + , m_generateHighPriority(0) + , m_generateNormalPriority(0) + , m_generateLowPriority(0) + , m_completedRequests(0) + , m_quit(false) +{ + Q_ASSERT(!instance); + instance = this; +} + +NemoThumbnailLoader::~NemoThumbnailLoader() +{ + shutdown(); +} + +void NemoThumbnailLoader::updateRequest(NemoThumbnailItem *item, bool identityChanged) +{ + QString fileName; + QByteArray cacheKey; + if (identityChanged) { + fileName = item->m_source.toLocalFile(); + cacheKey = NemoThumbnailProvider::cacheKey(fileName, item->m_sourceSize); + if (item->m_fillMode == NemoThumbnailItem::PreserveAspectFit) + cacheKey += 'F'; + + QPixmap pixmap; + if (QPixmapCache::find(cacheKey, &pixmap)) { + if (item->m_request) + cancelRequest(item); + item->m_pixmap = pixmap; + item->setImplicitWidth(pixmap.width()); + item->setImplicitHeight(pixmap.height()); + return; + } + } + + QMutexLocker locker(&m_mutex); + + if (!item->m_request) { // There's no current request. + item->m_request = new ThumbnailRequest(item, fileName, cacheKey); + } else if (item->m_request->previous) { // The current request is pending. + if (identityChanged) { + item->m_request->cacheKey = cacheKey; + item->m_request->fileName = fileName; + item->m_request->size = item->m_sourceSize; + item->m_request->fillMode = item->m_fillMode; + } + item->m_request->mimeType = item->m_mimeType; + } else { // The current request is being processed. Replace it. + item->m_request->item = 0; + item->m_request = identityChanged + ? new ThumbnailRequest(item, fileName, cacheKey) + : new ThumbnailRequest(item, item->m_request->fileName, item->m_request->cacheKey); + } + + ThumbnailRequest::enqueue(m_thumbnailRequests[item->m_priority], item->m_request); + + m_waitCondition.wakeOne(); +} + +void NemoThumbnailLoader::cancelRequest(NemoThumbnailItem *item) +{ + Q_ASSERT(item->m_request); + + QMutexLocker locker(&m_mutex); + // The only time a request doesn't belong to a list is while it is being processed. + if (item->m_request->previous) + delete item->m_request; + else + item->m_request->item = 0; + item->m_request = 0; +} + +void NemoThumbnailLoader::shutdown() +{ + if (!instance) + return; + + { + QMutexLocker locker(&instance->m_mutex); + + instance->m_quit = true; + + instance->m_waitCondition.wakeOne(); + } + + instance->wait(); + + for (int i = 0; i < NemoThumbnailItem::PriorityCount; ++i) { + while (instance->m_thumbnailRequests[i]) + delete instance->m_thumbnailRequests[i]; + while (instance->m_generateRequests[i]) + delete instance->m_generateRequests[i]; + } + while (instance->m_completedRequests) + delete instance->m_completedRequests; +} + +bool NemoThumbnailLoader::event(QEvent *event) +{ + if (event->type() == QEvent::User) { + ThumbnailRequest *completedRequest; + { + QMutexLocker locker(&m_mutex); + completedRequest = m_completedRequests; + if (completedRequest) + completedRequest->previous = &completedRequest; + m_completedRequests = 0; + } + + while (completedRequest) { + if (completedRequest->item) { + completedRequest->item->m_request = 0; + if (!completedRequest->image.isNull()) { + completedRequest->item->m_pixmap = QPixmap::fromImage(completedRequest->image); + QPixmapCache::insert(completedRequest->cacheKey, completedRequest->item->m_pixmap); + completedRequest->item->m_status = NemoThumbnailItem::Ready; + completedRequest->item->setImplicitWidth(completedRequest->item->m_pixmap.width()); + completedRequest->item->setImplicitHeight(completedRequest->item->m_pixmap.height()); + emit completedRequest->item->statusChanged(); + } else { + completedRequest->item->m_pixmap = QPixmap(); + completedRequest->item->m_status = NemoThumbnailItem::Error; + emit completedRequest->item->statusChanged(); + } + completedRequest->item->update(); + } + delete completedRequest; + } + + return true; + } else { + return NemoThumbnailLoader::event(event); + } +} + +void NemoThumbnailLoader::run() +{ + NemoThumbnailProvider::setupCache(); + + QMutexLocker locker(&m_mutex); + + for (;;) { + ThumbnailRequest *request = 0; + bool tryCache = true; + NemoThumbnailItem::Priority priority = NemoThumbnailItem::LowPriority; + + // Grab the next request in priority order. High and normal priority thumbnails are + // prioritized over generating any thumbnail, and low priority loading or generation + // is deprioritized over everything else. + if (m_quit) { + return; + } else if ((request = ThumbnailRequest::dequeue(m_thumbnailHighPriority))) { + priority = NemoThumbnailItem::HighPriority; + } else if ((request = ThumbnailRequest::dequeue(m_thumbnailNormalPriority))) { + priority = NemoThumbnailItem::NormalPriority; + } else if ((request = ThumbnailRequest::dequeue(m_generateHighPriority))) { + tryCache = false; + } else if ((request = ThumbnailRequest::dequeue(m_generateNormalPriority))) { + tryCache = false; + } else if ((request = ThumbnailRequest::dequeue(m_thumbnailLowPriority))) { + priority = NemoThumbnailItem::LowPriority; + } else if ((request = ThumbnailRequest::dequeue(m_generateLowPriority))) { + tryCache = false; + } else { + m_waitCondition.wait(&m_mutex); + continue; + } + + Q_ASSERT(request); + const QByteArray cacheKey = request->cacheKey; + const QString fileName = request->fileName; + const QString mimeType = request->mimeType; + const QSize requestedSize = request->size; + const bool crop = request->fillMode == NemoThumbnailItem::PreserveAspectCrop; + + locker.unlock(); + + if (tryCache) { + QImage image = NemoThumbnailProvider::loadThumbnail(fileName, cacheKey); + + locker.relock(); + if (!request->item) { + // The request was cancelled while the thumbnail was loading, delete it now so + // so to not spend time generating a thumbnail that won't be used. + delete request; + } else if (!image.isNull()) { + request->image = image; + if (!m_completedRequests) + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + ThumbnailRequest::enqueue(m_completedRequests, request); + } else { + ThumbnailRequest::enqueue(m_generateRequests[priority], request); + } + } else { + QImage image = !mimeType.startsWith(QLatin1String("video/"), Qt::CaseInsensitive) + ? NemoThumbnailProvider::generateThumbnail(fileName, cacheKey, requestedSize, crop) + : NemoVideoThumbnailer::generateThumbnail(fileName, cacheKey, requestedSize, crop); + + locker.relock(); + request->image = image; + if (!m_completedRequests) + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + ThumbnailRequest::enqueue(m_completedRequests, request); + } + } +} --- thumbnailer/nemothumbnailitem.h +++ thumbnailer/nemothumbnailitem.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012 Jolla Ltd + * Contact: Andrew den Exter <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#ifndef NEMOTHUMBNAILITEM_H +#define NEMOTHUMBNAILITEM_H + +#include <QtCore/qmutex.h> +#include <QtCore/qthread.h> +#include <QtCore/qwaitcondition.h> +#include <QtDeclarative/qdeclarativeitem.h> + +struct ThumbnailRequest; + +class NemoThumbnailItem : public QDeclarativeItem +{ + Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(QString mimeType READ mimeType WRITE setMimeType NOTIFY mimeTypeChanged) + Q_PROPERTY(QSize sourceSize READ sourceSize WRITE setSourceSize NOTIFY sourceSizeChanged) + Q_PROPERTY(FillMode fillMode READ fillMode WRITE setFillMode NOTIFY fillModeChanged) + Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_ENUMS(Priority) + Q_ENUMS(Status) + Q_ENUMS(FillMode) +public: + enum FillMode + { + PreserveAspectFit = 1, // Use the same values as Image for compatibility. + PreserveAspectCrop + }; + + enum Priority + { + HighPriority, + NormalPriority, + LowPriority + }; + + enum + { + PriorityCount = 3 + }; + + enum Status + { + Null, + Ready, + Loading, + Error + }; + + explicit NemoThumbnailItem(QDeclarativeItem *parent = 0); + ~NemoThumbnailItem(); + + void componentComplete(); + + QUrl source() const; + void setSource(const QUrl &source); + + QString mimeType() const; + void setMimeType(const QString &mimeType); + + QSize sourceSize() const; + void setSourceSize(const QSize &size); + + FillMode fillMode() const; + void setFillMode(FillMode mode); + + Priority priority() const; + void setPriority(Priority priority); + + Status status() const; + + void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *); + +Q_SIGNALS: + void sourceChanged(); + void mimeTypeChanged(); + void sourceSizeChanged(); + void fillModeChanged(); + void priorityChanged(); + void statusChanged(); + +private: + Q_DISABLE_COPY(NemoThumbnailItem) + + void updateThumbnail(bool identityChanged); + + + ThumbnailRequest *m_request; + QUrl m_source; + QString m_mimeType; + QSize m_sourceSize; + QPixmap m_pixmap; + Priority m_priority; + Status m_status; + FillMode m_fillMode; + + friend struct ThumbnailRequest; + friend class NemoThumbnailLoader; +}; + +class NemoThumbnailLoader : public QThread +{ +public: + explicit NemoThumbnailLoader(QObject *parent = 0); + ~NemoThumbnailLoader(); + + void updateRequest(NemoThumbnailItem *item, bool identityChanged); + void cancelRequest(NemoThumbnailItem *item); + + static void shutdown(); + + static NemoThumbnailLoader *instance; + +protected: + bool event(QEvent *event); + void run(); + +private: + union { + struct { + ThumbnailRequest *m_thumbnailRequests[NemoThumbnailItem::PriorityCount]; + ThumbnailRequest *m_generateRequests[NemoThumbnailItem::PriorityCount]; + }; + struct { + ThumbnailRequest *m_thumbnailHighPriority; + ThumbnailRequest *m_thumbnailNormalPriority; + ThumbnailRequest *m_thumbnailLowPriority; + ThumbnailRequest *m_generateHighPriority; + ThumbnailRequest *m_generateNormalPriority; + ThumbnailRequest *m_generateLowPriority; + }; + }; + ThumbnailRequest *m_completedRequests; + + QMutex m_mutex; + QWaitCondition m_waitCondition; + bool m_quit; +}; + +#endif --- thumbnailer/nemothumbnailprovider.cpp +++ thumbnailer/nemothumbnailprovider.cpp @@ -37,6 +37,7 @@ #include <QImageReader> #include <QDateTime> #include <QtEndian> +#include <QElapsedTimer> #undef THUMBNAILER_DEBUG @@ -59,7 +60,7 @@ return cachePath() + QDir::separator() + "raw"; } -static void setupCache() +void NemoThumbnailProvider::setupCache() { // the syscalls make baby jesus cry; but this protects us against sins like users QDir d(cachePath()); @@ -86,7 +87,7 @@ hashKey; } -static QByteArray cacheKey(const QString &id, const QSize &requestedSize) +QByteArray NemoThumbnailProvider::cacheKey(const QString &id, const QSize &requestedSize) { QByteArray baId = id.toLatin1(); // is there a more efficient way than a copy? @@ -115,7 +116,7 @@ return QImage(); } -static void writeCacheFile(const QByteArray &hashKey, const QImage &img) +void NemoThumbnailProvider::writeCacheFile(const QByteArray &hashKey, const QImage &img) { QFile fi(cacheFileName(hashKey, true)); if (!fi.open(QIODevice::WriteOnly)) { @@ -187,7 +188,7 @@ // needed for stupid things like gallery model, which pass us a url if (id.startsWith("file://")) { - qWarning() << Q_FUNC_INFO << "Removing file:// prefix, before: " << id; +// qWarning() << Q_FUNC_INFO << "Removing file:// prefix, before: " << id; QString &nid = const_cast<QString &>(id); nid = nid.remove(0, 7); } @@ -209,32 +210,55 @@ return img; } + return generateThumbnail(id, hashData, requestedSize); +} + +QImage NemoThumbnailProvider::loadThumbnail(const QString &fileName, const QByteArray &cacheKey) +{ + return ::attemptCachedServe(fileName, cacheKey); +} + +QImage NemoThumbnailProvider::generateThumbnail(const QString &id, const QByteArray &hashData, const QSize &requestedSize, bool crop) +{ + QImage img; + QSize originalSize; + QByteArray format; + // image was not in cache thus we read it QImageReader ir(id); - QSize originalSize = ir.size(); - QByteArray format = ir.format(); + if (!ir.canRead()) + return img; + + originalSize = ir.size(); + format = ir.format(); - // scales arbitrary sized source image to requested size scaling either up or down - // keeping aspect ratio of the original image intact by maximizing either width or height - // and cropping the rest of the image away if (originalSize != requestedSize && originalSize.isValid()) { - QSize scaledSize(requestedSize); - // now scale it filling the original rectangle by keeping aspect ratio - scaledSize.scale(originalSize, Qt::KeepAspectRatio); - - // set the adjusted clipping rectangle in the center of the original image - QRect clipRect(0, 0, scaledSize.width(), scaledSize.height()); - QPoint originalCenterPoint(originalSize.width() / 2, originalSize.height() / 2); - clipRect.moveCenter(originalCenterPoint); - ir.setClipRect(clipRect); - - // set requested target size of a thumbnail - // as clipping rectangle is of same aspect ratio as requestedSize no distortion should happen - ir.setScaledSize(requestedSize); - img = ir.read(); + if (crop) { + // scales arbitrary sized source image to requested size scaling either up or down + // keeping aspect ratio of the original image intact by maximizing either width or height + // and cropping the rest of the image away + QSize scaledSize(requestedSize); + // now scale it filling the original rectangle by keeping aspect ratio + scaledSize.scale(originalSize, Qt::KeepAspectRatio); + + // set the adjusted clipping rectangle in the center of the original image + QRect clipRect(0, 0, scaledSize.width(), scaledSize.height()); + QPoint originalCenterPoint(originalSize.width() / 2, originalSize.height() / 2); + clipRect.moveCenter(originalCenterPoint); + ir.setClipRect(clipRect); + + // set requested target size of a thumbnail + // as clipping rectangle is of same aspect ratio as requestedSize no distortion should happen + ir.setScaledSize(requestedSize); + } else { + // Maintains correct aspect ratio without cropping, as such the final image may + // be smaller than requested in one dimension. + QSize scaledSize(originalSize); + scaledSize.scale(requestedSize, Qt::KeepAspectRatio); + ir.setScaledSize(scaledSize); + } } - else - img = ir.read(); + img = ir.read(); NemoImageMetadata meta(id, format); if (meta.orientation() != NemoImageMetadata::TopLeft) @@ -245,6 +269,7 @@ (originalSize != requestedSize && originalSize.isValid())) { writeCacheFile(hashData, img); } + TDEBUG() << Q_FUNC_INFO << "Wrote " << id << " to cache"; return img; } --- thumbnailer/nemothumbnailprovider.h +++ thumbnailer/nemothumbnailprovider.h @@ -43,6 +43,13 @@ } QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); + + + static void setupCache(); + static QByteArray cacheKey(const QString &fileName, const QSize &requestedSize); + static QImage loadThumbnail(const QString &fileName, const QByteArray &cacheKey); + static QImage generateThumbnail(const QString &fileName, const QByteArray &cacheKey, const QSize &requestedSize, bool crop = true); + static void writeCacheFile(const QByteArray &cacheKey, const QImage &thumbnail); }; #endif // NEMOTHUMBNAILPROVIDER_H --- thumbnailer/nemovideothumbnailer.cpp +++ thumbnailer/nemovideothumbnailer.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2012 Jolla Ltd + * Contact: Andrew den Exter <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#include "nemovideothumbnailer.h" +#include "nemothumbnailprovider.h" + +#include <QLibrary> + +typedef QImage (*CreateThumbnailFunc)(const QString &fileName, const QSize &requestedSize, bool crop); + +namespace NemoVideoThumbnailer { + +QImage generateThumbnail(const QString &fileName, const QByteArray &cacheKey, const QSize &requestedSize, bool crop) +{ + QImage image; + + static CreateThumbnailFunc createThumbnail = (CreateThumbnailFunc)QLibrary::resolve( + QLatin1String(NEMO_THUMBNAILER_DIR "/libvideothumbnailer.so"), "createThumbnail"); + + if (createThumbnail) { + image = createThumbnail(fileName, requestedSize, crop); + + if (!image.isNull()) + NemoThumbnailProvider::writeCacheFile(cacheKey, image); + } else { + qWarning("Cannot generate video thumbnail, thumbnailer function not available."); + } + + return image; +} + +} --- thumbnailer/nemovideothumbnailer.h +++ thumbnailer/nemovideothumbnailer.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2012 Jolla Ltd + * Contact: Andrew den Exter <[email protected]> + * + * You may use this file under the terms of the BSD license as follows: + * + * "Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Nemo Mobile nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + */ + +#ifndef NEMOVIDEOTHUMBNAILER_H +#define NEMOVIDEOTHUMBNAILER_H + +#include <QImage> + +namespace NemoVideoThumbnailer +{ + QImage generateThumbnail(const QString &fileName, const QByteArray &cacheKey, const QSize &requestedSize, bool crop); +} + +#endif --- thumbnailer/plugin.cpp +++ thumbnailer/plugin.cpp @@ -34,13 +34,9 @@ #include <QDeclarativeEngine> #include <QDeclarativeExtensionPlugin> +#include "nemothumbnailitem.h" #include "nemothumbnailprovider.h" -// see registration below -class DummyThumbnailerItem : public QDeclarativeItem -{ -}; - class Q_DECL_EXPORT NemoThumbnailerPlugin : public QDeclarativeExtensionPlugin { public: @@ -50,20 +46,20 @@ { Q_ASSERT(uri == QLatin1String("org.nemomobile.thumbnailer")); engine->addImageProvider(QLatin1String("nemoThumbnail"), new NemoThumbnailProvider); + + m_loader.start(QThread::IdlePriority); + qAddPostRoutine(NemoThumbnailLoader::shutdown); } void registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.nemomobile.thumbnailer")); - // if we don't register at least one type, then - // QDeclarativeMetaType::isModule won't consider us a module. if that - // happens, the isModule check in QDeclarativeImportsPrivate::add will - // fail, and it won't consider us installed. - // - // +1 for ditzy hacks, huh? - qmlRegisterUncreatableType<DummyThumbnailerItem>(uri, 1, 0, "DummyNemoThumbnailType", "Dummy type to make QML consider this a module"); + qmlRegisterType<NemoThumbnailItem>(uri, 1, 0, "Thumbnail"); } + +private: + NemoThumbnailLoader m_loader; }; Q_EXPORT_PLUGIN2(nemothumbnailer, NemoThumbnailerPlugin); --- thumbnailer/plugin.pro +++ thumbnailer/plugin.pro @@ -0,0 +1,16 @@ +TARGET = nemothumbnailer +PLUGIN_IMPORT_PATH = org/nemomobile/thumbnailer + +SOURCES += plugin.cpp \ + nemothumbnailprovider.cpp \ + nemoimagemetadata.cpp \ + nemothumbnailitem.cpp \ + nemovideothumbnailer.cpp +HEADERS += nemothumbnailprovider.h \ + nemoimagemetadata.h \ + nemothumbnailitem.h \ + nemovideothumbnailer.h + +DEFINES += NEMO_THUMBNAILER_DIR=\\\"$$[QT_INSTALL_IMPORTS]/$$$$PLUGIN_IMPORT_PATH/thumbnailers\\\" + +include(../plugin.pri) --- thumbnailer/thumbnailer.pro +++ thumbnailer/thumbnailer.pro @@ -1,10 +1,6 @@ -TARGET = nemothumbnailer -PLUGIN_IMPORT_PATH = org/nemomobile/thumbnailer +TEMPLATE = subdirs -SOURCES += plugin.cpp \ - nemothumbnailprovider.cpp \ - nemoimagemetadata.cpp -HEADERS += nemothumbnailprovider.h \ - nemoimagemetadata.h +SUBDIRS = plugin.pro -include(../plugin.pri) +packagesExist(gstreamer-0.10 gstreamer-app-0.10): SUBDIRS += gstvideothumbnailer +else: warning("gstreamer packages not available, video thumbnailing disabled") ++++++ nemo-qml-plugins.yaml --- nemo-qml-plugins.yaml +++ nemo-qml-plugins.yaml @@ -2,7 +2,7 @@ Summary: Nemo QML plugins source package. Group: System/Libraries Description: Do not install this, install the subpackaged plugins. -Version: 0.1.5 +Version: 0.1.6 Release: 1 Sources: - "%{name}-%{version}.tar.bz2"
