diff --git a/applications/filebrowser/package/contents/ui/Browser.qml b/applications/filebrowser/package/contents/ui/Browser.qml index be10076c..9bc6fc23 100644 --- a/applications/filebrowser/package/contents/ui/Browser.qml +++ b/applications/filebrowser/package/contents/ui/Browser.qml @@ -1,528 +1,529 @@ /* * Copyright 2011 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 1.1 import org.kde.metadatamodels 0.1 as MetadataModels import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.plasma.slccomponents 0.1 as SlcComponents import org.kde.draganddrop 1.0 import org.kde.qtextracomponents 0.1 PlasmaComponents.Page { id: resourceBrowser objectName: "resourceBrowser" property string currentUdi anchors { fill: parent topMargin: toolBar.height } PlasmaCore.DataSource { id: hotplugSource engine: "hotplug" connectedSources: sources } PlasmaCore.DataSource { id: devicesSource engine: "soliddevice" connectedSources: hotplugSource.sources onDataChanged: { //access it here due to the async nature of the dataengine if (resultsGrid.model != dirModel && devicesSource.data[resourceBrowser.currentUdi]["File Path"] != "") { dirModel.url = devicesSource.data[resourceBrowser.currentUdi]["File Path"] fileBrowserRoot.model = dirModel } } } //FIXME: this will have to be removed Timer { interval: 100 running: true onTriggered: backConnection.target = application.action("back") } Connections { id: backConnection target: application.action("back") onTriggered: { resourceInstance.uri = "" fileBrowserRoot.goBack() } } tools: Item { width: parent.width height: childrenRect.height PlasmaCore.DataModel { id: devicesModel dataSource: hotplugSource } Breadcrumb { id: breadCrumb path: dirModel.url.substr(devicesSource.data[resourceBrowser.currentUdi]["File Path"].length + String("file://").length) anchors { left: parent.left right: searchBox.left verticalCenter: parent.verticalCenter leftMargin: y } } MobileComponents.ViewSearch { id: searchBox anchors.centerIn: parent onSearchQueryChanged: { - metadataModel.extraParameters["nfo:fileName"] = searchBox.searchQuery + // the "*" are needed for substring match. + metadataModel.extraParameters["nfo:fileName"] = "*" + searchBox.searchQuery + "*" busy = (searchBox.searchQuery.length > 0) } } Item { width: childrenRect.width height: childrenRect.height clip: true anchors { right: emptyTrashButton.left bottom: parent.bottom bottomMargin: -4 rightMargin: 4 } PlasmaComponents.ButtonRow { z: 900 y: sidebar.open ? 0 : height exclusive: true Behavior on y { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } SidebarTab { id: mainTab text: i18n("Tools") onCheckedChanged: { if (checked) { while (sidebarStack.depth > 1) { sidebarStack.pop() } } } } SidebarTab { text: i18n("Time") enabled: fileBrowserRoot.model == metadataModel opacity: enabled ? 1 : 0.6 onCheckedChanged: { if (checked) { if (sidebarStack.depth > 1) { sidebarStack.replace(Qt.createComponent("TimelineSidebar.qml")) } else { sidebarStack.push(Qt.createComponent("TimelineSidebar.qml")) } } } } SidebarTab { text: i18n("Tags") enabled: fileBrowserRoot.model == metadataModel opacity: enabled ? 1 : 0.6 onCheckedChanged: { print(checked) if (checked) { if (sidebarStack.depth > 1) { sidebarStack.replace(Qt.createComponent("TagsBar.qml")) } else { sidebarStack.push(Qt.createComponent("TagsBar.qml")) } } } } } } PlasmaComponents.ToolButton { id: emptyTrashButton width: theme.largeIconSize height: width anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: y } visible: fileBrowserRoot.model == dirModel && dirModel.url == "trash:/" enabled: dirModel.count > 0 iconSource: "trash-empty" onClicked: application.emptyTrash() } } ListModel { id: selectedModel signal modelCleared } Connections { target: metadataModel onModelReset: { selectedModel.clear() selectedModel.modelCleared() } } //BUG: For some reason onCountChanged doesn't get binded directly in ListModel Connections { target: selectedModel onCountChanged: { var newUrls = new Array() for (var i = 0; i < selectedModel.count; ++i) { newUrls[i] = selectedModel.get(i).url } dragArea.mimeData.urls = newUrls } } Connections { target: metadataModel onModelReset: selectedModel.clear() onCountChanged: { searchBox.restartBusyTimer() } } Image { id: browserFrame z: 100 source: "image://appbackgrounds/standard" fillMode: Image.Tile anchors { top: parent.top bottom: parent.bottom } width: parent.width x: 0 //This pinch area is for selection PinchArea { id: pinchArea anchors { fill: parent leftMargin: 0 } property bool selecting: false property int selectingX property int selectingY pinch.target: parent onPinchStarted: { //hotspot to start select procedures print("point1: " + pinch.point1.x + " " + pinch.point1.y) print("Selecting") selecting = true selectingX = pinch.point2.x selectingY = pinch.point2.y selectionRect.opacity = 0.4 } onPinchUpdated: { //only one point if (pinch.point1.x == pinch.point2.x) { return } selectionRect.x = Math.min(pinch.point1.x, pinch.point2.x) selectionRect.y = Math.min(pinch.point1.y, pinch.point2.y) selectionRect.width = Math.abs(pinch.point2.x - pinch.point1.x) selectionRect.height = Math.abs(pinch.point2.y - pinch.point1.y) if (selecting) { print("Selected" + resultsGrid.childAt(pinch.point2.x, pinch.point2.y)) selectingX = pinch.point2.x selectingY = pinch.point2.y } } onPinchFinished: { selectionRect.opacity = 0 selecting = false } Rectangle { id: selectionRect color: theme.highlightColor opacity: 0.4 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } DragArea { id: dragArea anchors.fill: parent //startDragDistance: 200 enabled: false mimeData { source: parent } onDrop: enabled = false MouseEventListener { anchors.fill: parent onPressed: startY = mouse.y onPositionChanged: { if (selectedModel.count > 0 && Math.abs(mouse.y - startY) > 200 && !contextMenu.visible) { parent.enabled = true } } onReleased: { selectedModel.modelCleared() selectedModel.clear() selectionRect.x = -1 selectionRect.y = -1 selectionRect.width = 0 selectionRect.height = 0 } MobileComponents.IconGrid { id: resultsGrid anchors.fill: parent model: fileBrowserRoot.model delegate: Item { id: resourceDelegate width: resultsGrid.delegateWidth height: resultsGrid.delegateHeight PlasmaCore.FrameSvgItem { id: highlightFrame imagePath: "widgets/viewitem" prefix: "selected+hover" anchors.fill: parent property bool contains: resourceDelegate.x+resourceDelegate.width > selectionRect.x && resourceDelegate.y+resourceDelegate.height > selectionRect.y && resourceDelegate.x < selectionRect.x+selectionRect.width && resourceDelegate.y < selectionRect.y+selectionRect.height opacity: 0 /*Behavior on opacity { NumberAnimation {duration: 250} }*/ onContainsChanged: { if (contains) { selectedModel.append({"url": model.url}) opacity = 1 } else { for (var i = 0; i < selectedModel.count; ++i) { if ((model.url && model.url == selectedModel.get(i).url)) { opacity = 0 selectedModel.remove(i) return } } } } } Connections { target: selectedModel onModelCleared: highlightFrame.opacity = 0 } MobileComponents.ResourceDelegate { className: model["className"] ? model["className"] : "" genericClassName: (resultsGrid.model == metadataModel) ? (model["genericClassName"] ? model["genericClassName"] : "") : "FileDataObject" property string label: model.label ? model.label : model.display width: resultsGrid.delegateWidth height: resultsGrid.delegateHeight onPressAndHold: { resourceInstance.uri = model["url"] ? model["url"] : model["resourceUri"] resourceInstance.title = model["label"] if (highlightFrame.opacity == 1) { for (var i = 0; i < selectedModel.count; ++i) { if ((model.url && model.url == selectedModel.get(i).url)) { highlightFrame.opacity = 0 selectedModel.remove(i) return } } } else { highlightFrame.opacity = 1 selectedModel.append({"url": model.url}) } } onClicked: openFile(model["url"], mimeType) } Component.onCompleted: { for (var i = 0; i < selectedModel.count; ++i) { if ((model.url && model.url == selectedModel.get(i).url)) { highlightFrame.opacity = 1 return } } } } } } } } Image { source: "image://appbackgrounds/shadow-right" fillMode: Image.TileVertically anchors { left: parent.right top: parent.top bottom: parent.bottom } } PlasmaCore.FrameSvgItem { id: handleGraphics imagePath: "dialogs/background" enabledBorders: "LeftBorder|TopBorder|BottomBorder" width: handleIcon.width + margins.left + margins.right + 4 height: handleIcon.width * 1.6 + margins.top + margins.bottom + 4 anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: -1 } //TODO: an icon PlasmaCore.SvgItem { id: handleIcon svg: PlasmaCore.Svg {imagePath: "toolbar-icons/show"} elementId: "show-menu" x: parent.margins.left y: parent.margins.top width: theme.smallMediumIconSize height: width anchors.verticalCenter: parent.verticalCenter } } MouseArea { anchors { top: parent.top bottom: parent.bottom left: handleGraphics.left right: handleGraphics.right } drag { target: browserFrame axis: Drag.XAxis //-50, an overshoot to make it look smooter minimumX: -sidebar.width - 50 maximumX: 0 } property int startX property bool toggle: true onPressed: { startX = browserFrame.x toggle = true } onPositionChanged: { if (Math.abs(browserFrame.x - startX) > 20) { toggle = false } } onReleased: { if (toggle) { sidebar.open = !sidebar.open } else { sidebar.open = (browserFrame.x < -sidebar.width/2) } sidebarSlideAnimation.to = sidebar.open ? -sidebar.width : 0 sidebarSlideAnimation.running = true } } //FIXME: use a state machine SequentialAnimation { id: sidebarSlideAnimation property alias to: actualSlideAnimation.to NumberAnimation { id: actualSlideAnimation target: browserFrame properties: "x" duration: 250 easing.type: Easing.InOutQuad } ScriptAction { script: pinchArea.anchors.leftMargin = -browserFrame.x } } } Item { id: sidebar property bool open: false width: parent.width/4 x: parent.width - width Behavior on width { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } anchors { top: parent.top bottom: parent.bottom } Item { anchors.fill: parent clip: true PlasmaComponents.PageStack { id: sidebarStack width: fileBrowserRoot.width/4 - theme.defaultFont.mSize.width * 2 initialPage: Qt.createComponent("CategorySidebar.qml") anchors { left: parent.left top: parent.top bottom: parent.bottom bottomMargin: 0 topMargin: toolBar.height leftMargin: theme.defaultFont.mSize.width * 2 rightMargin: theme.defaultFont.mSize.width } } } } ParallelAnimation { id: positionAnim property Item target property int x property int y NumberAnimation { target: positionAnim.target to: positionAnim.y properties: "y" duration: 250 easing.type: Easing.InOutQuad } NumberAnimation { target: positionAnim.target to: positionAnim.x properties: "x" duration: 250 easing.type: Easing.InOutQuad } } SlcComponents.SlcMenu { id: contextMenu } } diff --git a/components/metadatamodel/metadatamodel.cpp b/components/metadatamodel/metadatamodel.cpp index 5947c47e..9279ee70 100644 --- a/components/metadatamodel/metadatamodel.cpp +++ b/components/metadatamodel/metadatamodel.cpp @@ -1,943 +1,945 @@ /* Copyright 2011 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "metadatamodel.h" #include "resourcewatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kao.h" using namespace Nepomuk::Vocabulary; using namespace Soprano::Vocabulary; MetadataModel::MetadataModel(QObject *parent) : AbstractMetadataModel(parent), m_runningClients(0), m_countQueryClient(0), m_limit(0), m_pageSize(30), m_scoreResources(false), m_thumbnailSize(180, 120), m_thumbnailerPlugins(new QStringList(KIO::PreviewJob::availablePlugins())) { m_newEntriesTimer = new QTimer(this); m_newEntriesTimer->setSingleShot(true); connect(m_newEntriesTimer, SIGNAL(timeout()), this, SLOT(newEntriesDelayed())); m_previewTimer = new QTimer(this); m_previewTimer->setSingleShot(true); connect(m_previewTimer, SIGNAL(timeout()), this, SLOT(delayedPreview())); //using the same cache of the engine, they index both by url m_imageCache = new KImageCache("plasma_engine_preview", 10485760); m_watcher = new Nepomuk::ResourceWatcher(this); m_watcher->addProperty(NAO::numericRating()); connect(m_watcher, SIGNAL(propertyAdded(Nepomuk::Resource,Nepomuk::Types::Property,QVariant)), this, SLOT(propertyChanged(Nepomuk::Resource,Nepomuk::Types::Property,QVariant))); QHash roleNames; roleNames[Qt::DisplayRole] = "display"; roleNames[Qt::DecorationRole] = "decoration"; roleNames[Label] = "label"; roleNames[Description] = "description"; roleNames[Types] = "types"; roleNames[ClassName] = "className"; roleNames[GenericClassName] = "genericClassName"; roleNames[HasSymbol] = "hasSymbol"; roleNames[Icon] = "icon"; roleNames[Thumbnail] = "thumbnail"; roleNames[IsFile] = "isFile"; roleNames[Exists] = "exists"; roleNames[Rating] = "rating"; roleNames[NumericRating] = "numericRating"; roleNames[Symbols] = "symbols"; roleNames[ResourceUri] = "resourceUri"; roleNames[ResourceType] = "resourceType"; roleNames[MimeType] = "mimeType"; roleNames[Url] = "url"; roleNames[Topics] = "topics"; roleNames[TopicsNames] = "topicsNames"; roleNames[Tags] = "tags"; roleNames[TagsNames] = "tagsNames"; setRoleNames(roleNames); } MetadataModel::~MetadataModel() { delete m_imageCache; } void MetadataModel::setQuery(const Nepomuk::Query::Query &query) { m_query = query; if (Nepomuk::Query::QueryServiceClient::serviceAvailable()) { askRefresh(); } } Nepomuk::Query::Query MetadataModel::query() const { return m_query; } void MetadataModel::setQueryString(const QString &query) { if (query == m_queryString || query == "nepomuk") { return; } m_queryString = query; askRefresh(); emit queryStringChanged(); } QString MetadataModel::queryString() const { return m_queryString; } void MetadataModel::setLimit(int limit) { if (limit == m_limit) { return; } m_limit = limit; askRefresh(); emit limitChanged(); } int MetadataModel::limit() const { return m_limit; } void MetadataModel::setScoreResources(bool score) { if (m_scoreResources == score) { return; } m_scoreResources = score; askRefresh(); emit scoreResourcesChanged(); } bool MetadataModel::scoreResources() const { return m_scoreResources; } void MetadataModel::setLazyLoading(bool lazy) { //lazy loading depends from the page zise, that is not directly user controllable if (lazy == (m_pageSize > 0)) { return; } //TODO: a way to control this? maybe from the available memory? m_pageSize = lazy ? 30 : -1; askRefresh(); emit lazyLoadingChanged(); } bool MetadataModel::lazyLoading() const { return (m_pageSize > 0); } void MetadataModel::setSortBy(const QVariantList &sortBy) { QStringList stringList = variantToStringList(sortBy); if (m_sortBy == stringList) { return; } m_sortBy = stringList; askRefresh(); emit sortByChanged(); } QVariantList MetadataModel::sortBy() const { return stringToVariantList(m_sortBy); } void MetadataModel::setSortOrder(Qt::SortOrder sortOrder) { if (m_sortOrder == sortOrder) { return; } m_sortOrder = sortOrder; askRefresh(); emit sortOrderChanged(); } Qt::SortOrder MetadataModel::sortOrder() const { return m_sortOrder; } int MetadataModel::find(const QString &resourceUri) { int index = -1; int i = 0; Nepomuk::Resource resToFind = Nepomuk::Resource::fromResourceUri(resourceUri); foreach (const Nepomuk::Resource &res, m_resources) { if (res == resToFind) { index = i; break; } ++i; } return index; } void MetadataModel::doQuery() { QDeclarativePropertyMap *parameters = qobject_cast(extraParameters()); //check if really all properties to build the query are null if (m_queryString.isEmpty() && resourceType().isEmpty() && mimeType().isEmpty() && activityId().isEmpty() && tagStrings().size() == 0 && !startDate().isValid() && !endDate().isValid() && minimumRating() <= 0 && maximumRating() <= 0 && parameters->size() == 0) { return; } setStatus(Waiting); m_query = Nepomuk::Query::Query(); m_query.setQueryFlags(Nepomuk::Query::Query::WithoutFullTextExcerpt); Nepomuk::Query::AndTerm rootTerm; if (!m_queryString.isEmpty()) { rootTerm.addSubTerm(Nepomuk::Query::QueryParser::parseQuery(m_queryString).term()); } if (!resourceType().isEmpty()) { //FIXME: more elegant QString type = resourceType(); bool negation = false; if (type.startsWith('!')) { type = type.remove(0, 1); negation = true; } if (negation) { rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(Nepomuk::Query::ResourceTypeTerm(propertyUrl(type)))); } else { rootTerm.addSubTerm(Nepomuk::Query::ResourceTypeTerm(propertyUrl(type))); if (type != "nfo:Bookmark") { //FIXME: remove bookmarks if not explicitly asked for rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(Nepomuk::Query::ResourceTypeTerm(propertyUrl("nfo:Bookmark")))); } } } if (!mimeType().isEmpty()) { QString type = mimeType(); bool negation = false; if (type.startsWith('!')) { type = type.remove(0, 1); negation = true; } Nepomuk::Query::ComparisonTerm term(Nepomuk::Vocabulary::NIE::mimeType(), Nepomuk::Query::LiteralTerm(type)); if (negation) { rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term)); } else { rootTerm.addSubTerm(term); } } if (parameters && parameters->size() > 0) { foreach (const QString &key, parameters->keys()) { QString parameter = parameters->value(key).toString(); if (parameter.isEmpty()) { continue; } bool negation = false; if (parameter.startsWith('!')) { parameter = parameter.remove(0, 1); negation = true; } //FIXME: Contains should work, but doesn't match for file names - Nepomuk::Query::ComparisonTerm term(propertyUrl(key), Nepomuk::Query::LiteralTerm(parameter), Nepomuk::Query::ComparisonTerm::Regexp); + // we must prepend and append "*" to the file name for the default Nepomuk match type (Contains) really work. + Nepomuk::Query::ComparisonTerm term(propertyUrl(key), Nepomuk::Query::LiteralTerm(parameter)); + if (negation) { rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term)); } else { rootTerm.addSubTerm(term); } } } if (!activityId().isEmpty()) { QString activity = activityId(); bool negation = false; if (activity.startsWith('!')) { activity = activity.remove(0, 1); negation = true; } kDebug() << "Asking for resources of activity" << activityId(); Nepomuk::Resource acRes(activity, Nepomuk::Vocabulary::KAO::Activity()); Nepomuk::Query::ComparisonTerm term(Soprano::Vocabulary::NAO::isRelated(), Nepomuk::Query::ResourceTerm(acRes)); term.setInverted(true); if (negation) { rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term)); } else { rootTerm.addSubTerm(term); } } foreach (const QString &tag, tagStrings()) { QString individualTag = tag; bool negation = false; if (individualTag.startsWith('!')) { individualTag = individualTag.remove(0, 1); negation = true; } Nepomuk::Query::ComparisonTerm term( Soprano::Vocabulary::NAO::hasTag(), Nepomuk::Query::ResourceTerm(Nepomuk::Tag(individualTag))); if (negation) { rootTerm.addSubTerm(Nepomuk::Query::NegationTerm::negateTerm(term)); } else { rootTerm.addSubTerm(term); } } if (startDate().isValid() || endDate().isValid()) { rootTerm.addSubTerm(Nepomuk::Query::dateRangeQuery(startDate(), endDate()).term()); } if (minimumRating() > 0) { const Nepomuk::Query::LiteralTerm ratingTerm(minimumRating()); Nepomuk::Query::ComparisonTerm term = Nepomuk::Types::Property(propertyUrl("nao:numericRating")) > ratingTerm; rootTerm.addSubTerm(term); } if (maximumRating() > 0) { const Nepomuk::Query::LiteralTerm ratingTerm(maximumRating()); Nepomuk::Query::ComparisonTerm term = Nepomuk::Types::Property(propertyUrl("nao:numericRating")) < ratingTerm; rootTerm.addSubTerm(term); } if (m_scoreResources) { QString activity = activityId(); if (activity.startsWith('!')) { activity = activity.remove(0, 1); } Nepomuk::Query::ComparisonTerm term = Nepomuk::Query::ComparisonTerm(propertyUrl("kao:targettedResource"), Nepomuk::Query::Term()); term.setVariableName("c"); term.setInverted(true); Nepomuk::Query::AndTerm andTerm = Nepomuk::Query::AndTerm(); Nepomuk::Query::ResourceTypeTerm typeTerm(KAO::ResourceScoreCache()); andTerm.addSubTerm(typeTerm); if (!activity.isEmpty()) { Nepomuk::Query::ComparisonTerm usedActivityTerm(propertyUrl("kao:usedActivity"), Nepomuk::Query::ResourceTerm(Nepomuk::Resource(activity, Nepomuk::Vocabulary::KAO::Activity())) ); andTerm.addSubTerm(usedActivityTerm); } Nepomuk::Query::ComparisonTerm cachedScoreTerm(propertyUrl("kao:cachedScore"), Nepomuk::Query::Term()); cachedScoreTerm.setVariableName("score"); cachedScoreTerm.setSortWeight(1, Qt::DescendingOrder); andTerm.addSubTerm(cachedScoreTerm); term.setSubTerm(andTerm); rootTerm.addSubTerm(term); } //bind directly some properties, to avoid calling hyper inefficient resource::property { m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NIE::url())); m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NAO::hasSymbol())); m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NIE::mimeType())); m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(NAO::description())); m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(Xesam::description())); m_query.addRequestProperty(Nepomuk::Query::Query::RequestProperty(RDFS::comment())); } int weight = m_sortBy.length() + 1; foreach (const QString &sortProperty, m_sortBy) { Nepomuk::Query::ComparisonTerm sortTerm(propertyUrl(sortProperty), Nepomuk::Query::Term()); sortTerm.setSortWeight(weight, m_sortOrder); rootTerm.addSubTerm(sortTerm); --weight; } m_query.setTerm(rootTerm); kDebug()<<"Sparql query:"<)), this, SLOT(countQueryResult(QList))); if (m_limit > 0) { m_query.setLimit(m_limit); } m_countQueryClient->sparqlQuery(m_query.toSparqlQuery(Nepomuk::Query::Query::CreateCountQuery)); //if page size is invalid, fetch all if (m_pageSize < 1) { fetchResultsPage(0); } } void MetadataModel::fetchResultsPage(int page) { Nepomuk::Query::QueryServiceClient *client = new Nepomuk::Query::QueryServiceClient(this); m_queryClients[page] = client; m_pagesForClient[client] = page; m_validIndexForPage[page] = 0; Nepomuk::Query::Query pageQuery(m_query); if (m_pageSize > 0) { pageQuery.setOffset(m_pageSize*page); pageQuery.setLimit(m_pageSize); } client->query(pageQuery); connect(client, SIGNAL(newEntries(QList)), this, SLOT(newEntries(QList))); connect(client, SIGNAL(entriesRemoved(QList)), this, SLOT(entriesRemoved(QList))); connect(client, SIGNAL(finishedListing()), this, SLOT(finishedListing())); m_queryClientsHistory << client; ++m_runningClients; } void MetadataModel::countQueryResult(const QList< Nepomuk::Query::Result > &entries) { setStatus(Running); //this should be always 1 foreach (const Nepomuk::Query::Result &res, entries) { int count = res.additionalBinding(QLatin1String("cnt")).variant().toInt(); if (count < m_resources.size()) { beginRemoveRows(QModelIndex(), count-1, m_resources.size()-1); m_resources.resize(count); endRemoveRows(); } else if (count > m_resources.size()) { beginInsertRows(QModelIndex(), m_resources.size(), count-1); m_resources.resize(count); endInsertRows(); } } } void MetadataModel::newEntries(const QList< Nepomuk::Query::Result > &entries) { setStatus(Running); const int page = m_pagesForClient.value(qobject_cast(sender())); foreach (const Nepomuk::Query::Result &res, entries) { //kDebug() << "Result!!!" << res.resource().genericLabel() << res.resource().type(); //kDebug() << "Result label:" << res.genericLabel(); Nepomuk::Resource resource = res.resource(); if (res.requestProperties().value(propertyUrl("nie:url")).toString().isEmpty()) { continue; } m_resourcesToInsert[page] << resource; //pre-popuplating of the cache to avoid accessing properties directly //label is a bit too complex to take from query if (resource.hasType(Nepomuk::Vocabulary::NFO::PaginatedTextDocument())) { // pdf files m_cachedResources[resource][Label] = resource.property(Nepomuk::Vocabulary::NFO::fileName()).toString(); //kDebug() << "Using label" << m_cachedResources[resource][Label] << "instead of" << resource.genericLabel(); } else { m_cachedResources[resource][Label] = resource.genericLabel(); } QString description = res.requestProperties().value(NAO::description()).toString(); if (description.isEmpty()) { description = res.requestProperties().value(Xesam::description()).toString(); } if (description.isEmpty()) { description = res.requestProperties().value(RDFS::comment()).toString(); } if (!description.isEmpty()) { m_cachedResources[resource][Description] = description; } m_cachedResources[resource][Url] = res.requestProperties().value(propertyUrl("nie:url")).toString(); QStringList types; foreach (const QUrl &u, resource.types()) { types << u.toString(); } m_cachedResources[resource][Types] = types; Soprano::Node symbol = res.requestProperties().value(NAO::hasSymbol()); if (!symbol.toString().isEmpty()) { Nepomuk::Resource symbolRes(symbol.uri()); m_cachedResources[resource][Icon] = symbolRes.genericLabel(); } else { //if it's an application, fetch the icon from the desktop file Nepomuk::Types::Class resClass(resource.resourceType()); if (resClass.label() == "Application") { KService::Ptr serv = KService::serviceByDesktopPath(m_cachedResources[resource][Url].toUrl().path()); if (serv) { m_cachedResources[resource][Icon] = serv->icon(); } else { m_cachedResources[resource][Icon] = KMimeType::iconNameForUrl(m_cachedResources[resource][Url].toString()); } } else { m_cachedResources[resource][Icon] = KMimeType::iconNameForUrl(m_cachedResources[resource][Url].toString()); } } //those seems to not be possible avoiding to access the resource m_cachedResources[resource][ClassName] = resource.className(); m_cachedResources[resource][ResourceType] = resource.resourceType(); m_cachedResources[resource][IsFile] = resource.isFile(); m_cachedResources[resource][HasSymbol] = res.requestProperties().value(NAO::hasSymbol()).toString(); m_cachedResources[resource][MimeType] = res.requestProperties().value(NIE::mimeType()).toString(); //FIXME: The most complicated of all, this should really be simplified { //FIXME: a more elegant way is needed QString genericClassName = m_cachedResources.value(resource).value(ClassName).toString(); //FIXME: most bookmarks are Document too, so Bookmark wins if (m_cachedResources.value(resource).value(Label).value >().contains(NFO::Bookmark())) { m_cachedResources[resource][GenericClassName] = "Bookmark"; } else { Nepomuk::Types::Class resClass(resource.resourceType()); foreach (const Nepomuk::Types::Class &parentClass, resClass.parentClasses()) { const QString label = parentClass.label(); if (label == "Document" || label == "Audio" || label == "Video" || label == "Image" || label == "Contact") { genericClassName = label; break; //two cases where the class is 2 levels behind the level of generalization we want } else if (parentClass.label() == "RasterImage") { genericClassName = "Image"; } else if (parentClass.label() == "TextDocument") { genericClassName = "Document"; } } m_cachedResources[resource][GenericClassName] = genericClassName; } } } if (!m_newEntriesTimer->isActive() && !m_resourcesToInsert[page].isEmpty()) { m_newEntriesTimer->start(200); } } void MetadataModel::newEntriesDelayed() { if (m_resourcesToInsert.isEmpty()) { return; } m_elapsedTime.start(); QHash >::const_iterator i; for (i = m_resourcesToInsert.constBegin(); i != m_resourcesToInsert.constEnd(); ++i) { const QList resourcesToInsert = i.value(); m_watcher->stop(); int pageStart = 0; if (m_pageSize > 0) { pageStart = i.key() * m_pageSize; } int startOffset = m_validIndexForPage.value(i.key()); int offset = startOffset; //if new result arrive on an already running query, they may arrive before countQueryResult if (m_resources.size() < pageStart + startOffset + 1) { beginInsertRows(QModelIndex(), m_resources.size(), pageStart + startOffset); m_resources.resize(pageStart + startOffset + 1); endInsertRows(); } //this happens only when m_validIndexForPage has been invalidate by row removal if (!m_validIndexForPage.contains(i.key()) && m_resources[pageStart + startOffset].isValid()) { while (startOffset < m_resources.size() && m_resources[pageStart + startOffset].isValid()) { ++startOffset; ++offset; } } foreach (const Nepomuk::Resource &res, resourcesToInsert) { //kDebug() << "Result!!!" << res.genericLabel() << res.type(); //kDebug() << "Page:" << i.key() << "Index:"<< pageStart + offset; m_uriToResourceIndex[res.resourceUri()] = pageStart + offset; //there can be new results before the count query gets updated if (pageStart + offset < m_resources.size()) { m_resources[pageStart + offset] = res; m_watcher->addResource(res); ++offset; } else { beginInsertRows(QModelIndex(), m_resources.size(), pageStart + offset); m_resources.resize(pageStart + offset + 1); m_resources[pageStart + offset] = res; m_watcher->addResource(res); ++offset; endInsertRows(); } } m_validIndexForPage[i.key()] = offset; m_watcher->start(); emit dataChanged(createIndex(pageStart + startOffset, 0), createIndex(pageStart + startOffset + resourcesToInsert.count()-1, 0)); } kDebug() << "Elapsed time populating the model" << m_elapsedTime.elapsed(); m_resourcesToInsert.clear(); } void MetadataModel::propertyChanged(Nepomuk::Resource res, Nepomuk::Types::Property prop, QVariant val) { Q_UNUSED(prop) Q_UNUSED(val) const int index = m_uriToResourceIndex.value(res.resourceUri()); if (index >= 0) { emit dataChanged(createIndex(index, 0, 0), createIndex(index, 0, 0)); } } void MetadataModel::entriesRemoved(const QList &urls) { int prevIndex = -100; //pack all the stuff to remove in groups, to emit the least possible signals //this assumes urls are in the same order they arrived ion the results //it's a map because we want to remove values from the vector in inverted order to keep indexes valid trough the remove loop QMap toRemove; foreach (const QUrl &url, urls) { const int index = m_uriToResourceIndex.value(url); if (index == prevIndex + 1) { toRemove[prevIndex]++; } else { toRemove[index] = 1; } prevIndex = index; } //all the page indexes may be invalid now m_validIndexForPage.clear(); QMap::const_iterator i = toRemove.constEnd(); while (i != toRemove.constBegin()) { --i; beginRemoveRows(QModelIndex(), i.key(), i.key()+i.value()-1); m_resources.remove(i.key(), i.value()); endRemoveRows(); } //another loop, we don't depend to m_uriToResourceIndex in data(), but we take this doublesafety foreach (const QUrl &url, urls) { m_uriToResourceIndex.remove(url); } //FIXME: this loop makes all the optimizations useless, get rid either of it or the optimizations for (int i = 0; i < m_resources.count(); ++i) { m_uriToResourceIndex[m_resources[i].resourceUri()] = i; } emit countChanged(); } void MetadataModel::finishedListing() { --m_runningClients; if (m_runningClients <= 0) { setStatus(Idle); if (m_queryClientsHistory.count() > 10) { for (int i = 0; i < m_queryClientsHistory.count() - 10; ++i) { Nepomuk::Query::QueryServiceClient *client = m_queryClientsHistory.first(); m_queryClientsHistory.pop_front(); int page = m_pagesForClient.value(client); m_queryClients.remove(page); m_pagesForClient.remove(client); delete client; } } } } QVariant MetadataModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= m_resources.count()){ return QVariant(); } const Nepomuk::Resource &resource = m_resources[index.row()]; if (!resource.isValid() && m_pageSize > 0 && !m_queryClients.contains(floor(index.row()/m_pageSize))) { //HACK const_cast(this)->fetchResultsPage(floor(index.row()/m_pageSize)); return QVariant(); //m_pageSize <= 0, means fetch all } else if (!resource.isValid() && !m_queryClients.contains(0)) { //HACK const_cast(this)->fetchResultsPage(0); return QVariant(); } else if (!resource.isValid()) { return QVariant(); } //We're lucky: was cached if (m_cachedResources.value(resource).contains(role)) { return m_cachedResources.value(resource).value(role); } switch (role) { case Qt::DisplayRole: case Label: return m_cachedResources.value(resource).value(Label); case Qt::DecorationRole: return KIcon(m_cachedResources.value(resource).value(Icon).toString()); case HasSymbol: case Icon: return m_cachedResources.value(resource).value(Icon).toString(); case Thumbnail: { KUrl url(m_cachedResources.value(resource).value(Url).toString()); if (m_cachedResources.value(resource).value(IsFile).toBool() && url.isLocalFile()) { QImage preview = QImage(m_thumbnailSize, QImage::Format_ARGB32_Premultiplied); if (m_imageCache->findImage(url.prettyUrl(), &preview)) { return preview; } m_previewTimer->start(100); const_cast(this)->m_filesToPreview[url] = QPersistentModelIndex(index); } return QVariant(); } case Exists: return resource.exists(); case Rating: return resource.rating(); case NumericRating: return resource.property(NAO::numericRating()).toString(); case Symbols: return resource.symbols(); case ResourceUri: return resource.resourceUri(); case Topics: { QStringList topics; foreach (const Nepomuk::Resource &u, resource.topics()) { topics << u.resourceUri().toString(); } return topics; } case TopicsNames: { QStringList topicNames; foreach (const Nepomuk::Resource &u, resource.topics()) { topicNames << u.genericLabel(); } return topicNames; } case Tags: { QStringList tags; foreach (const Nepomuk::Tag &tag, resource.tags()) { tags << tag.resourceUri().toString(); } return tags; } case TagsNames: { QStringList tagNames; foreach (const Nepomuk::Tag &tag, resource.tags()) { tagNames << tag.genericLabel(); } return tagNames; } default: return QVariant(); } } QVariantHash MetadataModel::get(int row) const { QModelIndex idx = index(row, 0); QVariantHash hash; QHash::const_iterator i; for (i = roleNames().constBegin(); i != roleNames().constEnd(); ++i) { hash[i.value()] = data(idx, i.key()); } return hash; } void MetadataModel::delayedPreview() { QHash::const_iterator i = m_filesToPreview.constBegin(); KFileItemList list; while (i != m_filesToPreview.constEnd()) { KUrl file = i.key(); QPersistentModelIndex index = i.value(); if (!m_previewJobs.contains(file) && file.isValid()) { list.append(KFileItem(file, QString(), 0)); m_previewJobs.insert(file, QPersistentModelIndex(index)); } ++i; } if (list.size() > 0) { KIO::PreviewJob* job = KIO::filePreview(list, m_thumbnailSize, m_thumbnailerPlugins); //job->setIgnoreMaximumSize(true); kDebug() << "Created job" << job; connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), this, SLOT(showPreview(KFileItem,QPixmap))); connect(job, SIGNAL(failed(KFileItem)), this, SLOT(previewFailed(KFileItem))); } m_filesToPreview.clear(); } void MetadataModel::showPreview(const KFileItem &item, const QPixmap &preview) { QPersistentModelIndex index = m_previewJobs.value(item.url()); m_previewJobs.remove(item.url()); if (!index.isValid()) { return; } m_imageCache->insertImage(item.url().prettyUrl(), preview.toImage()); //kDebug() << "preview size:" << preview.size(); emit dataChanged(index, index); } void MetadataModel::previewFailed(const KFileItem &item) { m_previewJobs.remove(item.url()); } // Just signal QSortFilterProxyModel to do the real sorting. void MetadataModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); Q_UNUSED(order); beginResetModel(); endResetModel(); } void MetadataModel::setThumbnailSize(const QSize& size) { m_thumbnailSize = size; emit thumbnailSizeChanged(); } QSize MetadataModel::thumbnailSize() const { return m_thumbnailSize; } #include "metadatamodel.moc"