diff --git a/applications/filebrowser/package/contents/ui/Browser.qml b/applications/filebrowser/package/contents/ui/Browser.qml index 2f9d8777..dad08b0d 100644 --- a/applications/filebrowser/package/contents/ui/Browser.qml +++ b/applications/filebrowser/package/contents/ui/Browser.qml @@ -1,542 +1,542 @@ /* * 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: { // 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() } + onFinishedListingChanged: { searchBox.setIdle() } } 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 leftMargin: -1 } } 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 } //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 } } } } Image { source: "image://appbackgrounds/shadow-bottom" fillMode: Image.TileHorizontally opacity: 0.8 anchors { left: parent.left top: toolBar.bottom right: parent.right topMargin: -2 } } 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/applications/videoplayer/package/contents/ui/Toolbar.qml b/applications/videoplayer/package/contents/ui/Toolbar.qml index c98e3061..c0bae2d7 100644 --- a/applications/videoplayer/package/contents/ui/Toolbar.qml +++ b/applications/videoplayer/package/contents/ui/Toolbar.qml @@ -1,129 +1,129 @@ /* * 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.0 import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.qtextracomponents 0.1 PlasmaCore.FrameSvgItem { id: toolbar anchors { left: parent.left right: parent.right } signal zoomIn() signal zoomOut() height: childrenRect.height + margins.bottom imagePath: "widgets/frame" prefix: "raised" enabledBorders: "BottomBorder" z: 9000 Behavior on y { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } QIconItem { icon: QIcon("go-previous") width: 48 height: 48 opacity: viewer.scale==1?1:0 anchors.verticalCenter: parent.verticalCenter Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } MouseArea { anchors.fill: parent onClicked: imageViewer.state = "browsing" } } Text { text: i18n("%1 of %2", fullList.currentIndex+1, fullList.count) anchors.centerIn: parent font.pointSize: 14 font.bold: true color: theme.textColor visible: viewer.scale==1 style: Text.Raised styleColor: theme.backgroundColor } MobileComponents.ViewSearch { id: searchBox anchors { left: parent.left right:parent.right top:parent.top } onSearchQueryChanged: { filterModel.filterRegExp = ".*"+searchBox.searchQuery+".*" busy = (searchBox.searchQuery.length > 0) } Connections { target: filterModel - onCountChanged: { searchBox.restartBusyTimer() } + onFinishedListingChanged: { searchBox.setIdle() } } opacity: viewer.scale==1?0:1 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } PlasmaCore.Svg { id: iconsSvg imagePath: "widgets/configuration-icons" } Row { opacity: viewer.scale==1?1:0 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } anchors { verticalCenter: parent.verticalCenter right: parent.right } //TODO: ad hoc icons MobileComponents.ActionButton { svg: iconsSvg elementId: "add" onClicked: { toolbar.zoomIn() } } MobileComponents.ActionButton { svg: iconsSvg elementId: "remove" onClicked: { toolbar.zoomOut() } } } } diff --git a/components/metadatamodel/metadatamodel.cpp b/components/metadatamodel/metadatamodel.cpp index 9279ee70..15867316 100644 --- a/components/metadatamodel/metadatamodel.cpp +++ b/components/metadatamodel/metadatamodel.cpp @@ -1,945 +1,949 @@ /* 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 // 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); } + + // Nepomuk::Query::QueryServiceClient does not emit finishedListing signal when there is no new entries (no matches). + QTimer::singleShot(5000, this, SIGNAL(finishedListingChanged())); } 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; } } } + + emit finishedListingChanged(); } 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" diff --git a/components/metadatamodel/metadatamodel.h b/components/metadatamodel/metadatamodel.h index 561cec4d..9cfa6957 100644 --- a/components/metadatamodel/metadatamodel.h +++ b/components/metadatamodel/metadatamodel.h @@ -1,241 +1,242 @@ /* 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. */ #ifndef METADATAMODEL_H #define METADATAMODEL_H #include "abstractmetadatamodel.h" #include #include #include #include #include #include #include namespace Nepomuk { class ResourceWatcher; } class QTimer; class KImageCache; /** * This is the main class of the Nepomuk model bindings: given a query built by assigning its properties such as queryString, resourceType, startDate etc, it constructs a model with a resource per row, with direct access of its main properties as roles. * * @author Marco Martin */ class MetadataModel : public AbstractMetadataModel { Q_OBJECT /** * @property string a free form query in the Nepomuk desktop query language */ Q_PROPERTY(QString queryString READ queryString WRITE setQueryString NOTIFY queryStringChanged) /** * @property Array list of fields the results will be sorted: their order is the priority in sorting */ Q_PROPERTY(QVariantList sortBy READ sortBy WRITE setSortBy NOTIFY sortByChanged) /** * @property SortOrder Qt.Ascending or Qt.Descending */ Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged) /** * @property int optional limit to cut off the results */ Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) /** * If true the resources will be filtered and sorted by the most relevant as a whole or in relation to the activity indicated in the activityId property */ Q_PROPERTY(bool scoreResources READ scoreResources WRITE setScoreResources NOTIFY scoreResourcesChanged) /** * load as less resources as possible from Nepomuk (only load when asked from the view) * default is true, you shouldn't need to change it. * if lazyLoading is false the results are live-updating, but will take a lot more system resources */ Q_PROPERTY(bool lazyLoading READ lazyLoading WRITE setLazyLoading NOTIFY lazyLoadingChanged) /** * Use this property to specify the size of thumbnail which the model should attempt to generate for the thumbnail role. */ Q_PROPERTY(QSize thumbnailSize READ thumbnailSize WRITE setThumbnailSize NOTIFY thumbnailSizeChanged) public: enum Roles { Label = Qt::UserRole+1, Description, Types, ClassName, GenericClassName, HasSymbol, Icon, Thumbnail, IsFile, Exists, Rating, NumericRating, Symbols, ResourceUri, ResourceType, MimeType, Url, Topics, TopicsNames, Tags, TagsNames }; MetadataModel(QObject *parent = 0); ~MetadataModel(); void setQuery(const Nepomuk::Query::Query &query); Nepomuk::Query::Query query() const; virtual int count() const {return m_resources.count();} void setQueryString(const QString &query); QString queryString() const; void setSortBy(const QVariantList &sortBy); QVariantList sortBy() const; void setSortOrder(Qt::SortOrder sortOrder); Qt::SortOrder sortOrder() const; void setLazyLoading(bool size); bool lazyLoading() const; void setLimit(int limit); int limit() const; void setScoreResources(bool score); bool scoreResources() const; void setThumbnailSize(const QSize &size); QSize thumbnailSize() const; /** * searches for a resource in the whole model * @arg resToFind the uri or url of the resource */ Q_INVOKABLE int find(const QString &resToFind); //Reimplemented QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; /** * Reimplemented * Just signal QSortFilterProxyModel to do the real sorting. * Use this class as parameter to QSortFilterProxyModel->setSourceModel (C++) or * PlasmaCore.SortFilterModel.sourceModel (QML) to get the real sorting. * WARNING: avoid putting this model into SortFilterModel if possible: * it would cause loading every single item of the model, * while for big models we want lazy loading. * rely on its internal sorting feature instead. * @see sortBy */ Q_INVOKABLE void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); /** * Compatibility with ListModel * @returns an Object that represents the item with all roles as properties */ Q_INVOKABLE QVariantHash get(int row) const; Q_SIGNALS: void queryStringChanged(); void sortByChanged(); void sortOrderChanged(); void limitChanged(); void lazyLoadingChanged(); void scoreResourcesChanged(); void thumbnailSizeChanged(); + void finishedListingChanged(); protected Q_SLOTS: void countQueryResult(const QList< Nepomuk::Query::Result > &entries); void newEntries(const QList< Nepomuk::Query::Result > &entries); void entriesRemoved(const QList &urls); virtual void doQuery(); void newEntriesDelayed(); void finishedListing(); void propertyChanged(Nepomuk::Resource res, Nepomuk::Types::Property prop, QVariant val); void showPreview(const KFileItem &item, const QPixmap &preview); void previewFailed(const KFileItem &item); void delayedPreview(); protected: void fetchResultsPage(int page); private: Nepomuk::Query::Query m_query; //mapping page->query client QHash m_queryClients; //mapping query client->page QHash m_pagesForClient; //where is the last valid (already populated) index for a given page QHash m_validIndexForPage; //keep always running at most 10 clients, get rid of the old ones //won't be possible to monitor forresources going away, but is too heavy QList m_queryClientsHistory; //how many service clients are running now? int m_runningClients; Nepomuk::Query::QueryServiceClient *m_countQueryClient; Nepomuk::ResourceWatcher* m_watcher; QVector m_resources; QHash > m_resourcesToInsert; QHash m_uriToResourceIndex; QTimer *m_newEntriesTimer; QTime m_elapsedTime; //pieces to build m_query QString m_queryString; int m_limit; int m_pageSize; bool m_scoreResources; QStringList m_sortBy; Qt::SortOrder m_sortOrder; //previews QTimer *m_previewTimer; QHash m_filesToPreview; QSize m_thumbnailSize; QHash m_previewJobs; KImageCache* m_imageCache; QStringList* m_thumbnailerPlugins; QHash > m_cachedResources; }; #endif diff --git a/components/mobilecomponents/ViewSearch.qml b/components/mobilecomponents/ViewSearch.qml index d4db80b3..159a8d0d 100644 --- a/components/mobilecomponents/ViewSearch.qml +++ b/components/mobilecomponents/ViewSearch.qml @@ -1,81 +1,89 @@ /* 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. */ import QtQuick 1.0 import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.qtextracomponents 0.1 import org.kde.plasma.mobilecomponents 0.1 as MobileComponents Item { id: searchFieldContainer property string searchQuery property int delay : 100 property bool busy: false + // hide busy indicator after busyTimeout miliseconds. + property alias busyTimeout: busyTimer.interval + onSearchQueryChanged: { searchField.text = searchQuery + busy = true + + if (busyTimeout > 0) { + busyTimer.restart() + } } width: searchField.width height: searchField.height PlasmaComponents.TextField { id : searchField placeholderText: i18n("Search...") clearButtonShown: true anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter onTextChanged: searchTimer.restart() } PlasmaComponents.BusyIndicator { anchors.verticalCenter: searchField.verticalCenter anchors.right: searchField.right anchors.rightMargin: searchFieldContainer.height height: searchField.height width: searchField.height visible: searchFieldContainer.busy - running: searchFieldContainer.busy + running: visible } - function restartBusyTimer() { - busyTimer.restart() + function setIdle() { + searchFieldContainer.busy = false } Timer { id: busyTimer repeat: false - interval: 1000 + interval: 0 running: false onTriggered: { searchFieldContainer.busy = false } } Timer { id: searchTimer interval: delay running: false repeat: false onTriggered: searchQuery = searchField.text } } diff --git a/qmlpackages/launcher/contents/ui/main.qml b/qmlpackages/launcher/contents/ui/main.qml index 620867fd..30d0e086 100644 --- a/qmlpackages/launcher/contents/ui/main.qml +++ b/qmlpackages/launcher/contents/ui/main.qml @@ -1,175 +1,180 @@ /* Copyright 2010 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. */ import QtQuick 1.0 import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.extras 0.1 as PlasmaExtras import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.plasma.extras 0.1 as PlasmaExtras import org.kde.plasma.mobilecomponents 0.1 as MobileComponents import org.kde.plasma.slccomponents 0.1 as SlcComponents import org.kde.runnermodel 0.1 as RunnerModels MouseArea { id: main width: 400 height: 150 //just to hide the keyboard onClicked: main.forceActiveFocus() signal itemLaunched function resetStatus() { searchField.searchQuery = "" appGrid.currentPage = 0 } Image { id: background width: parent.width * 1.5 height:parent.height source: "image://appbackgrounds/contextarea" fillMode: Image.Tile x: -((width-parent.width) * (appGrid.currentPage / appGrid.pagesCount)) Behavior on x { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } PlasmaExtras.ResourceInstance { id: resourceInstance } SlcComponents.SlcMenu { id: contextMenu } PlasmaCore.Theme { id: theme } PlasmaCore.DataSource { id: appsSource engine: "org.kde.active.apps" connectedSources: ["Apps"] } PlasmaCore.SortFilterModel { id: appsModel sourceModel: PlasmaCore.DataModel { keyRoleFilter: ".*" dataSource: appsSource } sortRole: "name" } RunnerModels.RunnerModel { id: runnerModel runners: [ "services", "nepomuksearch", "recentdocuments", "desktopsessions" , "PowerDevil", "calculator" ] - onCountChanged: { searchField.restartBusyTimer() } + + Component.onCompleted: { + runnerModel.finishedListingChanged.connect(searchField.setIdle) + } } MobileComponents.ViewSearch { id: searchField + // set the timeout for the busy indicator. + busyTimeout: 1000 + anchors { left: parent.left right: parent.right top: parent.top topMargin: 8 } onSearchQueryChanged: { if (searchQuery.length < 3) { //HACK: assigning null makes the view really discard the old model and assign the new one appGrid.model = null appGrid.model = appsModel runnerModel.query = "" busy = false } else { appGrid.model = runnerModel runnerModel.query = searchQuery - busy = true } } Component.onCompleted: { delay = 10 } } MobileComponents.IconGrid { id: appGrid model: appsModel onCurrentPageChanged: resourceInstance.uri = "" delegate: Component { MobileComponents.ResourceDelegate { id: launcherDelegate width: appGrid.delegateWidth height: appGrid.delegateHeight className: "FileDataObject" genericClassName: "FileDataObject" property string label: model["name"] ? model["name"] : model["label"] //property string mimeType: model["mimeType"] ? model["mimeType"] : "application/x-desktop" onPressAndHold: ParallelAnimation { PlasmaExtras.ReleasedAnimation { targetItem: launcherDelegate } ScriptAction { script: { resourceInstance.uri = model["resourceUri"] ? model["resourceUri"] : model["entryPath"] resourceInstance.title = model["name"] ? model["name"] : model["text"] } } } onClicked: { //showing apps model? if (searchField.searchQuery == "") { var service = appsSource.serviceForSource(appsSource.connectedSources[0]) var operation = service.operationDescription("launch") operation["Path"] = model["entryPath"] service.startOperationCall(operation) } else { runnerModel.run(index) } resetStatus() itemLaunched() } onPressed: PlasmaExtras.PressedAnimation { targetItem: launcherDelegate } onReleased: PlasmaExtras.ReleasedAnimation { targetItem: launcherDelegate } } } anchors { left: parent.left right: parent.right top: searchField.bottom bottom: parent.bottom topMargin: 6 bottomMargin: 4 } } } diff --git a/shell/widgetsexplorer/package/contents/ui/MenuTabBar.qml b/shell/widgetsexplorer/package/contents/ui/MenuTabBar.qml index f2508179..c7b7c3b2 100644 --- a/shell/widgetsexplorer/package/contents/ui/MenuTabBar.qml +++ b/shell/widgetsexplorer/package/contents/ui/MenuTabBar.qml @@ -1,226 +1,265 @@ /* * Copyright 2010 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.0 import org.kde.plasma.components 0.1 as PlasmaComponents import org.kde.plasma.core 0.1 as PlasmaCore import org.kde.metadatamodels 0.1 as MetadataModels import org.kde.runnermodel 0.1 as RunnerModels PlasmaComponents.TabBar { id: tabBar property Component startComponent: topComponent anchors { top: searchField.bottom topMargin: 8 horizontalCenter: parent.horizontalCenter } width: Math.min(implicitWidth, parent.width - 100) CategoryTab { id: topTab text: i18n("Top") component: topComponent resourceType: "_top" Timer { id: switchPageTimer interval: 100 onTriggered: { topTab.visible = (topPageExistenceModel.count >= 1) if (!topTab.visible) { tabBar.currentTab = appsTab stack.replace(appsComponent) } } } MetadataModels.MetadataModel { id: topPageExistenceModel activityId: "!"+activitySource.data["Status"]["Current"] scoreResources: true limit: 1 onStatusChanged: { if (status == MetadataModels.MetadataModel.Running) { switchPageTimer.running = true } } onCountChanged: switchPageTimer.running = true } } CategoryTab { id: appsTab text: i18n("Apps") component: appsComponent resourceType: "_Apps" } CategoryTab { text: i18n("Bookmarks") resourceType: "nfo:Bookmark" component: bookmarksComponent } CategoryTab { text: i18n("Contacts") resourceType: "nfo:Contact" component: contactsComponent } CategoryTab { text: i18n("Documents") resourceType: "nfo:Document" component: documentsComponent } CategoryTab { text: i18n("Images") resourceType: "nfo:Image" component: imagesComponent } CategoryTab { text: i18n("Music") resourceType: "nfo:Audio" component: musicComponent } CategoryTab { text: i18n("Videos") resourceType: "nfo:Video" component: videoComponent } CategoryTab { text: i18n("Widgets") component: widgetsComponent resourceType: "_Widgets" } Component { id: topComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: topModel sortOrder: Qt.DescendingOrder activityId: "!"+activitySource.data["Status"]["Current"] scoreResources: true queryString: "*" + searchField.searchQuery + "*" + + Component.onCompleted: { + topModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: appsComponent ResourceBrowser { defaultClassName: "FileDataObject" model: PlasmaCore.SortFilterModel { id: appsModel sourceModel: PlasmaCore.DataModel { keyRoleFilter: ".*" dataSource: PlasmaCore.DataSource { id: appsSource engine: "org.kde.active.apps" connectedSources: ["Apps"] interval: 0 } } sortRole: "name" filterRole: "name" filterRegExp: ".*"+searchField.searchQuery+".*" + + Component.onCompleted: { + appsModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: bookmarksComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: bookmarksModel sortOrder: Qt.AscendingOrder activityId: "!"+activitySource.data["Status"]["Current"] sortBy: ["nie:url"] resourceType: "nfo:Bookmark" + + Component.onCompleted: { + bookmarksModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: contactsComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: contactsModel sortOrder: Qt.AscendingOrder activityId: "!"+activitySource.data["Status"]["Current"] sortBy: ["nco:fullname"] resourceType: "nco:Contact" queryString: "*" + searchField.searchQuery + "*" + + Component.onCompleted: { + contactsModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: documentsComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: documentsModel sortBy: ["nfo:fileName"] activityId: "!"+activitySource.data["Status"]["Current"] sortOrder: Qt.AscendingOrder resourceType: "nfo:Document" queryString: "*" + searchField.searchQuery + "*" + + Component.onCompleted: { + documentsModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: imagesComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: imagesModel sortBy: ["nfo:fileName"] activityId: "!"+activitySource.data["Status"]["Current"] sortOrder: Qt.AscendingOrder resourceType: "nfo:Image" queryString: "*" + searchField.searchQuery + "*" + + Component.onCompleted: { + imagesModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: musicComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: musicModel sortBy: ["nie:title"] activityId: "!"+activitySource.data["Status"]["Current"] sortOrder: Qt.AscendingOrder resourceType: "nfo:Audio" queryString: "*" + searchField.searchQuery + "*" + + Component.onCompleted: { + musicModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: videoComponent ResourceBrowser { model: MetadataModels.MetadataModel { + id: videoModel sortBy: ["nfo:fileName"] activityId: "!"+activitySource.data["Status"]["Current"] sortOrder: Qt.AscendingOrder resourceType: "nfo:Video" queryString: "*" + searchField.searchQuery + "*" + + Component.onCompleted: { + videoModel.finishedListingChanged.connect(searchField.setIdle) + } } } } Component { id: widgetsComponent WidgetExplorer {} } } diff --git a/shell/widgetsexplorer/package/contents/ui/view.qml b/shell/widgetsexplorer/package/contents/ui/view.qml index f08bf5a9..fa918f5d 100644 --- a/shell/widgetsexplorer/package/contents/ui/view.qml +++ b/shell/widgetsexplorer/package/contents/ui/view.qml @@ -1,170 +1,169 @@ /* * Copyright 2010 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.0 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.metadatamodels 0.1 as MetadataModels import org.kde.runnermodel 0.1 as RunnerModels PlasmaComponents.Sheet { id: widgetsExplorer objectName: "widgetsExplorer" title: i18n("Add Items") acceptButtonText: i18n("Add Items") rejectButtonText: i18n("Cancel") signal addAppletRequested(string plugin) signal closeRequested function addItems() { var service = metadataSource.serviceForSource("") var operation = service.operationDescription("connectToActivity") operation["ActivityUrl"] = activitySource.data["Status"]["Current"] for (var i = 0; i < selectedModel.count; ++i) { var item = selectedModel.get(i) if (item.resourceUri) { operation["ResourceUrl"] = item.resourceUri service.startOperationCall(operation) } else if (item.pluginName) { widgetsExplorer.addAppletRequested(item.pluginName) } } } //used only toexplicitly close the keyboard TextInput { id: inputPanelController; width:0; height:0} PlasmaCore.DataSource { id: metadataSource engine: "org.kde.active.metadata" } Binding { target: acceptButton property: "enabled" value: selectedModel.count > 0 } onAccepted: { widgetsExplorer.addItems() } onStatusChanged: { if (status == PlasmaComponents.DialogStatus.Closed) { closeRequested() } } ListModel { id: selectedModel } MetadataModels.MetadataUserTypes { id: userTypes } MetadataModels.MetadataCloudModel { id: cloudModel cloudCategory: "rdf:type" allowedCategories: userTypes.userTypes } PlasmaCore.DataSource { id: activitySource engine: "org.kde.activities" connectedSources: ["Status"] interval: 0 } Timer { running: true interval: 100 onTriggered: open() } content: [ MobileComponents.ViewSearch { id: searchField MobileComponents.IconButton { icon: QIcon("go-previous") width: 32 height: 32 onClicked: { searchField.searchQuery = "" stack.pop() } opacity: stack.depth > 1 ? 1 : 0 Behavior on opacity { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad } } } anchors { left: parent.left right: parent.right top: parent.top } onSearchQueryChanged: { if (stack.depth == 1 && searchQuery.length > 3) { stack.push(globalSearchComponent) - busy = true - } else { - busy = false } } }, MenuTabBar { id: tabBar }, PlasmaComponents.PageStack { id: stack clip: true anchors { left: parent.left right: parent.right top: tabBar.bottom bottom: parent.bottom } initialPage: tabBar.startComponent } ] Component { id: globalSearchComponent ResourceBrowser { model: MetadataModels.MetadataModel { id: runnerModel queryString: searchField.searchQuery.length > 3 ? "*" + searchField.searchQuery + "*" : "" onQueryStringChanged: { if (searchField.searchQuery.length <= 3) { stack.pop() } } - onCountChanged: { searchField.restartBusyTimer() } + Component.onCompleted: { + runnerModel.finishedListingChanged.connect(searchField.setIdle) + } } } } }